Introduce Saved dialogs (#4177)
This commit is contained in:
parent
44261055e6
commit
c3c71cbc9e
@ -1,7 +1,9 @@
|
|||||||
export class ChatAbortController extends AbortController {
|
import type { ThreadId } from '../../types';
|
||||||
private threads = new Map<number, AbortController>();
|
|
||||||
|
|
||||||
public getThreadSignal(threadId: number): AbortSignal {
|
export class ChatAbortController extends AbortController {
|
||||||
|
private threads = new Map<ThreadId, AbortController>();
|
||||||
|
|
||||||
|
public getThreadSignal(threadId: ThreadId): AbortSignal {
|
||||||
let controller = this.threads.get(threadId);
|
let controller = this.threads.get(threadId);
|
||||||
if (!controller) {
|
if (!controller) {
|
||||||
controller = new AbortController();
|
controller = new AbortController();
|
||||||
@ -10,7 +12,7 @@ export class ChatAbortController extends AbortController {
|
|||||||
return controller.signal;
|
return controller.signal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abortThread(threadId: number, reason?: string): void {
|
public abortThread(threadId: ThreadId, reason?: string): void {
|
||||||
this.threads.get(threadId)?.abort(reason);
|
this.threads.get(threadId)?.abort(reason);
|
||||||
this.threads.delete(threadId);
|
this.threads.delete(threadId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -29,7 +29,8 @@ type Limit =
|
|||||||
| 'about_length_limit'
|
| 'about_length_limit'
|
||||||
| 'chatlist_invites_limit'
|
| 'chatlist_invites_limit'
|
||||||
| 'chatlist_joined_limit'
|
| 'chatlist_joined_limit'
|
||||||
| 'recommended_channels_limit';
|
| 'recommended_channels_limit'
|
||||||
|
| 'saved_dialogs_pinned_limit';
|
||||||
type LimitKey = `${Limit}_${LimitType}`;
|
type LimitKey = `${Limit}_${LimitType}`;
|
||||||
type LimitsConfig = Record<LimitKey, number>;
|
type LimitsConfig = Record<LimitKey, number>;
|
||||||
|
|
||||||
@ -124,6 +125,7 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
|||||||
chatlistInvites: getLimit(appConfig, 'chatlist_invites_limit', 'chatlistInvites'),
|
chatlistInvites: getLimit(appConfig, 'chatlist_invites_limit', 'chatlistInvites'),
|
||||||
chatlistJoined: getLimit(appConfig, 'chatlist_joined_limit', 'chatlistJoined'),
|
chatlistJoined: getLimit(appConfig, 'chatlist_joined_limit', 'chatlistJoined'),
|
||||||
recommendedChannels: getLimit(appConfig, 'recommended_channels_limit', 'recommendedChannels'),
|
recommendedChannels: getLimit(appConfig, 'recommended_channels_limit', 'recommendedChannels'),
|
||||||
|
savedDialogsPinned: getLimit(appConfig, 'saved_dialogs_pinned_limit', 'savedDialogsPinned'),
|
||||||
},
|
},
|
||||||
hash,
|
hash,
|
||||||
areStoriesHidden: appConfig.stories_all_hidden,
|
areStoriesHidden: appConfig.stories_all_hidden,
|
||||||
|
|||||||
@ -126,6 +126,20 @@ export function buildApiChatFromDialog(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildApiChatFromSavedDialog(
|
||||||
|
dialog: GramJs.SavedDialog,
|
||||||
|
peerEntity: GramJs.TypeUser | GramJs.TypeChat,
|
||||||
|
): ApiChat {
|
||||||
|
const { peer } = dialog;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: getApiChatIdFromMtpPeer(peer),
|
||||||
|
type: getApiChatTypeFromPeerEntity(peerEntity),
|
||||||
|
title: getApiChatTitleFromMtpPeer(peer, peerEntity),
|
||||||
|
...buildApiChatFieldsFromPeerEntity(peerEntity),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function buildApiChatPermissions(peerEntity: GramJs.TypeUser | GramJs.TypeChat): {
|
function buildApiChatPermissions(peerEntity: GramJs.TypeUser | GramJs.TypeChat): {
|
||||||
adminRights?: ApiChatAdminRights;
|
adminRights?: ApiChatAdminRights;
|
||||||
currentUserBannedRights?: ApiChatBannedRights;
|
currentUserBannedRights?: ApiChatBannedRights;
|
||||||
|
|||||||
@ -266,13 +266,15 @@ function buildApiMessageForwardInfo(fwdFrom: GramJs.MessageFwdHeader, isChatWith
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
date: fwdFrom.date,
|
date: fwdFrom.date,
|
||||||
|
savedDate: fwdFrom.savedDate,
|
||||||
isImported: fwdFrom.imported,
|
isImported: fwdFrom.imported,
|
||||||
isChannelPost: Boolean(fwdFrom.channelPost),
|
isChannelPost: Boolean(fwdFrom.channelPost),
|
||||||
channelPostId: fwdFrom.channelPost,
|
channelPostId: fwdFrom.channelPost,
|
||||||
isLinkedChannelPost: Boolean(fwdFrom.channelPost && savedFromPeerId && !isChatWithSelf),
|
isLinkedChannelPost: Boolean(fwdFrom.channelPost && savedFromPeerId && !isChatWithSelf),
|
||||||
|
savedFromPeerId,
|
||||||
|
fromId,
|
||||||
fromChatId: savedFromPeerId || fromId,
|
fromChatId: savedFromPeerId || fromId,
|
||||||
fromMessageId: fwdFrom.savedFromMsgId || fwdFrom.channelPost,
|
fromMessageId: fwdFrom.savedFromMsgId || fwdFrom.channelPost,
|
||||||
senderUserId: fromId,
|
|
||||||
hiddenUserName: fwdFrom.fromName,
|
hiddenUserName: fwdFrom.fromName,
|
||||||
postAuthorTitle: fwdFrom.postAuthor,
|
postAuthorTitle: fwdFrom.postAuthor,
|
||||||
};
|
};
|
||||||
@ -747,6 +749,7 @@ function buildNewPoll(poll: ApiNewPoll, localId: number) {
|
|||||||
|
|
||||||
export function buildLocalMessage(
|
export function buildLocalMessage(
|
||||||
chat: ApiChat,
|
chat: ApiChat,
|
||||||
|
lastMessageId?: number,
|
||||||
text?: string,
|
text?: string,
|
||||||
entities?: ApiMessageEntity[],
|
entities?: ApiMessageEntity[],
|
||||||
replyInfo?: ApiInputReplyInfo,
|
replyInfo?: ApiInputReplyInfo,
|
||||||
@ -760,7 +763,7 @@ export function buildLocalMessage(
|
|||||||
sendAs?: ApiPeer,
|
sendAs?: ApiPeer,
|
||||||
story?: ApiStory | ApiStorySkipped,
|
story?: ApiStory | ApiStorySkipped,
|
||||||
): ApiMessage {
|
): ApiMessage {
|
||||||
const localId = getNextLocalMessageId(chat.lastMessage?.id);
|
const localId = getNextLocalMessageId(lastMessageId);
|
||||||
const media = attachment && buildUploadingMedia(attachment);
|
const media = attachment && buildUploadingMedia(attachment);
|
||||||
const isChannel = chat.type === 'chatTypeChannel';
|
const isChannel = chat.type === 'chatTypeChannel';
|
||||||
|
|
||||||
@ -811,6 +814,7 @@ export function buildLocalForwardedMessage({
|
|||||||
noAuthors,
|
noAuthors,
|
||||||
noCaptions,
|
noCaptions,
|
||||||
isCurrentUserPremium,
|
isCurrentUserPremium,
|
||||||
|
lastMessageId,
|
||||||
}: {
|
}: {
|
||||||
toChat: ApiChat;
|
toChat: ApiChat;
|
||||||
toThreadId?: number;
|
toThreadId?: number;
|
||||||
@ -819,8 +823,9 @@ export function buildLocalForwardedMessage({
|
|||||||
noAuthors?: boolean;
|
noAuthors?: boolean;
|
||||||
noCaptions?: boolean;
|
noCaptions?: boolean;
|
||||||
isCurrentUserPremium?: boolean;
|
isCurrentUserPremium?: boolean;
|
||||||
|
lastMessageId?: number;
|
||||||
}): ApiMessage {
|
}): ApiMessage {
|
||||||
const localId = getNextLocalMessageId(toChat?.lastMessage?.id);
|
const localId = getNextLocalMessageId(lastMessageId);
|
||||||
const {
|
const {
|
||||||
content,
|
content,
|
||||||
chatId: fromChatId,
|
chatId: fromChatId,
|
||||||
@ -874,11 +879,13 @@ export function buildLocalForwardedMessage({
|
|||||||
// Forward info doesn't get added when users forwards his own messages, also when forwarding audio
|
// Forward info doesn't get added when users forwards his own messages, also when forwarding audio
|
||||||
...(message.chatId !== currentUserId && !isAudio && !noAuthors && {
|
...(message.chatId !== currentUserId && !isAudio && !noAuthors && {
|
||||||
forwardInfo: {
|
forwardInfo: {
|
||||||
date: message.date,
|
date: message.forwardInfo?.date || message.date,
|
||||||
|
savedDate: message.date,
|
||||||
isChannelPost: false,
|
isChannelPost: false,
|
||||||
fromChatId,
|
fromChatId,
|
||||||
fromMessageId,
|
fromMessageId,
|
||||||
senderUserId: senderId,
|
fromId: senderId,
|
||||||
|
savedFromPeerId: message.chatId,
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
...(message.chatId === currentUserId && !noAuthors && { forwardInfo: message.forwardInfo }),
|
...(message.chatId === currentUserId && !noAuthors && { forwardInfo: message.forwardInfo }),
|
||||||
|
|||||||
@ -37,6 +37,7 @@ import {
|
|||||||
buildApiChatFolderFromSuggested,
|
buildApiChatFolderFromSuggested,
|
||||||
buildApiChatFromDialog,
|
buildApiChatFromDialog,
|
||||||
buildApiChatFromPreview,
|
buildApiChatFromPreview,
|
||||||
|
buildApiChatFromSavedDialog,
|
||||||
buildApiChatlistExportedInvite,
|
buildApiChatlistExportedInvite,
|
||||||
buildApiChatlistInvite,
|
buildApiChatlistInvite,
|
||||||
buildApiChatReactions,
|
buildApiChatReactions,
|
||||||
@ -84,6 +85,18 @@ type FullChatData = {
|
|||||||
isForumAsMessages?: true;
|
isForumAsMessages?: true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type ChatListData = {
|
||||||
|
chatIds: string[];
|
||||||
|
chats: ApiChat[];
|
||||||
|
users: ApiUser[];
|
||||||
|
userStatusesById: Record<string, ApiUserStatus>;
|
||||||
|
draftsById: Record<string, ApiDraft>;
|
||||||
|
orderedPinnedIds: string[] | undefined;
|
||||||
|
totalChatCount: number;
|
||||||
|
messages: ApiMessage[];
|
||||||
|
lastMessageByChatId: Record<string, number>;
|
||||||
|
};
|
||||||
|
|
||||||
let onUpdate: OnApiUpdate;
|
let onUpdate: OnApiUpdate;
|
||||||
|
|
||||||
export function init(_onUpdate: OnApiUpdate) {
|
export function init(_onUpdate: OnApiUpdate) {
|
||||||
@ -95,19 +108,18 @@ export async function fetchChats({
|
|||||||
offsetDate,
|
offsetDate,
|
||||||
archived,
|
archived,
|
||||||
withPinned,
|
withPinned,
|
||||||
lastLocalServiceMessage,
|
lastLocalServiceMessageId,
|
||||||
}: {
|
}: {
|
||||||
limit: number;
|
limit: number;
|
||||||
offsetDate?: number;
|
offsetDate?: number;
|
||||||
archived?: boolean;
|
archived?: boolean;
|
||||||
withPinned?: boolean;
|
withPinned?: boolean;
|
||||||
lastLocalServiceMessage?: ApiMessage;
|
lastLocalServiceMessageId?: number;
|
||||||
}) {
|
}): Promise<ChatListData | undefined> {
|
||||||
const result = await invokeRequest(new GramJs.messages.GetDialogs({
|
const result = await invokeRequest(new GramJs.messages.GetDialogs({
|
||||||
offsetPeer: new GramJs.InputPeerEmpty(),
|
offsetPeer: new GramJs.InputPeerEmpty(),
|
||||||
limit,
|
limit,
|
||||||
offsetDate,
|
offsetDate,
|
||||||
folderId: archived ? ARCHIVED_FOLDER_ID : undefined,
|
|
||||||
...(withPinned && { excludePinned: true }),
|
...(withPinned && { excludePinned: true }),
|
||||||
}));
|
}));
|
||||||
const resultPinned = withPinned
|
const resultPinned = withPinned
|
||||||
@ -125,12 +137,10 @@ export async function fetchChats({
|
|||||||
}
|
}
|
||||||
updateLocalDb(result);
|
updateLocalDb(result);
|
||||||
|
|
||||||
const lastMessagesByChatId = buildCollectionByKey(
|
const messages = (resultPinned ? resultPinned.messages : [])
|
||||||
(resultPinned ? resultPinned.messages : []).concat(result.messages)
|
.concat(result.messages)
|
||||||
.map(buildApiMessage)
|
.map(buildApiMessage)
|
||||||
.filter(Boolean),
|
.filter(Boolean);
|
||||||
'chatId',
|
|
||||||
);
|
|
||||||
|
|
||||||
dispatchThreadInfoUpdates(result.messages);
|
dispatchThreadInfoUpdates(result.messages);
|
||||||
|
|
||||||
@ -145,6 +155,7 @@ export async function fetchChats({
|
|||||||
const dialogs = (resultPinned ? resultPinned.dialogs : []).concat(result.dialogs);
|
const dialogs = (resultPinned ? resultPinned.dialogs : []).concat(result.dialogs);
|
||||||
|
|
||||||
const orderedPinnedIds: string[] = [];
|
const orderedPinnedIds: string[] = [];
|
||||||
|
const lastMessageByChatId: Record<string, number> = {};
|
||||||
|
|
||||||
dialogs.forEach((dialog) => {
|
dialogs.forEach((dialog) => {
|
||||||
if (
|
if (
|
||||||
@ -158,6 +169,7 @@ export async function fetchChats({
|
|||||||
|
|
||||||
const peerEntity = peersByKey[getPeerKey(dialog.peer)];
|
const peerEntity = peersByKey[getPeerKey(dialog.peer)];
|
||||||
const chat = buildApiChatFromDialog(dialog, peerEntity);
|
const chat = buildApiChatFromDialog(dialog, peerEntity);
|
||||||
|
lastMessageByChatId[chat.id] = dialog.topMessage;
|
||||||
|
|
||||||
if (dialog.pts) {
|
if (dialog.pts) {
|
||||||
updateChannelState(chat.id, dialog.pts);
|
updateChannelState(chat.id, dialog.pts);
|
||||||
@ -165,12 +177,10 @@ export async function fetchChats({
|
|||||||
|
|
||||||
if (
|
if (
|
||||||
chat.id === SERVICE_NOTIFICATIONS_USER_ID
|
chat.id === SERVICE_NOTIFICATIONS_USER_ID
|
||||||
&& lastLocalServiceMessage
|
&& lastLocalServiceMessageId
|
||||||
&& (!lastMessagesByChatId[chat.id] || lastLocalServiceMessage.date > lastMessagesByChatId[chat.id].date)
|
&& (lastLocalServiceMessageId > dialog.topMessage)
|
||||||
) {
|
) {
|
||||||
chat.lastMessage = lastLocalServiceMessage;
|
lastMessageByChatId[chat.id] = lastLocalServiceMessageId;
|
||||||
} else {
|
|
||||||
chat.lastMessage = lastMessagesByChatId[chat.id];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
chat.isListed = true;
|
chat.isListed = true;
|
||||||
@ -210,6 +220,96 @@ export async function fetchChats({
|
|||||||
draftsById,
|
draftsById,
|
||||||
orderedPinnedIds: withPinned ? orderedPinnedIds : undefined,
|
orderedPinnedIds: withPinned ? orderedPinnedIds : undefined,
|
||||||
totalChatCount,
|
totalChatCount,
|
||||||
|
lastMessageByChatId,
|
||||||
|
messages,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchSavedChats({
|
||||||
|
limit,
|
||||||
|
offsetDate,
|
||||||
|
withPinned,
|
||||||
|
}: {
|
||||||
|
limit: number;
|
||||||
|
offsetDate?: number;
|
||||||
|
withPinned?: boolean;
|
||||||
|
}): Promise<ChatListData | undefined> {
|
||||||
|
const result = await invokeRequest(new GramJs.messages.GetSavedDialogs({
|
||||||
|
offsetPeer: new GramJs.InputPeerEmpty(),
|
||||||
|
limit,
|
||||||
|
offsetDate,
|
||||||
|
...(withPinned && { excludePinned: true }),
|
||||||
|
}));
|
||||||
|
const resultPinned = withPinned
|
||||||
|
? await invokeRequest(new GramJs.messages.GetPinnedSavedDialogs())
|
||||||
|
: undefined;
|
||||||
|
|
||||||
|
if (!result || result instanceof GramJs.messages.SavedDialogsNotModified) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasPinned = resultPinned && !(resultPinned instanceof GramJs.messages.SavedDialogsNotModified);
|
||||||
|
|
||||||
|
if (hasPinned) {
|
||||||
|
updateLocalDb(resultPinned);
|
||||||
|
}
|
||||||
|
updateLocalDb(result);
|
||||||
|
|
||||||
|
dispatchThreadInfoUpdates(result.messages);
|
||||||
|
|
||||||
|
const messages = (hasPinned ? resultPinned.messages : [])
|
||||||
|
.concat(result.messages)
|
||||||
|
.map(buildApiMessage)
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
const peersByKey = preparePeers(result);
|
||||||
|
if (hasPinned) {
|
||||||
|
Object.assign(peersByKey, preparePeers(resultPinned, peersByKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogs = (hasPinned ? resultPinned.dialogs : []).concat(result.dialogs);
|
||||||
|
|
||||||
|
const chatIds: string[] = [];
|
||||||
|
const orderedPinnedIds: string[] = [];
|
||||||
|
const lastMessageByChatId: Record<string, number> = {};
|
||||||
|
|
||||||
|
const chats: ApiChat[] = [];
|
||||||
|
|
||||||
|
dialogs.forEach((dialog) => {
|
||||||
|
const peerEntity = peersByKey[getPeerKey(dialog.peer)];
|
||||||
|
const chat = buildApiChatFromSavedDialog(dialog, peerEntity);
|
||||||
|
const chatId = getApiChatIdFromMtpPeer(dialog.peer);
|
||||||
|
|
||||||
|
chatIds.push(chatId);
|
||||||
|
if (withPinned && dialog.pinned) {
|
||||||
|
orderedPinnedIds.push(chatId);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastMessageByChatId[chatId] = dialog.topMessage;
|
||||||
|
|
||||||
|
chats.push(chat);
|
||||||
|
});
|
||||||
|
|
||||||
|
const { users, userStatusesById } = buildApiUsersAndStatuses((hasPinned ? resultPinned.users : [])
|
||||||
|
.concat(result.users));
|
||||||
|
|
||||||
|
let totalChatCount: number;
|
||||||
|
if (result instanceof GramJs.messages.SavedDialogsSlice) {
|
||||||
|
totalChatCount = result.count;
|
||||||
|
} else {
|
||||||
|
totalChatCount = chatIds.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
chatIds,
|
||||||
|
chats,
|
||||||
|
users,
|
||||||
|
userStatusesById,
|
||||||
|
orderedPinnedIds: withPinned ? orderedPinnedIds : undefined,
|
||||||
|
totalChatCount,
|
||||||
|
lastMessageByChatId,
|
||||||
|
messages,
|
||||||
|
draftsById: {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,10 +448,7 @@ export async function requestChatUpdate({
|
|||||||
? lastLocalMessage
|
? lastLocalMessage
|
||||||
: lastRemoteMessage;
|
: lastRemoteMessage;
|
||||||
|
|
||||||
const chatUpdate = {
|
const chatUpdate = buildApiChatFromDialog(dialog, peerEntity);
|
||||||
...buildApiChatFromDialog(dialog, peerEntity),
|
|
||||||
...(!noLastMessage && { lastMessage }),
|
|
||||||
};
|
|
||||||
|
|
||||||
onUpdate({
|
onUpdate({
|
||||||
'@type': 'updateChat',
|
'@type': 'updateChat',
|
||||||
@ -359,6 +456,14 @@ export async function requestChatUpdate({
|
|||||||
chat: chatUpdate,
|
chat: chatUpdate,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!noLastMessage && lastMessage) {
|
||||||
|
onUpdate({
|
||||||
|
'@type': 'updateChatLastMessage',
|
||||||
|
id,
|
||||||
|
lastMessage,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
applyState(result.state);
|
applyState(result.state);
|
||||||
|
|
||||||
scheduleMutedChatUpdate(chatUpdate.id, chatUpdate.muteUntil, onUpdate);
|
scheduleMutedChatUpdate(chatUpdate.id, chatUpdate.muteUntil, onUpdate);
|
||||||
@ -859,6 +964,30 @@ export async function toggleChatPinned({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function toggleSavedDialogPinned({
|
||||||
|
chat, shouldBePinned,
|
||||||
|
}: {
|
||||||
|
chat: ApiChat;
|
||||||
|
shouldBePinned: boolean;
|
||||||
|
}) {
|
||||||
|
const { id, accessHash } = chat;
|
||||||
|
|
||||||
|
const isActionSuccessful = await invokeRequest(new GramJs.messages.ToggleSavedDialogPin({
|
||||||
|
peer: new GramJs.InputDialogPeer({
|
||||||
|
peer: buildInputPeer(id, accessHash),
|
||||||
|
}),
|
||||||
|
pinned: shouldBePinned || undefined,
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (isActionSuccessful) {
|
||||||
|
onUpdate({
|
||||||
|
'@type': 'updateSavedDialogPinned',
|
||||||
|
id: chat.id,
|
||||||
|
isPinned: shouldBePinned,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function toggleChatArchived({
|
export function toggleChatArchived({
|
||||||
chat, folderId,
|
chat, folderId,
|
||||||
}: {
|
}: {
|
||||||
@ -1409,7 +1538,8 @@ export function toggleJoinRequest(chat: ApiChat, isEnabled: boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function preparePeers(
|
function preparePeers(
|
||||||
result: GramJs.messages.Dialogs | GramJs.messages.DialogsSlice | GramJs.messages.PeerDialogs,
|
result: GramJs.messages.Dialogs | GramJs.messages.DialogsSlice | GramJs.messages.PeerDialogs |
|
||||||
|
GramJs.messages.SavedDialogs | GramJs.messages.SavedDialogsSlice,
|
||||||
currentStore?: Record<string, GramJs.TypeChat | GramJs.TypeUser>,
|
currentStore?: Record<string, GramJs.TypeChat | GramJs.TypeUser>,
|
||||||
) {
|
) {
|
||||||
const store: Record<string, GramJs.TypeChat | GramJs.TypeUser> = {};
|
const store: Record<string, GramJs.TypeChat | GramJs.TypeUser> = {};
|
||||||
@ -1439,6 +1569,7 @@ function preparePeers(
|
|||||||
|
|
||||||
function updateLocalDb(result: (
|
function updateLocalDb(result: (
|
||||||
GramJs.messages.Dialogs | GramJs.messages.DialogsSlice | GramJs.messages.PeerDialogs |
|
GramJs.messages.Dialogs | GramJs.messages.DialogsSlice | GramJs.messages.PeerDialogs |
|
||||||
|
GramJs.messages.SavedDialogs | GramJs.messages.SavedDialogsSlice |
|
||||||
GramJs.messages.ChatFull | GramJs.contacts.Found |
|
GramJs.messages.ChatFull | GramJs.contacts.Found |
|
||||||
GramJs.contacts.ResolvedPeer | GramJs.channels.ChannelParticipants |
|
GramJs.contacts.ResolvedPeer | GramJs.channels.ChannelParticipants |
|
||||||
GramJs.messages.Chats | GramJs.messages.ChatsSlice | GramJs.TypeUpdates | GramJs.messages.ForumTopics
|
GramJs.messages.Chats | GramJs.messages.ChatsSlice | GramJs.TypeUpdates | GramJs.messages.ForumTopics
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import type { TwoFaParams } from '../../../lib/gramjs/client/2fa';
|
|||||||
import TelegramClient from '../../../lib/gramjs/client/TelegramClient';
|
import TelegramClient from '../../../lib/gramjs/client/TelegramClient';
|
||||||
import { Logger as GramJsLogger } from '../../../lib/gramjs/extensions/index';
|
import { Logger as GramJsLogger } from '../../../lib/gramjs/extensions/index';
|
||||||
|
|
||||||
|
import type { ThreadId } from '../../../types';
|
||||||
import type {
|
import type {
|
||||||
ApiInitialArgs,
|
ApiInitialArgs,
|
||||||
ApiMediaFormat,
|
ApiMediaFormat,
|
||||||
@ -222,7 +223,7 @@ type InvokeRequestParams = {
|
|||||||
dcId?: number;
|
dcId?: number;
|
||||||
shouldIgnoreErrors?: boolean;
|
shouldIgnoreErrors?: boolean;
|
||||||
abortControllerChatId?: string;
|
abortControllerChatId?: string;
|
||||||
abortControllerThreadId?: number;
|
abortControllerThreadId?: ThreadId;
|
||||||
abortControllerGroup?: 'call';
|
abortControllerGroup?: 'call';
|
||||||
shouldRetryOnTimeout?: boolean;
|
shouldRetryOnTimeout?: boolean;
|
||||||
};
|
};
|
||||||
@ -351,7 +352,7 @@ export function getTmpPassword(currentPassword: string, ttl?: number) {
|
|||||||
return client.getTmpPassword(currentPassword, ttl);
|
return client.getTmpPassword(currentPassword, ttl);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function abortChatRequests(params: { chatId: string; threadId?: number }) {
|
export function abortChatRequests(params: { chatId: string; threadId?: ThreadId }) {
|
||||||
const { chatId, threadId } = params;
|
const { chatId, threadId } = params;
|
||||||
const controller = CHAT_ABORT_CONTROLLERS.get(chatId);
|
const controller = CHAT_ABORT_CONTROLLERS.get(chatId);
|
||||||
if (!threadId) {
|
if (!threadId) {
|
||||||
|
|||||||
@ -24,7 +24,7 @@ export {
|
|||||||
editTopic, toggleForum, fetchTopicById, createTopic, toggleParticipantsHidden, checkChatlistInvite,
|
editTopic, toggleForum, fetchTopicById, createTopic, toggleParticipantsHidden, checkChatlistInvite,
|
||||||
joinChatlistInvite, createChalistInvite, editChatlistInvite, deleteChatlistInvite, fetchChatlistInvites,
|
joinChatlistInvite, createChalistInvite, editChatlistInvite, deleteChatlistInvite, fetchChatlistInvites,
|
||||||
fetchLeaveChatlistSuggestions, leaveChatlist, togglePeerTranslations, setViewForumAsMessages,
|
fetchLeaveChatlistSuggestions, leaveChatlist, togglePeerTranslations, setViewForumAsMessages,
|
||||||
fetchChannelRecommendations,
|
fetchChannelRecommendations, fetchSavedChats, toggleSavedDialogPinned,
|
||||||
} from './chats';
|
} from './chats';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Api as GramJs } from '../../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
|
|
||||||
|
import type { ThreadId } from '../../../types';
|
||||||
import type {
|
import type {
|
||||||
ApiAttachment,
|
ApiAttachment,
|
||||||
ApiChat,
|
ApiChat,
|
||||||
@ -106,21 +107,25 @@ export async function fetchMessages({
|
|||||||
chat,
|
chat,
|
||||||
threadId,
|
threadId,
|
||||||
offsetId,
|
offsetId,
|
||||||
|
isSavedDialog,
|
||||||
...pagination
|
...pagination
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
offsetId?: number;
|
offsetId?: number;
|
||||||
|
isSavedDialog?: boolean;
|
||||||
addOffset?: number;
|
addOffset?: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
}) {
|
}) {
|
||||||
const RequestClass = threadId === MAIN_THREAD_ID ? GramJs.messages.GetHistory : GramJs.messages.GetReplies;
|
const RequestClass = threadId === MAIN_THREAD_ID
|
||||||
|
? GramJs.messages.GetHistory : isSavedDialog
|
||||||
|
? GramJs.messages.GetSavedHistory : GramJs.messages.GetReplies;
|
||||||
let result;
|
let result;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
result = await invokeRequest(new RequestClass({
|
result = await invokeRequest(new RequestClass({
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
...(threadId !== MAIN_THREAD_ID && {
|
...(threadId !== MAIN_THREAD_ID && !isSavedDialog && {
|
||||||
msgId: Number(threadId),
|
msgId: Number(threadId),
|
||||||
}),
|
}),
|
||||||
...(offsetId && {
|
...(offsetId && {
|
||||||
@ -241,6 +246,7 @@ let mediaQueue = Promise.resolve();
|
|||||||
export function sendMessage(
|
export function sendMessage(
|
||||||
{
|
{
|
||||||
chat,
|
chat,
|
||||||
|
lastMessageId,
|
||||||
text,
|
text,
|
||||||
entities,
|
entities,
|
||||||
replyInfo,
|
replyInfo,
|
||||||
@ -281,6 +287,7 @@ export function sendMessage(
|
|||||||
) {
|
) {
|
||||||
const localMessage = buildLocalMessage(
|
const localMessage = buildLocalMessage(
|
||||||
chat,
|
chat,
|
||||||
|
lastMessageId,
|
||||||
text,
|
text,
|
||||||
entities,
|
entities,
|
||||||
replyInfo,
|
replyInfo,
|
||||||
@ -707,10 +714,10 @@ export async function pinMessage({
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function unpinAllMessages({ chat, threadId }: { chat: ApiChat; threadId?: number }) {
|
export async function unpinAllMessages({ chat, threadId }: { chat: ApiChat; threadId?: ThreadId }) {
|
||||||
await invokeRequest(new GramJs.messages.UnpinAllMessages({
|
await invokeRequest(new GramJs.messages.UnpinAllMessages({
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
...(threadId && { topMsgId: threadId }),
|
...(threadId && { topMsgId: Number(threadId) }),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -806,7 +813,7 @@ export async function reportMessages({
|
|||||||
export async function sendMessageAction({
|
export async function sendMessageAction({
|
||||||
peer, threadId, action,
|
peer, threadId, action,
|
||||||
}: {
|
}: {
|
||||||
peer: ApiPeer; threadId?: number; action: ApiSendMessageAction;
|
peer: ApiPeer; threadId?: ThreadId; action: ApiSendMessageAction;
|
||||||
}) {
|
}) {
|
||||||
const gramAction = buildSendMessageAction(action);
|
const gramAction = buildSendMessageAction(action);
|
||||||
if (!gramAction) {
|
if (!gramAction) {
|
||||||
@ -820,7 +827,7 @@ export async function sendMessageAction({
|
|||||||
try {
|
try {
|
||||||
const result = await invokeRequest(new GramJs.messages.SetTyping({
|
const result = await invokeRequest(new GramJs.messages.SetTyping({
|
||||||
peer: buildInputPeer(peer.id, peer.accessHash),
|
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||||
topMsgId: threadId,
|
topMsgId: Number(threadId),
|
||||||
action: gramAction,
|
action: gramAction,
|
||||||
}), {
|
}), {
|
||||||
shouldThrow: true,
|
shouldThrow: true,
|
||||||
@ -837,7 +844,7 @@ export async function sendMessageAction({
|
|||||||
export async function markMessageListRead({
|
export async function markMessageListRead({
|
||||||
chat, threadId, maxId = 0,
|
chat, threadId, maxId = 0,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat; threadId: number; maxId?: number;
|
chat: ApiChat; threadId: ThreadId; maxId?: number;
|
||||||
}) {
|
}) {
|
||||||
const isChannel = getEntityTypeById(chat.id) === 'channel';
|
const isChannel = getEntityTypeById(chat.id) === 'channel';
|
||||||
|
|
||||||
@ -851,7 +858,7 @@ export async function markMessageListRead({
|
|||||||
} else if (isChannel) {
|
} else if (isChannel) {
|
||||||
await invokeRequest(new GramJs.messages.ReadDiscussion({
|
await invokeRequest(new GramJs.messages.ReadDiscussion({
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
msgId: threadId,
|
msgId: Number(threadId),
|
||||||
readMaxId: fixedMaxId,
|
readMaxId: fixedMaxId,
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
@ -999,12 +1006,13 @@ export async function fetchDiscussionMessage({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function searchMessagesLocal({
|
export async function searchMessagesLocal({
|
||||||
chat, type, query, threadId, minDate, maxDate, ...pagination
|
chat, isSavedDialog, type, query, threadId, minDate, maxDate, ...pagination
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
|
isSavedDialog?: boolean;
|
||||||
type?: ApiMessageSearchType | ApiGlobalMessageSearchType;
|
type?: ApiMessageSearchType | ApiGlobalMessageSearchType;
|
||||||
query?: string;
|
query?: string;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
offsetId?: number;
|
offsetId?: number;
|
||||||
addOffset?: number;
|
addOffset?: number;
|
||||||
limit: number;
|
limit: number;
|
||||||
@ -1037,9 +1045,12 @@ export async function searchMessagesLocal({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const peer = buildInputPeer(chat.id, chat.accessHash);
|
||||||
|
|
||||||
const result = await invokeRequest(new GramJs.messages.Search({
|
const result = await invokeRequest(new GramJs.messages.Search({
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: isSavedDialog ? new GramJs.InputPeerSelf() : peer,
|
||||||
topMsgId: threadId === MAIN_THREAD_ID ? undefined : threadId,
|
savedPeerId: isSavedDialog ? peer : undefined,
|
||||||
|
topMsgId: threadId !== MAIN_THREAD_ID && !isSavedDialog ? Number(threadId) : undefined,
|
||||||
filter,
|
filter,
|
||||||
q: query || '',
|
q: query || '',
|
||||||
minDate,
|
minDate,
|
||||||
@ -1288,10 +1299,11 @@ export async function forwardMessages({
|
|||||||
noCaptions,
|
noCaptions,
|
||||||
isCurrentUserPremium,
|
isCurrentUserPremium,
|
||||||
wasDrafted,
|
wasDrafted,
|
||||||
|
lastMessageId,
|
||||||
}: {
|
}: {
|
||||||
fromChat: ApiChat;
|
fromChat: ApiChat;
|
||||||
toChat: ApiChat;
|
toChat: ApiChat;
|
||||||
toThreadId?: number;
|
toThreadId?: ThreadId;
|
||||||
messages: ApiMessage[];
|
messages: ApiMessage[];
|
||||||
isSilent?: boolean;
|
isSilent?: boolean;
|
||||||
scheduledAt?: number;
|
scheduledAt?: number;
|
||||||
@ -1301,6 +1313,7 @@ export async function forwardMessages({
|
|||||||
noCaptions?: boolean;
|
noCaptions?: boolean;
|
||||||
isCurrentUserPremium?: boolean;
|
isCurrentUserPremium?: boolean;
|
||||||
wasDrafted?: boolean;
|
wasDrafted?: boolean;
|
||||||
|
lastMessageId?: number;
|
||||||
}) {
|
}) {
|
||||||
const messageIds = messages.map(({ id }) => id);
|
const messageIds = messages.map(({ id }) => id);
|
||||||
const randomIds = messages.map(generateRandomBigInt);
|
const randomIds = messages.map(generateRandomBigInt);
|
||||||
@ -1309,12 +1322,13 @@ export async function forwardMessages({
|
|||||||
messages.forEach((message, index) => {
|
messages.forEach((message, index) => {
|
||||||
const localMessage = buildLocalForwardedMessage({
|
const localMessage = buildLocalForwardedMessage({
|
||||||
toChat,
|
toChat,
|
||||||
toThreadId,
|
toThreadId: Number(toThreadId),
|
||||||
message,
|
message,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
noAuthors,
|
noAuthors,
|
||||||
noCaptions,
|
noCaptions,
|
||||||
isCurrentUserPremium,
|
isCurrentUserPremium,
|
||||||
|
lastMessageId,
|
||||||
});
|
});
|
||||||
localMessages[randomIds[index].toString()] = localMessage;
|
localMessages[randomIds[index].toString()] = localMessage;
|
||||||
|
|
||||||
@ -1337,7 +1351,7 @@ export async function forwardMessages({
|
|||||||
silent: isSilent || undefined,
|
silent: isSilent || undefined,
|
||||||
dropAuthor: noAuthors || undefined,
|
dropAuthor: noAuthors || undefined,
|
||||||
dropMediaCaptions: noCaptions || undefined,
|
dropMediaCaptions: noCaptions || undefined,
|
||||||
...(toThreadId && { topMsgId: toThreadId }),
|
...(toThreadId && { topMsgId: Number(toThreadId) }),
|
||||||
...(scheduledAt && { scheduleDate: scheduledAt }),
|
...(scheduledAt && { scheduleDate: scheduledAt }),
|
||||||
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
||||||
}), {
|
}), {
|
||||||
@ -1434,14 +1448,14 @@ function updateLocalDb(result: (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchPinnedMessages({ chat, threadId }: { chat: ApiChat; threadId: number }) {
|
export async function fetchPinnedMessages({ chat, threadId }: { chat: ApiChat; threadId: ThreadId }) {
|
||||||
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),
|
||||||
filter: new GramJs.InputMessagesFilterPinned(),
|
filter: new GramJs.InputMessagesFilterPinned(),
|
||||||
q: '',
|
q: '',
|
||||||
limit: PINNED_MESSAGES_LIMIT,
|
limit: PINNED_MESSAGES_LIMIT,
|
||||||
topMsgId: threadId,
|
topMsgId: Number(threadId),
|
||||||
},
|
},
|
||||||
), {
|
), {
|
||||||
abortControllerChatId: chat.id,
|
abortControllerChatId: chat.id,
|
||||||
|
|||||||
@ -267,6 +267,8 @@ export async function fetchProfilePhotos(user?: ApiUser, chat?: ApiChat) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (chat?.isRestricted) return undefined;
|
||||||
|
|
||||||
const result = await searchMessagesLocal({
|
const result = await searchMessagesLocal({
|
||||||
chat: chat!,
|
chat: chat!,
|
||||||
type: 'profilePhoto',
|
type: 'profilePhoto',
|
||||||
|
|||||||
@ -597,6 +597,26 @@ export function updater(update: Update) {
|
|||||||
ids,
|
ids,
|
||||||
folderId: update.folderId || undefined,
|
folderId: update.folderId || undefined,
|
||||||
});
|
});
|
||||||
|
} else if (
|
||||||
|
update instanceof GramJs.UpdateSavedDialogPinned
|
||||||
|
&& update.peer instanceof GramJs.DialogPeer
|
||||||
|
) {
|
||||||
|
onUpdate({
|
||||||
|
'@type': 'updateSavedDialogPinned',
|
||||||
|
id: getApiChatIdFromMtpPeer(update.peer.peer),
|
||||||
|
isPinned: update.pinned || false,
|
||||||
|
});
|
||||||
|
} else if (update instanceof GramJs.UpdatePinnedSavedDialogs) {
|
||||||
|
const ids = update.order
|
||||||
|
? update.order
|
||||||
|
.filter((dp): dp is GramJs.DialogPeer => dp instanceof GramJs.DialogPeer)
|
||||||
|
.map((dp) => getApiChatIdFromMtpPeer(dp.peer))
|
||||||
|
: [];
|
||||||
|
|
||||||
|
onUpdate({
|
||||||
|
'@type': 'updatePinnedSavedDialogIds',
|
||||||
|
ids,
|
||||||
|
});
|
||||||
} else if (update instanceof GramJs.UpdateFolderPeers) {
|
} else if (update instanceof GramJs.UpdateFolderPeers) {
|
||||||
update.folderPeers.forEach((folderPeer) => {
|
update.folderPeers.forEach((folderPeer) => {
|
||||||
const { folderId, peer } = folderPeer;
|
const { folderId, peer } = folderPeer;
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
|
import type { ThreadId } from '../../types';
|
||||||
import type { ApiBotCommand } from './bots';
|
import type { ApiBotCommand } from './bots';
|
||||||
import type {
|
import type {
|
||||||
ApiChatReactions, ApiMessage, ApiPhoto, ApiStickerSet,
|
ApiChatReactions, ApiPhoto, ApiStickerSet,
|
||||||
} from './messages';
|
} from './messages';
|
||||||
import type { ApiChatInviteImporter } from './misc';
|
import type { ApiChatInviteImporter } from './misc';
|
||||||
import type {
|
import type {
|
||||||
@ -21,7 +22,6 @@ export interface ApiChat {
|
|||||||
type: ApiChatType;
|
type: ApiChatType;
|
||||||
title: string;
|
title: string;
|
||||||
hasUnreadMark?: boolean;
|
hasUnreadMark?: boolean;
|
||||||
lastMessage?: ApiMessage;
|
|
||||||
lastReadOutboxMessageId?: number;
|
lastReadOutboxMessageId?: number;
|
||||||
lastReadInboxMessageId?: number;
|
lastReadInboxMessageId?: number;
|
||||||
unreadCount?: number;
|
unreadCount?: number;
|
||||||
@ -48,7 +48,7 @@ export interface ApiChat {
|
|||||||
emojiStatus?: ApiEmojiStatus;
|
emojiStatus?: ApiEmojiStatus;
|
||||||
isForum?: boolean;
|
isForum?: boolean;
|
||||||
isForumAsMessages?: true;
|
isForumAsMessages?: true;
|
||||||
topics?: Record<number, ApiTopic>;
|
topics?: Record<ThreadId, ApiTopic>;
|
||||||
listedTopicIds?: number[];
|
listedTopicIds?: number[];
|
||||||
topicsCount?: number;
|
topicsCount?: number;
|
||||||
orderedPinnedTopicIds?: number[];
|
orderedPinnedTopicIds?: number[];
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import type { ThreadId } from '../../types';
|
||||||
import type { ApiWebDocument } from './bots';
|
import type { ApiWebDocument } from './bots';
|
||||||
import type { ApiGroupCall, PhoneCallAction } from './calls';
|
import type { ApiGroupCall, PhoneCallAction } from './calls';
|
||||||
import type { ApiChat } from './chats';
|
import type { ApiChat } from './chats';
|
||||||
@ -370,12 +371,14 @@ export type ApiInputReplyInfo = ApiInputMessageReplyInfo | ApiInputStoryReplyInf
|
|||||||
|
|
||||||
export interface ApiMessageForwardInfo {
|
export interface ApiMessageForwardInfo {
|
||||||
date: number;
|
date: number;
|
||||||
|
savedDate?: number;
|
||||||
isImported?: boolean;
|
isImported?: boolean;
|
||||||
isChannelPost: boolean;
|
isChannelPost: boolean;
|
||||||
channelPostId?: number;
|
channelPostId?: number;
|
||||||
isLinkedChannelPost?: boolean;
|
isLinkedChannelPost?: boolean;
|
||||||
fromChatId?: string;
|
fromChatId?: string;
|
||||||
senderUserId?: string;
|
fromId?: string;
|
||||||
|
savedFromPeerId?: string;
|
||||||
fromMessageId?: number;
|
fromMessageId?: number;
|
||||||
hiddenUserName?: string;
|
hiddenUserName?: string;
|
||||||
postAuthorTitle?: string;
|
postAuthorTitle?: string;
|
||||||
@ -595,14 +598,14 @@ interface ApiBaseThreadInfo {
|
|||||||
|
|
||||||
export interface ApiCommentsInfo extends ApiBaseThreadInfo {
|
export interface ApiCommentsInfo extends ApiBaseThreadInfo {
|
||||||
isCommentsInfo: true;
|
isCommentsInfo: true;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
originChannelId: string;
|
originChannelId: string;
|
||||||
originMessageId: number;
|
originMessageId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiMessageThreadInfo extends ApiBaseThreadInfo {
|
export interface ApiMessageThreadInfo extends ApiBaseThreadInfo {
|
||||||
isCommentsInfo: false;
|
isCommentsInfo: false;
|
||||||
threadId: number;
|
threadId: ThreadId;
|
||||||
// For linked messages in discussion
|
// For linked messages in discussion
|
||||||
fromChannelId?: string;
|
fromChannelId?: string;
|
||||||
fromMessageId?: number;
|
fromMessageId?: number;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import type {
|
|||||||
VideoRotation,
|
VideoRotation,
|
||||||
VideoState,
|
VideoState,
|
||||||
} from '../../lib/secret-sauce';
|
} from '../../lib/secret-sauce';
|
||||||
import type { ApiPrivacyKey, PrivacyVisibility } from '../../types';
|
import type { ApiPrivacyKey, PrivacyVisibility, ThreadId } from '../../types';
|
||||||
import type { ApiBotMenuButton } from './bots';
|
import type { ApiBotMenuButton } from './bots';
|
||||||
import type {
|
import type {
|
||||||
ApiGroupCall, ApiPhoneCall,
|
ApiGroupCall, ApiPhoneCall,
|
||||||
@ -102,6 +102,12 @@ export type ApiUpdateChat = {
|
|||||||
noTopChatsRequest?: boolean;
|
noTopChatsRequest?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiUpdateChatLastMessage = {
|
||||||
|
'@type': 'updateChatLastMessage';
|
||||||
|
id: string;
|
||||||
|
lastMessage: ApiMessage;
|
||||||
|
};
|
||||||
|
|
||||||
export type ApiUpdateChatJoin = {
|
export type ApiUpdateChatJoin = {
|
||||||
'@type': 'updateChatJoin';
|
'@type': 'updateChatJoin';
|
||||||
id: string;
|
id: string;
|
||||||
@ -126,7 +132,7 @@ export type ApiUpdateChatInbox = {
|
|||||||
export type ApiUpdateChatTypingStatus = {
|
export type ApiUpdateChatTypingStatus = {
|
||||||
'@type': 'updateChatTypingStatus';
|
'@type': 'updateChatTypingStatus';
|
||||||
id: string;
|
id: string;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
typingStatus: ApiTypingStatus | undefined;
|
typingStatus: ApiTypingStatus | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -170,6 +176,17 @@ export type ApiUpdateChatPinned = {
|
|||||||
isPinned: boolean;
|
isPinned: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiUpdatePinnedSavedDialogIds = {
|
||||||
|
'@type': 'updatePinnedSavedDialogIds';
|
||||||
|
ids: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ApiUpdateSavedDialogPinned = {
|
||||||
|
'@type': 'updateSavedDialogPinned';
|
||||||
|
id: string;
|
||||||
|
isPinned: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
export type ApiUpdateChatFolder = {
|
export type ApiUpdateChatFolder = {
|
||||||
'@type': 'updateChatFolder';
|
'@type': 'updateChatFolder';
|
||||||
id: number;
|
id: number;
|
||||||
@ -312,7 +329,7 @@ export type ApiUpdateResetMessages = {
|
|||||||
export type ApiUpdateDraftMessage = {
|
export type ApiUpdateDraftMessage = {
|
||||||
'@type': 'draftMessage';
|
'@type': 'draftMessage';
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
draft?: ApiDraft;
|
draft?: ApiDraft;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -713,7 +730,7 @@ export type ApiUpdate = (
|
|||||||
ApiUpdateRecentReactions | ApiUpdateStory | ApiUpdateReadStories | ApiUpdateDeleteStory | ApiUpdateSentStoryReaction |
|
ApiUpdateRecentReactions | ApiUpdateStory | ApiUpdateReadStories | ApiUpdateDeleteStory | ApiUpdateSentStoryReaction |
|
||||||
ApiRequestReconnectApi | ApiRequestSync | ApiUpdateFetchingDifference | ApiUpdateChannelMessages |
|
ApiRequestReconnectApi | ApiRequestSync | ApiUpdateFetchingDifference | ApiUpdateChannelMessages |
|
||||||
ApiUpdateStealthMode | ApiUpdateAttachMenuBots | ApiUpdateNewAuthorization | ApiUpdateGroupInvitePrivacyForbidden |
|
ApiUpdateStealthMode | ApiUpdateAttachMenuBots | ApiUpdateNewAuthorization | ApiUpdateGroupInvitePrivacyForbidden |
|
||||||
ApiUpdateViewForumAsMessages
|
ApiUpdateViewForumAsMessages | ApiUpdateSavedDialogPinned | ApiUpdatePinnedSavedDialogIds | ApiUpdateChatLastMessage
|
||||||
);
|
);
|
||||||
|
|
||||||
export type OnApiUpdate = (update: ApiUpdate) => void;
|
export type OnApiUpdate = (update: ApiUpdate) => void;
|
||||||
|
|||||||
1
src/assets/font-icons/author-hidden.svg
Normal file
1
src/assets/font-icons/author-hidden.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none"><path fill="#fff" fill-rule="evenodd" d="m27.42 15.209 2.462 3.17a.559.559 0 0 1-.252.871c-.847.292-1.635.669-2.366 1.13-.733.463-1.347.973-1.841 1.53a.563.563 0 0 0 .091.829l.811.576a.558.558 0 0 1-.152.987 35.953 35.953 0 0 0-6.33 2.636c-1.726.923-3.261 1.905-4.606 2.948a.536.536 0 0 1-.51.083.559.559 0 0 1-.335-.708l.071-.202a15.37 15.37 0 0 1 3.017-5.077c1.782-2.005 3.998-3.508 6.648-4.511v-3.139c0-.145.108-.266.25-.278a5.583 5.583 0 0 0 2.682-.907.269.269 0 0 1 .36.062zm-22.102-.103 2.006.626c.446 2.05 1.18 3.841 2.203 5.374.947 1.419 2.268 2.805 3.96 4.159.23.183.278.518.11.76l-.556.793a.539.539 0 0 1-.686.174L5.836 23.69a.563.563 0 0 1-.065-.957l.592-.411a.562.562 0 0 0-.02-.932l-4.085-2.58a.564.564 0 0 1-.101-.863l2.614-2.7a.538.538 0 0 1 .547-.14zm17.107.975c.17.292.265.617.265.96 0 1.272-1.309 2.303-2.923 2.303-1.615 0-2.924-1.031-2.924-2.303 0-.171.024-.338.069-.498a42.01 42.01 0 0 0 5.415-.447zm-12.858.02c1.673.253 3.543.41 5.525.444l.014.054c.035.143.054.29.054.442 0 1.272-1.31 2.303-2.924 2.303-1.614 0-2.923-1.031-2.923-2.303 0-.335.09-.653.254-.94zM19.395 2c1.547 0 2.543 2.451 2.99 7.354 2.903.587 4.804 1.565 4.804 2.673 0 1.796-4.997 3.252-11.161 3.252-6.165 0-11.162-1.456-11.162-3.252 0-1.108 1.903-2.087 4.809-2.674C10.215 4.45 11.218 2 12.685 2c2.237 0 1.38 2.822 3.268 2.822S17.038 2 19.395 2Z" clip-rule="evenodd" style="fill:#000;stroke-width:.805101"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
1
src/assets/font-icons/my-notes.svg
Normal file
1
src/assets/font-icons/my-notes.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="none"><path fill="#fff" fill-rule="evenodd" d="M2.316 10.572c-.303-2.153-.454-3.229-.15-4.11a3.882 3.882 0 0 1 1.443-1.916c.763-.534 1.84-.685 3.992-.988l8.84-1.242c2.152-.303 3.228-.454 4.109-.15a3.881 3.881 0 0 1 1.915 1.443c.515.735.674 1.76.955 3.755h-8.61c-1.04 0-1.9 0-2.6.057-.727.06-1.395.187-2.022.507a5.172 5.172 0 0 0-2.26 2.26c-.32.627-.447 1.295-.507 2.022-.057.7-.057 1.56-.057 2.601v9.033c0 .416 0 .803.004 1.162a3.185 3.185 0 0 1-.906-.16 3.881 3.881 0 0 1-1.916-1.443c-.534-.763-.685-1.84-.988-3.992Zm6.339 4.292c0-2.173 0-3.26.423-4.09a3.881 3.881 0 0 1 1.696-1.696c.83-.423 1.917-.423 4.09-.423h8.926c2.174 0 3.26 0 4.09.423.731.372 1.325.965 1.697 1.696.423.83.423 1.917.423 4.09v8.926c0 2.174 0 3.26-.423 4.091a3.881 3.881 0 0 1-1.696 1.696c-.83.423-1.917.423-4.09.423h-8.927c-2.173 0-3.26 0-4.09-.423a3.881 3.881 0 0 1-1.696-1.696c-.423-.83-.423-1.917-.423-4.09Zm4.851-.388a.97.97 0 0 1 .97-.97h9.703a.97.97 0 0 1 0 1.94h-9.703a.97.97 0 0 1-.97-.97zm0 4.851a.97.97 0 0 1 .97-.97h9.703a.97.97 0 0 1 0 1.94h-9.703a.97.97 0 0 1-.97-.97zm.97 3.881a.97.97 0 1 0 0 1.94h6.792a.97.97 0 0 0 0-1.94z" clip-rule="evenodd" style="fill:#000;stroke-width:.90556"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.2 KiB |
@ -1,6 +1,6 @@
|
|||||||
import type { MouseEvent as ReactMouseEvent } from 'react';
|
import type { MouseEvent as ReactMouseEvent } from 'react';
|
||||||
import type { FC, TeactNode } from '../../lib/teact/teact';
|
import type { FC, TeactNode } from '../../lib/teact/teact';
|
||||||
import React, { memo, useRef } from '../../lib/teact/teact';
|
import React, { memo, useMemo, useRef } from '../../lib/teact/teact';
|
||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -16,6 +16,7 @@ import {
|
|||||||
getChatTitle,
|
getChatTitle,
|
||||||
getPeerStoryHtmlId,
|
getPeerStoryHtmlId,
|
||||||
getUserFullName,
|
getUserFullName,
|
||||||
|
isAnonymousForwardsChat,
|
||||||
isChatWithRepliesBot,
|
isChatWithRepliesBot,
|
||||||
isDeletedUser,
|
isDeletedUser,
|
||||||
isUserId,
|
isUserId,
|
||||||
@ -33,6 +34,7 @@ import useMediaTransition from '../../hooks/useMediaTransition';
|
|||||||
|
|
||||||
import OptimizedVideo from '../ui/OptimizedVideo';
|
import OptimizedVideo from '../ui/OptimizedVideo';
|
||||||
import AvatarStoryCircle from './AvatarStoryCircle';
|
import AvatarStoryCircle from './AvatarStoryCircle';
|
||||||
|
import Icon from './Icon';
|
||||||
|
|
||||||
import './Avatar.scss';
|
import './Avatar.scss';
|
||||||
|
|
||||||
@ -51,6 +53,7 @@ type OwnProps = {
|
|||||||
photo?: ApiPhoto;
|
photo?: ApiPhoto;
|
||||||
text?: string;
|
text?: string;
|
||||||
isSavedMessages?: boolean;
|
isSavedMessages?: boolean;
|
||||||
|
isSavedDialog?: boolean;
|
||||||
withVideo?: boolean;
|
withVideo?: boolean;
|
||||||
withStory?: boolean;
|
withStory?: boolean;
|
||||||
forPremiumPromo?: boolean;
|
forPremiumPromo?: boolean;
|
||||||
@ -72,6 +75,7 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
photo,
|
photo,
|
||||||
text,
|
text,
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
|
isSavedDialog,
|
||||||
withVideo,
|
withVideo,
|
||||||
withStory,
|
withStory,
|
||||||
forPremiumPromo,
|
forPremiumPromo,
|
||||||
@ -94,6 +98,7 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
const chat = peer && isPeerChat ? peer as ApiChat : undefined;
|
const chat = peer && isPeerChat ? peer as ApiChat : undefined;
|
||||||
const isDeleted = user && isDeletedUser(user);
|
const isDeleted = user && isDeletedUser(user);
|
||||||
const isReplies = peer && isChatWithRepliesBot(peer.id);
|
const isReplies = peer && isChatWithRepliesBot(peer.id);
|
||||||
|
const isAnonymousForwards = peer && isAnonymousForwardsChat(peer.id);
|
||||||
const isForum = chat?.isForum;
|
const isForum = chat?.isForum;
|
||||||
let imageHash: string | undefined;
|
let imageHash: string | undefined;
|
||||||
let videoHash: string | undefined;
|
let videoHash: string | undefined;
|
||||||
@ -112,6 +117,26 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const specialIcon = useMemo(() => {
|
||||||
|
if (isSavedMessages) {
|
||||||
|
return isSavedDialog ? 'my-notes' : 'avatar-saved-messages';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDeleted) {
|
||||||
|
return 'avatar-deleted-account';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isReplies) {
|
||||||
|
return 'reply-filled';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAnonymousForwards) {
|
||||||
|
return 'author-hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}, [isAnonymousForwards, isDeleted, isSavedDialog, isReplies, isSavedMessages]);
|
||||||
|
|
||||||
const imgBlobUrl = useMedia(imageHash, false, ApiMediaFormat.BlobUrl);
|
const imgBlobUrl = useMedia(imageHash, false, ApiMediaFormat.BlobUrl);
|
||||||
const videoBlobUrl = useMedia(videoHash, !shouldLoadVideo, ApiMediaFormat.BlobUrl);
|
const videoBlobUrl = useMedia(videoHash, !shouldLoadVideo, ApiMediaFormat.BlobUrl);
|
||||||
const hasBlobUrl = Boolean(imgBlobUrl || videoBlobUrl);
|
const hasBlobUrl = Boolean(imgBlobUrl || videoBlobUrl);
|
||||||
@ -137,40 +162,13 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
let content: TeactNode | undefined;
|
let content: TeactNode | undefined;
|
||||||
const author = user ? getUserFullName(user) : (chat ? getChatTitle(lang, chat) : text);
|
const author = user ? getUserFullName(user) : (chat ? getChatTitle(lang, chat) : text);
|
||||||
|
|
||||||
if (isSavedMessages) {
|
if (specialIcon) {
|
||||||
content = (
|
content = (
|
||||||
<i
|
<Icon
|
||||||
className={buildClassName(
|
name={specialIcon}
|
||||||
cn.icon,
|
className={cn.icon}
|
||||||
'icon',
|
|
||||||
'icon-avatar-saved-messages',
|
|
||||||
)}
|
|
||||||
role="img"
|
role="img"
|
||||||
aria-label={author}
|
ariaLabel={author}
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if (isDeleted) {
|
|
||||||
content = (
|
|
||||||
<i
|
|
||||||
className={buildClassName(
|
|
||||||
cn.icon,
|
|
||||||
'icon',
|
|
||||||
'icon-avatar-deleted-account',
|
|
||||||
)}
|
|
||||||
role="img"
|
|
||||||
aria-label={author}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if (isReplies) {
|
|
||||||
content = (
|
|
||||||
<i
|
|
||||||
className={buildClassName(
|
|
||||||
cn.icon,
|
|
||||||
'icon',
|
|
||||||
'icon-reply-filled',
|
|
||||||
)}
|
|
||||||
role="img"
|
|
||||||
aria-label={author}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
} else if (hasBlobUrl) {
|
} else if (hasBlobUrl) {
|
||||||
@ -214,6 +212,7 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
className,
|
className,
|
||||||
getPeerColorClass(peer),
|
getPeerColorClass(peer),
|
||||||
isSavedMessages && 'saved-messages',
|
isSavedMessages && 'saved-messages',
|
||||||
|
isAnonymousForwards && 'anonymous-forwards',
|
||||||
isDeleted && 'deleted-account',
|
isDeleted && 'deleted-account',
|
||||||
isReplies && 'replies-bot-account',
|
isReplies && 'replies-bot-account',
|
||||||
isForum && 'forum',
|
isForum && 'forum',
|
||||||
|
|||||||
@ -43,6 +43,7 @@ import Switcher from '../ui/Switcher';
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
chatOrUserId: string;
|
chatOrUserId: string;
|
||||||
|
isSavedDialog?: boolean;
|
||||||
forceShowSelf?: boolean;
|
forceShowSelf?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -57,11 +58,13 @@ type StateProps =
|
|||||||
description?: string;
|
description?: string;
|
||||||
chatInviteLink?: string;
|
chatInviteLink?: string;
|
||||||
topicLink?: string;
|
topicLink?: string;
|
||||||
|
hasSavedMessages?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const runDebounced = debounce((cb) => cb(), 500, false);
|
const runDebounced = debounce((cb) => cb(), 500, false);
|
||||||
|
|
||||||
const ChatExtra: FC<OwnProps & StateProps> = ({
|
const ChatExtra: FC<OwnProps & StateProps> = ({
|
||||||
|
chatOrUserId,
|
||||||
user,
|
user,
|
||||||
chat,
|
chat,
|
||||||
forceShowSelf,
|
forceShowSelf,
|
||||||
@ -72,6 +75,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
description,
|
description,
|
||||||
chatInviteLink,
|
chatInviteLink,
|
||||||
topicLink,
|
topicLink,
|
||||||
|
hasSavedMessages,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
loadFullUser,
|
loadFullUser,
|
||||||
@ -79,6 +83,7 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
updateChatMutedState,
|
updateChatMutedState,
|
||||||
updateTopicMutedState,
|
updateTopicMutedState,
|
||||||
loadPeerStories,
|
loadPeerStories,
|
||||||
|
openSavedDialog,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -151,6 +156,10 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleOpenSavedDialog = useLastCallback(() => {
|
||||||
|
openSavedDialog({ chatId: chatOrUserId });
|
||||||
|
});
|
||||||
|
|
||||||
if (!chat || chat.isRestricted || (isSelf && !forceShowSelf)) {
|
if (!chat || chat.isRestricted || (isSelf && !forceShowSelf)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -264,12 +273,17 @@ const ChatExtra: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
)}
|
)}
|
||||||
|
{hasSavedMessages && (
|
||||||
|
<ListItem icon="saved-messages" ripple onClick={handleOpenSavedDialog}>
|
||||||
|
<span>{lang('SavedMessagesTab')}</span>
|
||||||
|
</ListItem>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { chatOrUserId }): StateProps => {
|
(global, { chatOrUserId, isSavedDialog }): StateProps => {
|
||||||
const { countryList: { phoneCodes: phoneCodeList } } = global;
|
const { countryList: { phoneCodes: phoneCodeList } } = global;
|
||||||
|
|
||||||
const chat = chatOrUserId ? selectChat(global, chatOrUserId) : undefined;
|
const chat = chatOrUserId ? selectChat(global, chatOrUserId) : undefined;
|
||||||
@ -277,7 +291,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const isForum = chat?.isForum;
|
const isForum = chat?.isForum;
|
||||||
const isMuted = chat && selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global));
|
const isMuted = chat && selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global));
|
||||||
const { threadId } = selectCurrentMessageList(global) || {};
|
const { threadId } = selectCurrentMessageList(global) || {};
|
||||||
const topicId = isForum ? threadId : undefined;
|
const topicId = isForum ? Number(threadId) : undefined;
|
||||||
const chatInviteLink = chat ? selectChatFullInfo(global, chat.id)?.inviteLink : undefined;
|
const chatInviteLink = chat ? selectChatFullInfo(global, chat.id)?.inviteLink : undefined;
|
||||||
let description = user ? selectUserFullInfo(global, user.id)?.bio : undefined;
|
let description = user ? selectUserFullInfo(global, user.id)?.bio : undefined;
|
||||||
if (!description && chat) {
|
if (!description && chat) {
|
||||||
@ -291,6 +305,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const topicLink = topicId ? selectTopicLink(global, chatOrUserId, topicId) : undefined;
|
const topicLink = topicId ? selectTopicLink(global, chatOrUserId, topicId) : undefined;
|
||||||
|
|
||||||
|
const hasSavedMessages = !isSavedDialog && global.chats.listIds.saved?.includes(chatOrUserId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
phoneCodeList,
|
phoneCodeList,
|
||||||
chat,
|
chat,
|
||||||
@ -301,6 +317,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
chatInviteLink,
|
chatInviteLink,
|
||||||
description,
|
description,
|
||||||
topicLink,
|
topicLink,
|
||||||
|
hasSavedMessages,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(ChatExtra));
|
)(ChatExtra));
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import React, {
|
|||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
import type { ApiChat, ApiTopic } from '../../api/types';
|
import type { ApiChat, ApiTopic } from '../../api/types';
|
||||||
|
import type { ThreadId } from '../../types';
|
||||||
|
|
||||||
import { CHAT_HEIGHT_PX } from '../../config';
|
import { CHAT_HEIGHT_PX } from '../../config';
|
||||||
import { getCanPostInChat, isUserId } from '../../global/helpers';
|
import { getCanPostInChat, isUserId } from '../../global/helpers';
|
||||||
@ -41,7 +42,7 @@ export type OwnProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
loadMore?: NoneToVoidFunction;
|
loadMore?: NoneToVoidFunction;
|
||||||
onSearchChange: (search: string) => void;
|
onSearchChange: (search: string) => void;
|
||||||
onSelectChatOrUser: (chatOrUserId: string, threadId?: number) => void;
|
onSelectChatOrUser: (chatOrUserId: string, threadId?: ThreadId) => void;
|
||||||
onClose: NoneToVoidFunction;
|
onClose: NoneToVoidFunction;
|
||||||
onCloseAnimationEnd?: NoneToVoidFunction;
|
onCloseAnimationEnd?: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -30,7 +30,9 @@ import type {
|
|||||||
ApiDraft, GlobalState, MessageList,
|
ApiDraft, GlobalState, MessageList,
|
||||||
MessageListType, TabState,
|
MessageListType, TabState,
|
||||||
} from '../../global/types';
|
} from '../../global/types';
|
||||||
import type { IAnchorPosition, InlineBotSettings, ISettings } from '../../types';
|
import type {
|
||||||
|
IAnchorPosition, InlineBotSettings, ISettings, ThreadId,
|
||||||
|
} from '../../types';
|
||||||
import { MAIN_THREAD_ID } from '../../api/types';
|
import { MAIN_THREAD_ID } from '../../api/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -164,7 +166,7 @@ type ComposerType = 'messageList' | 'story';
|
|||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
type: ComposerType;
|
type: ComposerType;
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId: number;
|
threadId: ThreadId;
|
||||||
storyId?: number;
|
storyId?: number;
|
||||||
messageListType: MessageListType;
|
messageListType: MessageListType;
|
||||||
dropAreaState?: string;
|
dropAreaState?: string;
|
||||||
|
|||||||
@ -1,12 +1,14 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import React, { memo } from '../../lib/teact/teact';
|
import React, { memo, useMemo } from '../../lib/teact/teact';
|
||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
import type { ApiChat, ApiPeer, ApiUser } from '../../api/types';
|
import type { ApiChat, ApiPeer, ApiUser } from '../../api/types';
|
||||||
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
||||||
|
|
||||||
import { EMOJI_STATUS_LOOP_LIMIT } from '../../config';
|
import { EMOJI_STATUS_LOOP_LIMIT } from '../../config';
|
||||||
import { getChatTitle, getUserFullName, isUserId } from '../../global/helpers';
|
import {
|
||||||
|
getChatTitle, getUserFullName, isAnonymousForwardsChat, isChatWithRepliesBot, isUserId,
|
||||||
|
} from '../../global/helpers';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { copyTextToClipboard } from '../../util/clipboard';
|
import { copyTextToClipboard } from '../../util/clipboard';
|
||||||
import stopEvent from '../../util/stopEvent';
|
import stopEvent from '../../util/stopEvent';
|
||||||
@ -30,6 +32,7 @@ type OwnProps = {
|
|||||||
withEmojiStatus?: boolean;
|
withEmojiStatus?: boolean;
|
||||||
emojiStatusSize?: number;
|
emojiStatusSize?: number;
|
||||||
isSavedMessages?: boolean;
|
isSavedMessages?: boolean;
|
||||||
|
isSavedDialog?: boolean;
|
||||||
noLoopLimit?: boolean;
|
noLoopLimit?: boolean;
|
||||||
canCopyTitle?: boolean;
|
canCopyTitle?: boolean;
|
||||||
onEmojiStatusClick?: NoneToVoidFunction;
|
onEmojiStatusClick?: NoneToVoidFunction;
|
||||||
@ -44,6 +47,7 @@ const FullNameTitle: FC<OwnProps> = ({
|
|||||||
withEmojiStatus,
|
withEmojiStatus,
|
||||||
emojiStatusSize,
|
emojiStatusSize,
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
|
isSavedDialog,
|
||||||
noLoopLimit,
|
noLoopLimit,
|
||||||
canCopyTitle,
|
canCopyTitle,
|
||||||
onEmojiStatusClick,
|
onEmojiStatusClick,
|
||||||
@ -65,10 +69,26 @@ const FullNameTitle: FC<OwnProps> = ({
|
|||||||
showNotification({ message: `${isUser ? 'User' : 'Chat'} name was copied` });
|
showNotification({ message: `${isUser ? 'User' : 'Chat'} name was copied` });
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isSavedMessages) {
|
const specialTitle = useMemo(() => {
|
||||||
|
if (isSavedMessages) {
|
||||||
|
return lang(isSavedDialog ? 'MyNotes' : 'SavedMessages');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAnonymousForwardsChat(peer.id)) {
|
||||||
|
return lang('AnonymousForward');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isChatWithRepliesBot(peer.id)) {
|
||||||
|
return lang('RepliesTitle');
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}, [isSavedDialog, isSavedMessages, lang, peer.id]);
|
||||||
|
|
||||||
|
if (specialTitle) {
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName('title', styles.root, className)}>
|
<div className={buildClassName('title', styles.root, className)}>
|
||||||
<h3>{lang('SavedMessages')}</h3>
|
<h3>{specialTitle}</h3>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,11 +3,11 @@ import React, { memo, useEffect, useMemo } from '../../lib/teact/teact';
|
|||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiChat, ApiThreadInfo, ApiTopic, ApiTypingStatus,
|
ApiChat, ApiThreadInfo, ApiTopic, ApiTypingStatus, ApiUser,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { LangFn } from '../../hooks/useLang';
|
import type { LangFn } from '../../hooks/useLang';
|
||||||
import type { IconName } from '../../types/icons';
|
import type { IconName } from '../../types/icons';
|
||||||
import { MediaViewerOrigin, type StoryViewerOrigin } from '../../types';
|
import { MediaViewerOrigin, type StoryViewerOrigin, type ThreadId } from '../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getChatTypeString,
|
getChatTypeString,
|
||||||
@ -20,6 +20,7 @@ import {
|
|||||||
selectChatOnlineCount,
|
selectChatOnlineCount,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
selectThreadMessagesCount,
|
selectThreadMessagesCount,
|
||||||
|
selectUser,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { REM } from './helpers/mediaDimensions';
|
import { REM } from './helpers/mediaDimensions';
|
||||||
@ -39,7 +40,7 @@ const TOPIC_ICON_SIZE = 2.5 * REM;
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
className?: string;
|
className?: string;
|
||||||
statusIcon?: IconName;
|
statusIcon?: IconName;
|
||||||
typingStatus?: ApiTypingStatus;
|
typingStatus?: ApiTypingStatus;
|
||||||
@ -58,6 +59,7 @@ type OwnProps = {
|
|||||||
noStatusOrTyping?: boolean;
|
noStatusOrTyping?: boolean;
|
||||||
withStory?: boolean;
|
withStory?: boolean;
|
||||||
storyViewerOrigin?: StoryViewerOrigin;
|
storyViewerOrigin?: StoryViewerOrigin;
|
||||||
|
isSavedDialog?: boolean;
|
||||||
onClick?: VoidFunction;
|
onClick?: VoidFunction;
|
||||||
onEmojiStatusClick?: NoneToVoidFunction;
|
onEmojiStatusClick?: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
@ -70,6 +72,7 @@ type StateProps =
|
|||||||
onlineCount?: number;
|
onlineCount?: number;
|
||||||
areMessagesLoaded: boolean;
|
areMessagesLoaded: boolean;
|
||||||
messagesCount?: number;
|
messagesCount?: number;
|
||||||
|
self?: ApiUser;
|
||||||
};
|
};
|
||||||
|
|
||||||
const GroupChatInfo: FC<OwnProps & StateProps> = ({
|
const GroupChatInfo: FC<OwnProps & StateProps> = ({
|
||||||
@ -97,6 +100,8 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
storyViewerOrigin,
|
storyViewerOrigin,
|
||||||
noEmojiStatus,
|
noEmojiStatus,
|
||||||
emojiStatusSize,
|
emojiStatusSize,
|
||||||
|
isSavedDialog,
|
||||||
|
self,
|
||||||
onClick,
|
onClick,
|
||||||
onEmojiStatusClick,
|
onEmojiStatusClick,
|
||||||
}) => {
|
}) => {
|
||||||
@ -199,15 +204,28 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
>
|
>
|
||||||
{!noAvatar && !isTopic && (
|
{!noAvatar && !isTopic && (
|
||||||
<Avatar
|
<>
|
||||||
key={chat.id}
|
{isSavedDialog && self && (
|
||||||
size={avatarSize}
|
<Avatar
|
||||||
peer={chat}
|
key="saved-messages"
|
||||||
withStory={withStory}
|
size={avatarSize}
|
||||||
storyViewerOrigin={storyViewerOrigin}
|
peer={self}
|
||||||
storyViewerMode="single-peer"
|
isSavedMessages
|
||||||
onClick={withMediaViewer ? handleAvatarViewerOpen : undefined}
|
className="saved-dialog-avatar"
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
|
<Avatar
|
||||||
|
key={chat.id}
|
||||||
|
className={buildClassName(isSavedDialog && 'overlay-avatar')}
|
||||||
|
size={avatarSize}
|
||||||
|
peer={chat}
|
||||||
|
withStory={withStory}
|
||||||
|
storyViewerOrigin={storyViewerOrigin}
|
||||||
|
storyViewerMode="single-peer"
|
||||||
|
isSavedDialog={isSavedDialog}
|
||||||
|
onClick={withMediaViewer ? handleAvatarViewerOpen : undefined}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
{isTopic && (
|
{isTopic && (
|
||||||
<TopicIcon
|
<TopicIcon
|
||||||
@ -224,6 +242,7 @@ const GroupChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
peer={chat}
|
peer={chat}
|
||||||
emojiStatusSize={emojiStatusSize}
|
emojiStatusSize={emojiStatusSize}
|
||||||
withEmojiStatus={!noEmojiStatus}
|
withEmojiStatus={!noEmojiStatus}
|
||||||
|
isSavedDialog={isSavedDialog}
|
||||||
onEmojiStatusClick={onEmojiStatusClick}
|
onEmojiStatusClick={onEmojiStatusClick}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@ -258,6 +277,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const areMessagesLoaded = Boolean(selectChatMessages(global, chatId));
|
const areMessagesLoaded = Boolean(selectChatMessages(global, chatId));
|
||||||
const topic = threadId ? chat?.topics?.[threadId] : undefined;
|
const topic = threadId ? chat?.topics?.[threadId] : undefined;
|
||||||
const messagesCount = topic && selectThreadMessagesCount(global, chatId, threadId!);
|
const messagesCount = topic && selectThreadMessagesCount(global, chatId, threadId!);
|
||||||
|
const self = selectUser(global, global.currentUserId!);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chat,
|
chat,
|
||||||
@ -266,6 +286,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
topic,
|
topic,
|
||||||
areMessagesLoaded,
|
areMessagesLoaded,
|
||||||
messagesCount,
|
messagesCount,
|
||||||
|
self,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(GroupChatInfo));
|
)(GroupChatInfo));
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import type { AriaRole } from 'react';
|
||||||
import React from '../../lib/teact/teact';
|
import React from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type { IconName } from '../../types/icons';
|
import type { IconName } from '../../types/icons';
|
||||||
@ -8,18 +9,24 @@ type OwnProps = {
|
|||||||
name: IconName;
|
name: IconName;
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: string;
|
style?: string;
|
||||||
|
role?: AriaRole;
|
||||||
|
ariaLabel?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Icon = ({
|
const Icon = ({
|
||||||
name,
|
name,
|
||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
|
role,
|
||||||
|
ariaLabel,
|
||||||
}: OwnProps) => {
|
}: OwnProps) => {
|
||||||
return (
|
return (
|
||||||
<i
|
<i
|
||||||
className={buildClassName(`icon icon-${name}`, className)}
|
className={buildClassName(`icon icon-${name}`, className)}
|
||||||
style={style}
|
style={style}
|
||||||
aria-hidden
|
aria-hidden={!ariaLabel}
|
||||||
|
aria-label={ariaLabel}
|
||||||
|
role={role}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -44,6 +44,7 @@ type OwnProps = {
|
|||||||
noStatusOrTyping?: boolean;
|
noStatusOrTyping?: boolean;
|
||||||
noRtl?: boolean;
|
noRtl?: boolean;
|
||||||
adminMember?: ApiChatMember;
|
adminMember?: ApiChatMember;
|
||||||
|
isSavedDialog?: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
onEmojiStatusClick?: NoneToVoidFunction;
|
onEmojiStatusClick?: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
@ -52,6 +53,7 @@ type StateProps =
|
|||||||
{
|
{
|
||||||
user?: ApiUser;
|
user?: ApiUser;
|
||||||
userStatus?: ApiUserStatus;
|
userStatus?: ApiUserStatus;
|
||||||
|
self?: ApiUser;
|
||||||
isSavedMessages?: boolean;
|
isSavedMessages?: boolean;
|
||||||
areMessagesLoaded: boolean;
|
areMessagesLoaded: boolean;
|
||||||
};
|
};
|
||||||
@ -73,7 +75,9 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
noRtl,
|
noRtl,
|
||||||
user,
|
user,
|
||||||
userStatus,
|
userStatus,
|
||||||
|
self,
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
|
isSavedDialog,
|
||||||
areMessagesLoaded,
|
areMessagesLoaded,
|
||||||
adminMember,
|
adminMember,
|
||||||
ripple,
|
ripple,
|
||||||
@ -166,6 +170,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
withEmojiStatus={!noEmojiStatus}
|
withEmojiStatus={!noEmojiStatus}
|
||||||
emojiStatusSize={emojiStatusSize}
|
emojiStatusSize={emojiStatusSize}
|
||||||
isSavedMessages={isSavedMessages}
|
isSavedMessages={isSavedMessages}
|
||||||
|
isSavedDialog={isSavedDialog}
|
||||||
onEmojiStatusClick={onEmojiStatusClick}
|
onEmojiStatusClick={onEmojiStatusClick}
|
||||||
/>
|
/>
|
||||||
{customTitle && <span className="custom-title">{customTitle}</span>}
|
{customTitle && <span className="custom-title">{customTitle}</span>}
|
||||||
@ -179,6 +184,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
withEmojiStatus={!noEmojiStatus}
|
withEmojiStatus={!noEmojiStatus}
|
||||||
emojiStatusSize={emojiStatusSize}
|
emojiStatusSize={emojiStatusSize}
|
||||||
isSavedMessages={isSavedMessages}
|
isSavedMessages={isSavedMessages}
|
||||||
|
isSavedDialog={isSavedDialog}
|
||||||
onEmojiStatusClick={onEmojiStatusClick}
|
onEmojiStatusClick={onEmojiStatusClick}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -186,11 +192,22 @@ const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName('ChatInfo', className)} dir={!noRtl && lang.isRtl ? 'rtl' : undefined}>
|
<div className={buildClassName('ChatInfo', className)} dir={!noRtl && lang.isRtl ? 'rtl' : undefined}>
|
||||||
|
{isSavedDialog && self && (
|
||||||
|
<Avatar
|
||||||
|
key="saved-messages"
|
||||||
|
size={avatarSize}
|
||||||
|
peer={self}
|
||||||
|
isSavedMessages
|
||||||
|
className="saved-dialog-avatar"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<Avatar
|
<Avatar
|
||||||
key={user.id}
|
key={user.id}
|
||||||
size={avatarSize}
|
size={avatarSize}
|
||||||
peer={user}
|
peer={user}
|
||||||
|
className={buildClassName(isSavedDialog && 'overlay-avatar')}
|
||||||
isSavedMessages={isSavedMessages}
|
isSavedMessages={isSavedMessages}
|
||||||
|
isSavedDialog={isSavedDialog}
|
||||||
withStory={withStory}
|
withStory={withStory}
|
||||||
storyViewerOrigin={storyViewerOrigin}
|
storyViewerOrigin={storyViewerOrigin}
|
||||||
storyViewerMode="single-peer"
|
storyViewerMode="single-peer"
|
||||||
@ -210,6 +227,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const user = selectUser(global, userId);
|
const user = selectUser(global, userId);
|
||||||
const userStatus = selectUserStatus(global, userId);
|
const userStatus = selectUserStatus(global, userId);
|
||||||
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
||||||
|
const self = isSavedMessages ? user : selectUser(global, global.currentUserId!);
|
||||||
const areMessagesLoaded = Boolean(selectChatMessages(global, userId));
|
const areMessagesLoaded = Boolean(selectChatMessages(global, userId));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -217,6 +235,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
userStatus,
|
userStatus,
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
areMessagesLoaded,
|
areMessagesLoaded,
|
||||||
|
self,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(PrivateChatInfo));
|
)(PrivateChatInfo));
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import type { GlobalState } from '../../global/types';
|
|||||||
import { MediaViewerOrigin } from '../../types';
|
import { MediaViewerOrigin } from '../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getUserStatus, isChatChannel, isUserId, isUserOnline,
|
getUserStatus, isAnonymousForwardsChat, isChatChannel, isUserId, isUserOnline,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import {
|
import {
|
||||||
selectChat,
|
selectChat,
|
||||||
@ -53,7 +53,6 @@ type StateProps =
|
|||||||
user?: ApiUser;
|
user?: ApiUser;
|
||||||
userStatus?: ApiUserStatus;
|
userStatus?: ApiUserStatus;
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
isSavedMessages?: boolean;
|
|
||||||
mediaId?: number;
|
mediaId?: number;
|
||||||
avatarOwnerId?: string;
|
avatarOwnerId?: string;
|
||||||
topic?: ApiTopic;
|
topic?: ApiTopic;
|
||||||
@ -75,7 +74,6 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
|||||||
user,
|
user,
|
||||||
userStatus,
|
userStatus,
|
||||||
chat,
|
chat,
|
||||||
isSavedMessages,
|
|
||||||
connectionState,
|
connectionState,
|
||||||
mediaId,
|
mediaId,
|
||||||
avatarOwnerId,
|
avatarOwnerId,
|
||||||
@ -107,8 +105,8 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
|||||||
const slideAnimation = hasSlideAnimation ? (lang.isRtl ? 'slideRtl' : 'slide') : 'none';
|
const slideAnimation = hasSlideAnimation ? (lang.isRtl ? 'slideRtl' : 'slide') : 'none';
|
||||||
|
|
||||||
const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0);
|
const [currentPhotoIndex, setCurrentPhotoIndex] = useState(0);
|
||||||
const isFirst = isSavedMessages || photos.length <= 1 || currentPhotoIndex === 0;
|
const isFirst = photos.length <= 1 || currentPhotoIndex === 0;
|
||||||
const isLast = isSavedMessages || photos.length <= 1 || currentPhotoIndex === photos.length - 1;
|
const isLast = photos.length <= 1 || currentPhotoIndex === photos.length - 1;
|
||||||
|
|
||||||
// Set the current avatar photo to the last selected photo in Media Viewer after it is closed
|
// Set the current avatar photo to the last selected photo in Media Viewer after it is closed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -227,7 +225,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderPhotoTabs() {
|
function renderPhotoTabs() {
|
||||||
if (isSavedMessages || !photos || photos.length <= 1) {
|
if (!photos || photos.length <= 1) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,7 +239,7 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderPhoto(isActive?: boolean) {
|
function renderPhoto(isActive?: boolean) {
|
||||||
const photo = !isSavedMessages && photos.length > 0
|
const photo = photos.length > 0
|
||||||
? photos[currentPhotoIndex]
|
? photos[currentPhotoIndex]
|
||||||
: undefined;
|
: undefined;
|
||||||
const profilePhoto = photo || userPersonalPhoto || userProfilePhoto || chatProfilePhoto || userFallbackPhoto;
|
const profilePhoto = photo || userPersonalPhoto || userProfilePhoto || chatProfilePhoto || userFallbackPhoto;
|
||||||
@ -252,7 +250,6 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
|||||||
user={user}
|
user={user}
|
||||||
chat={chat}
|
chat={chat}
|
||||||
photo={profilePhoto}
|
photo={profilePhoto}
|
||||||
isSavedMessages={isSavedMessages}
|
|
||||||
canPlayVideo={Boolean(isActive && canPlayVideo)}
|
canPlayVideo={Boolean(isActive && canPlayVideo)}
|
||||||
onClick={handleProfilePhotoClick}
|
onClick={handleProfilePhotoClick}
|
||||||
/>
|
/>
|
||||||
@ -260,6 +257,11 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderStatus() {
|
function renderStatus() {
|
||||||
|
const peerId = (chatId || userId)!;
|
||||||
|
|
||||||
|
const isAnonymousForwards = isAnonymousForwardsChat(peerId);
|
||||||
|
if (isAnonymousForwards) return undefined;
|
||||||
|
|
||||||
if (user) {
|
if (user) {
|
||||||
return (
|
return (
|
||||||
<div className={buildClassName(styles.status, 'status', isUserOnline(user, userStatus) && 'online')}>
|
<div className={buildClassName(styles.status, 'status', isUserOnline(user, userStatus) && 'online')}>
|
||||||
@ -348,26 +350,24 @@ const ProfileInfo: FC<OwnProps & StateProps> = ({
|
|||||||
peer={(user || chat)!}
|
peer={(user || chat)!}
|
||||||
withEmojiStatus
|
withEmojiStatus
|
||||||
emojiStatusSize={EMOJI_STATUS_SIZE}
|
emojiStatusSize={EMOJI_STATUS_SIZE}
|
||||||
isSavedMessages={isSavedMessages}
|
|
||||||
onEmojiStatusClick={handleStatusClick}
|
onEmojiStatusClick={handleStatusClick}
|
||||||
noLoopLimit
|
noLoopLimit
|
||||||
canCopyTitle
|
canCopyTitle
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!isSavedMessages && renderStatus()}
|
{renderStatus()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { userId, forceShowSelf }): StateProps => {
|
(global, { userId }): StateProps => {
|
||||||
const { connectionState } = global;
|
const { connectionState } = global;
|
||||||
const user = selectUser(global, userId);
|
const user = selectUser(global, userId);
|
||||||
const isPrivate = isUserId(userId);
|
const isPrivate = isUserId(userId);
|
||||||
const userStatus = selectUserStatus(global, userId);
|
const userStatus = selectUserStatus(global, userId);
|
||||||
const chat = selectChat(global, userId);
|
const chat = selectChat(global, userId);
|
||||||
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
|
||||||
const { mediaId, avatarOwnerId } = selectTabState(global).mediaViewer;
|
const { mediaId, avatarOwnerId } = selectTabState(global).mediaViewer;
|
||||||
const isForum = chat?.isForum;
|
const isForum = chat?.isForum;
|
||||||
const { threadId: currentTopicId } = selectCurrentMessageList(global) || {};
|
const { threadId: currentTopicId } = selectCurrentMessageList(global) || {};
|
||||||
@ -387,7 +387,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
userProfilePhoto: userFullInfo?.profilePhoto,
|
userProfilePhoto: userFullInfo?.profilePhoto,
|
||||||
userFallbackPhoto: userFullInfo?.fallbackPhoto,
|
userFallbackPhoto: userFullInfo?.fallbackPhoto,
|
||||||
chatProfilePhoto: chatFullInfo?.profilePhoto,
|
chatProfilePhoto: chatFullInfo?.profilePhoto,
|
||||||
isSavedMessages,
|
|
||||||
mediaId,
|
mediaId,
|
||||||
avatarOwnerId,
|
avatarOwnerId,
|
||||||
emojiStatusSticker,
|
emojiStatusSticker,
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
import type { FC, TeactNode } from '../../lib/teact/teact';
|
import type { FC, TeactNode } from '../../lib/teact/teact';
|
||||||
import React, { memo, useEffect, useRef } from '../../lib/teact/teact';
|
import React, {
|
||||||
|
memo, useEffect, useMemo, useRef,
|
||||||
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type { ApiChat, ApiPhoto, ApiUser } from '../../api/types';
|
import type { ApiChat, ApiPhoto, ApiUser } from '../../api/types';
|
||||||
|
|
||||||
@ -8,6 +10,7 @@ import {
|
|||||||
getChatTitle,
|
getChatTitle,
|
||||||
getUserFullName,
|
getUserFullName,
|
||||||
getVideoAvatarMediaHash,
|
getVideoAvatarMediaHash,
|
||||||
|
isAnonymousForwardsChat,
|
||||||
isChatWithRepliesBot,
|
isChatWithRepliesBot,
|
||||||
isDeletedUser,
|
isDeletedUser,
|
||||||
isUserId,
|
isUserId,
|
||||||
@ -27,6 +30,7 @@ import useMediaTransition from '../../hooks/useMediaTransition';
|
|||||||
|
|
||||||
import OptimizedVideo from '../ui/OptimizedVideo';
|
import OptimizedVideo from '../ui/OptimizedVideo';
|
||||||
import Spinner from '../ui/Spinner';
|
import Spinner from '../ui/Spinner';
|
||||||
|
import Icon from './Icon';
|
||||||
|
|
||||||
import './ProfilePhoto.scss';
|
import './ProfilePhoto.scss';
|
||||||
|
|
||||||
@ -34,6 +38,7 @@ type OwnProps = {
|
|||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
user?: ApiUser;
|
user?: ApiUser;
|
||||||
isSavedMessages?: boolean;
|
isSavedMessages?: boolean;
|
||||||
|
isSavedDialog?: boolean;
|
||||||
photo?: ApiPhoto;
|
photo?: ApiPhoto;
|
||||||
canPlayVideo: boolean;
|
canPlayVideo: boolean;
|
||||||
onClick: NoneToVoidFunction;
|
onClick: NoneToVoidFunction;
|
||||||
@ -44,6 +49,7 @@ const ProfilePhoto: FC<OwnProps> = ({
|
|||||||
user,
|
user,
|
||||||
photo,
|
photo,
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
|
isSavedDialog,
|
||||||
canPlayVideo,
|
canPlayVideo,
|
||||||
onClick,
|
onClick,
|
||||||
}) => {
|
}) => {
|
||||||
@ -55,8 +61,9 @@ const ProfilePhoto: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const isDeleted = user && isDeletedUser(user);
|
const isDeleted = user && isDeletedUser(user);
|
||||||
const isRepliesChat = chat && isChatWithRepliesBot(chat.id);
|
const isRepliesChat = chat && isChatWithRepliesBot(chat.id);
|
||||||
|
const isAnonymousForwards = chat && isAnonymousForwardsChat(chat.id);
|
||||||
const peer = user || chat;
|
const peer = user || chat;
|
||||||
const canHaveMedia = peer && !isSavedMessages && !isDeleted && !isRepliesChat;
|
const canHaveMedia = peer && !isSavedMessages && !isDeleted && !isRepliesChat && !isAnonymousForwards;
|
||||||
const { isVideo } = photo || {};
|
const { isVideo } = photo || {};
|
||||||
|
|
||||||
const avatarHash = canHaveMedia && getChatAvatarHash(peer, 'normal');
|
const avatarHash = canHaveMedia && getChatAvatarHash(peer, 'normal');
|
||||||
@ -84,14 +91,30 @@ const ProfilePhoto: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
}, [canPlayVideo]);
|
}, [canPlayVideo]);
|
||||||
|
|
||||||
|
const specialIcon = useMemo(() => {
|
||||||
|
if (isSavedMessages) {
|
||||||
|
return isSavedDialog ? 'my-notes' : 'avatar-saved-messages';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDeleted) {
|
||||||
|
return 'avatar-deleted-account';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRepliesChat) {
|
||||||
|
return 'reply-filled';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAnonymousForwards) {
|
||||||
|
return 'author-hidden';
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}, [isAnonymousForwards, isDeleted, isSavedDialog, isRepliesChat, isSavedMessages]);
|
||||||
|
|
||||||
let content: TeactNode | undefined;
|
let content: TeactNode | undefined;
|
||||||
|
|
||||||
if (isSavedMessages) {
|
if (specialIcon) {
|
||||||
content = <i className="icon icon-avatar-saved-messages" />;
|
content = <Icon name={specialIcon} role="img" />;
|
||||||
} else if (isDeleted) {
|
|
||||||
content = <i className="icon icon-avatar-deleted-account" />;
|
|
||||||
} else if (isRepliesChat) {
|
|
||||||
content = <i className="icon icon-reply-filled" />;
|
|
||||||
} else if (hasMedia) {
|
} else if (hasMedia) {
|
||||||
content = (
|
content = (
|
||||||
<>
|
<>
|
||||||
@ -141,6 +164,7 @@ const ProfilePhoto: FC<OwnProps> = ({
|
|||||||
'ProfilePhoto',
|
'ProfilePhoto',
|
||||||
getPeerColorClass(peer),
|
getPeerColorClass(peer),
|
||||||
isSavedMessages && 'saved-messages',
|
isSavedMessages && 'saved-messages',
|
||||||
|
isAnonymousForwards && 'anonymous-forwards',
|
||||||
isDeleted && 'deleted-account',
|
isDeleted && 'deleted-account',
|
||||||
isRepliesChat && 'replies-bot-account',
|
isRepliesChat && 'replies-bot-account',
|
||||||
(!isSavedMessages && !hasMedia) && 'no-photo',
|
(!isSavedMessages && !hasMedia) && 'no-photo',
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import React, { memo, useMemo, useState } from '../../lib/teact/teact';
|
|||||||
import { getGlobal, withGlobal } from '../../global';
|
import { getGlobal, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { ApiChat, ApiChatType } from '../../api/types';
|
import type { ApiChat, ApiChatType } from '../../api/types';
|
||||||
|
import type { ThreadId } from '../../types';
|
||||||
import { MAIN_THREAD_ID } from '../../api/types';
|
import { MAIN_THREAD_ID } from '../../api/types';
|
||||||
|
|
||||||
import { API_CHAT_TYPES } from '../../config';
|
import { API_CHAT_TYPES } from '../../config';
|
||||||
@ -11,10 +12,10 @@ import {
|
|||||||
filterUsersByName,
|
filterUsersByName,
|
||||||
getCanPostInChat,
|
getCanPostInChat,
|
||||||
isDeletedUser,
|
isDeletedUser,
|
||||||
sortChatIds,
|
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import { filterChatIdsByType } from '../../global/selectors';
|
import { filterChatIdsByType } from '../../global/selectors';
|
||||||
import { unique } from '../../util/iteratees';
|
import { unique } from '../../util/iteratees';
|
||||||
|
import sortChatIds from './helpers/sortChatIds';
|
||||||
|
|
||||||
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
@ -27,7 +28,7 @@ export type OwnProps = {
|
|||||||
className?: string;
|
className?: string;
|
||||||
filter?: ApiChatType[];
|
filter?: ApiChatType[];
|
||||||
loadMore?: NoneToVoidFunction;
|
loadMore?: NoneToVoidFunction;
|
||||||
onSelectRecipient: (peerId: string, threadId?: number) => void;
|
onSelectRecipient: (peerId: string, threadId?: ThreadId) => void;
|
||||||
onClose: NoneToVoidFunction;
|
onClose: NoneToVoidFunction;
|
||||||
onCloseAnimationEnd?: NoneToVoidFunction;
|
onCloseAnimationEnd?: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
@ -85,7 +86,7 @@ const RecipientPicker: FC<OwnProps & StateProps> = ({
|
|||||||
const sorted = sortChatIds(unique([
|
const sorted = sortChatIds(unique([
|
||||||
...filterChatsByName(lang, chatIds, chatsById, search, currentUserId),
|
...filterChatsByName(lang, chatIds, chatsById, search, currentUserId),
|
||||||
...(contactIds && filter.includes('users') ? filterUsersByName(contactIds, usersById, search) : []),
|
...(contactIds && filter.includes('users') ? filterUsersByName(contactIds, usersById, search) : []),
|
||||||
]), chatsById, undefined, priorityIds);
|
]), undefined, priorityIds);
|
||||||
|
|
||||||
return filterChatIdsByType(global, sorted, filter);
|
return filterChatIdsByType(global, sorted, filter);
|
||||||
}, [pinnedIds, currentUserId, activeListIds, search, archivedListIds, lang, chatsById, contactIds, filter, isOpen]);
|
}, [pinnedIds, currentUserId, activeListIds, search, archivedListIds, lang, chatsById, contactIds, filter, isOpen]);
|
||||||
|
|||||||
39
src/components/common/helpers/sortChatIds.ts
Normal file
39
src/components/common/helpers/sortChatIds.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { getGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import { selectChat, selectChatLastMessage } from '../../../global/selectors';
|
||||||
|
import { orderBy } from '../../../util/iteratees';
|
||||||
|
|
||||||
|
const VERIFIED_PRIORITY_BASE = 3e9;
|
||||||
|
const PINNED_PRIORITY_BASE = 3e8;
|
||||||
|
|
||||||
|
export default function sortChatIds(
|
||||||
|
chatIds: string[],
|
||||||
|
shouldPrioritizeVerified = false,
|
||||||
|
priorityIds?: string[],
|
||||||
|
) {
|
||||||
|
// Avoid calling sort on every global change
|
||||||
|
const global = getGlobal();
|
||||||
|
return orderBy(chatIds, (id) => {
|
||||||
|
const chat = selectChat(global, id);
|
||||||
|
if (!chat) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let priority = 0;
|
||||||
|
|
||||||
|
const lastMessage = selectChatLastMessage(global, id);
|
||||||
|
if (lastMessage) {
|
||||||
|
priority += lastMessage.date;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shouldPrioritizeVerified && chat.isVerified) {
|
||||||
|
priority += VERIFIED_PRIORITY_BASE; // ~100 years in seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
if (priorityIds && priorityIds.includes(id)) {
|
||||||
|
priority = Date.now() + PINNED_PRIORITY_BASE + (priorityIds.length - priorityIds.indexOf(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
return priority;
|
||||||
|
}, 'desc');
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, { memo, useEffect } from '../../../lib/teact/teact';
|
import React, { memo, useEffect, useMemo } from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -29,6 +29,8 @@ import { getMessageReplyInfo } from '../../../global/helpers/replies';
|
|||||||
import {
|
import {
|
||||||
selectCanAnimateInterface,
|
selectCanAnimateInterface,
|
||||||
selectChat,
|
selectChat,
|
||||||
|
selectChatLastMessage,
|
||||||
|
selectChatLastMessageId,
|
||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectDraft,
|
selectDraft,
|
||||||
@ -37,6 +39,7 @@ import {
|
|||||||
selectNotifyExceptions,
|
selectNotifyExceptions,
|
||||||
selectNotifySettings,
|
selectNotifySettings,
|
||||||
selectOutgoingStatus,
|
selectOutgoingStatus,
|
||||||
|
selectPeer,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
selectThreadParam,
|
selectThreadParam,
|
||||||
selectTopicFromMessage,
|
selectTopicFromMessage,
|
||||||
@ -49,6 +52,7 @@ import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../util/windowEnvironment';
|
|||||||
|
|
||||||
import useAppLayout from '../../../hooks/useAppLayout';
|
import useAppLayout from '../../../hooks/useAppLayout';
|
||||||
import useChatContextActions from '../../../hooks/useChatContextActions';
|
import useChatContextActions from '../../../hooks/useChatContextActions';
|
||||||
|
import useEnsureMessage from '../../../hooks/useEnsureMessage';
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
|
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
@ -76,6 +80,7 @@ type OwnProps = {
|
|||||||
animationType: ChatAnimationTypes;
|
animationType: ChatAnimationTypes;
|
||||||
isPinned?: boolean;
|
isPinned?: boolean;
|
||||||
offsetTop: number;
|
offsetTop: number;
|
||||||
|
isSavedDialog?: boolean;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
onDragEnter?: (chatId: string) => void;
|
onDragEnter?: (chatId: string) => void;
|
||||||
};
|
};
|
||||||
@ -99,6 +104,9 @@ type StateProps = {
|
|||||||
lastMessageTopic?: ApiTopic;
|
lastMessageTopic?: ApiTopic;
|
||||||
typingStatus?: ApiTypingStatus;
|
typingStatus?: ApiTypingStatus;
|
||||||
withInterfaceAnimations?: boolean;
|
withInterfaceAnimations?: boolean;
|
||||||
|
lastMessageId?: number;
|
||||||
|
lastMessage?: ApiMessage;
|
||||||
|
currentUserId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Chat: FC<OwnProps & StateProps> = ({
|
const Chat: FC<OwnProps & StateProps> = ({
|
||||||
@ -127,10 +135,16 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
canChangeFolder,
|
canChangeFolder,
|
||||||
lastMessageTopic,
|
lastMessageTopic,
|
||||||
typingStatus,
|
typingStatus,
|
||||||
|
lastMessageId,
|
||||||
|
lastMessage,
|
||||||
|
isSavedDialog,
|
||||||
|
currentUserId,
|
||||||
onDragEnter,
|
onDragEnter,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
openChat,
|
openChat,
|
||||||
|
openSavedDialog,
|
||||||
|
toggleChatInfo,
|
||||||
focusLastMessage,
|
focusLastMessage,
|
||||||
loadTopics,
|
loadTopics,
|
||||||
openForumPanel,
|
openForumPanel,
|
||||||
@ -147,7 +161,9 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
const [shouldRenderChatFolderModal, markRenderChatFolderModal, unmarkRenderChatFolderModal] = useFlag();
|
const [shouldRenderChatFolderModal, markRenderChatFolderModal, unmarkRenderChatFolderModal] = useFlag();
|
||||||
const [shouldRenderReportModal, markRenderReportModal, unmarkRenderReportModal] = useFlag();
|
const [shouldRenderReportModal, markRenderReportModal, unmarkRenderReportModal] = useFlag();
|
||||||
|
|
||||||
const { lastMessage, isForum, isForumAsMessages } = chat || {};
|
const { isForum, isForumAsMessages } = chat || {};
|
||||||
|
|
||||||
|
useEnsureMessage(isSavedDialog ? currentUserId : chatId, lastMessageId, lastMessage);
|
||||||
|
|
||||||
const { renderSubtitle, ref } = useChatListEntry({
|
const { renderSubtitle, ref } = useChatListEntry({
|
||||||
chat,
|
chat,
|
||||||
@ -164,6 +180,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
animationType,
|
animationType,
|
||||||
withInterfaceAnimations,
|
withInterfaceAnimations,
|
||||||
orderDiff,
|
orderDiff,
|
||||||
|
isSavedDialog,
|
||||||
});
|
});
|
||||||
|
|
||||||
const getIsForumPanelClosed = useSelectorSignal(selectIsForumPanelClosed);
|
const getIsForumPanelClosed = useSelectorSignal(selectIsForumPanelClosed);
|
||||||
@ -171,6 +188,15 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
const handleClick = useLastCallback(() => {
|
const handleClick = useLastCallback(() => {
|
||||||
const noForumTopicPanel = isMobile && isForumAsMessages;
|
const noForumTopicPanel = isMobile && isForumAsMessages;
|
||||||
|
|
||||||
|
if (isSavedDialog) {
|
||||||
|
openSavedDialog({ chatId, noForumTopicPanel: true }, { forceOnHeavyAnimation: true });
|
||||||
|
|
||||||
|
if (isMobile) {
|
||||||
|
toggleChatInfo({ force: false });
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (isForum) {
|
if (isForum) {
|
||||||
if (isForumPanelOpen) {
|
if (isForumPanelOpen) {
|
||||||
closeForumPanel(undefined, { forceOnHeavyAnimation: true });
|
closeForumPanel(undefined, { forceOnHeavyAnimation: true });
|
||||||
@ -228,6 +254,8 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
isPinned,
|
isPinned,
|
||||||
isMuted,
|
isMuted,
|
||||||
canChangeFolder,
|
canChangeFolder,
|
||||||
|
isSavedDialog,
|
||||||
|
currentUserId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const isIntersecting = useIsIntersecting(ref, chat ? observeIntersection : undefined);
|
const isIntersecting = useIsIntersecting(ref, chat ? observeIntersection : undefined);
|
||||||
@ -242,6 +270,16 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
const isOnline = user && userStatus && isUserOnline(user, userStatus);
|
const isOnline = user && userStatus && isUserOnline(user, userStatus);
|
||||||
const { hasShownClass: isAvatarOnlineShown } = useShowTransition(isOnline);
|
const { hasShownClass: isAvatarOnlineShown } = useShowTransition(isOnline);
|
||||||
|
|
||||||
|
const href = useMemo(() => {
|
||||||
|
if (!IS_OPEN_IN_NEW_TAB_SUPPORTED) return undefined;
|
||||||
|
|
||||||
|
if (isSavedDialog) {
|
||||||
|
return `#${createLocationHash(currentUserId, 'thread', chatId)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `#${createLocationHash(chatId, 'thread', MAIN_THREAD_ID)}`;
|
||||||
|
}, [chatId, currentUserId, isSavedDialog]);
|
||||||
|
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -260,7 +298,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
<ListItem
|
<ListItem
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={className}
|
className={className}
|
||||||
href={IS_OPEN_IN_NEW_TAB_SUPPORTED ? `#${createLocationHash(chatId, 'thread', MAIN_THREAD_ID)}` : undefined}
|
href={href}
|
||||||
style={`top: ${offsetTop}px`}
|
style={`top: ${offsetTop}px`}
|
||||||
ripple={!isForum && !isMobile}
|
ripple={!isForum && !isMobile}
|
||||||
contextActions={contextActions}
|
contextActions={contextActions}
|
||||||
@ -272,6 +310,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
<Avatar
|
<Avatar
|
||||||
peer={peer}
|
peer={peer}
|
||||||
isSavedMessages={user?.isSelf}
|
isSavedMessages={user?.isSelf}
|
||||||
|
isSavedDialog={isSavedDialog}
|
||||||
withStory={!user?.isSelf}
|
withStory={!user?.isSelf}
|
||||||
withStoryGap={isAvatarOnlineShown}
|
withStoryGap={isAvatarOnlineShown}
|
||||||
storyViewerOrigin={StoryViewerOrigin.ChatList}
|
storyViewerOrigin={StoryViewerOrigin.ChatList}
|
||||||
@ -291,21 +330,22 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
peer={peer}
|
peer={peer}
|
||||||
withEmojiStatus
|
withEmojiStatus
|
||||||
isSavedMessages={chatId === user?.id && user?.isSelf}
|
isSavedMessages={chatId === user?.id && user?.isSelf}
|
||||||
|
isSavedDialog={isSavedDialog}
|
||||||
observeIntersection={observeIntersection}
|
observeIntersection={observeIntersection}
|
||||||
/>
|
/>
|
||||||
{isMuted && <i className="icon icon-muted" />}
|
{isMuted && !isSavedDialog && <i className="icon icon-muted" />}
|
||||||
<div className="separator" />
|
<div className="separator" />
|
||||||
{chat.lastMessage && (
|
{lastMessage && (
|
||||||
<LastMessageMeta
|
<LastMessageMeta
|
||||||
message={chat.lastMessage}
|
message={lastMessage}
|
||||||
outgoingStatus={lastMessageOutgoingStatus}
|
outgoingStatus={!isSavedDialog ? lastMessageOutgoingStatus : undefined}
|
||||||
draftDate={draft?.date}
|
draftDate={draft?.date}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="subtitle">
|
<div className="subtitle">
|
||||||
{renderSubtitle()}
|
{renderSubtitle()}
|
||||||
<ChatBadge chat={chat} isPinned={isPinned} isMuted={isMuted} />
|
<ChatBadge chat={chat} isPinned={isPinned} isMuted={isMuted} isSavedDialog={isSavedDialog} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{shouldRenderDeleteModal && (
|
{shouldRenderDeleteModal && (
|
||||||
@ -346,29 +386,34 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { chatId }): StateProps => {
|
(global, { chatId, isSavedDialog }): StateProps => {
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
return {};
|
return {
|
||||||
|
currentUserId: global.currentUserId!,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { lastMessage } = chat;
|
const lastMessageId = selectChatLastMessageId(global, chatId, isSavedDialog ? 'saved' : 'all');
|
||||||
const { senderId, isOutgoing } = lastMessage || {};
|
const lastMessage = selectChatLastMessage(global, chatId, isSavedDialog ? 'saved' : 'all');
|
||||||
|
const { senderId, isOutgoing, forwardInfo } = lastMessage || {};
|
||||||
|
const actualSenderId = isSavedDialog ? forwardInfo?.fromId : senderId;
|
||||||
const replyToMessageId = lastMessage && getMessageReplyInfo(lastMessage)?.replyToMsgId;
|
const replyToMessageId = lastMessage && getMessageReplyInfo(lastMessage)?.replyToMsgId;
|
||||||
const lastMessageSender = senderId
|
const lastMessageSender = actualSenderId ? selectPeer(global, actualSenderId) : undefined;
|
||||||
? (selectUser(global, senderId) || selectChat(global, senderId)) : undefined;
|
|
||||||
const lastMessageAction = lastMessage ? getMessageAction(lastMessage) : undefined;
|
const lastMessageAction = lastMessage ? getMessageAction(lastMessage) : undefined;
|
||||||
const actionTargetMessage = lastMessageAction && replyToMessageId
|
const actionTargetMessage = lastMessageAction && replyToMessageId
|
||||||
? selectChatMessage(global, chat.id, replyToMessageId)
|
? selectChatMessage(global, chat.id, replyToMessageId)
|
||||||
: undefined;
|
: undefined;
|
||||||
const { targetUserIds: actionTargetUserIds, targetChatId: actionTargetChatId } = lastMessageAction || {};
|
const { targetUserIds: actionTargetUserIds, targetChatId: actionTargetChatId } = lastMessageAction || {};
|
||||||
const privateChatUserId = getPrivateChatUserId(chat);
|
const privateChatUserId = getPrivateChatUserId(chat);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
chatId: currentChatId,
|
chatId: currentChatId,
|
||||||
threadId: currentThreadId,
|
threadId: currentThreadId,
|
||||||
type: messageListType,
|
type: messageListType,
|
||||||
} = selectCurrentMessageList(global) || {};
|
} = selectCurrentMessageList(global) || {};
|
||||||
const isSelected = chatId === currentChatId && currentThreadId === MAIN_THREAD_ID;
|
const isSelected = chatId === currentChatId && (isSavedDialog
|
||||||
|
? chatId === currentThreadId : currentThreadId === MAIN_THREAD_ID);
|
||||||
const isSelectedForum = (chat.isForum && chatId === currentChatId)
|
const isSelectedForum = (chat.isForum && chatId === currentChatId)
|
||||||
|| chatId === selectTabState(global).forumPanelChatId;
|
|| chatId === selectTabState(global).forumPanelChatId;
|
||||||
|
|
||||||
@ -399,6 +444,9 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
lastMessageTopic,
|
lastMessageTopic,
|
||||||
typingStatus,
|
typingStatus,
|
||||||
withInterfaceAnimations: selectCanAnimateInterface(global),
|
withInterfaceAnimations: selectCanAnimateInterface(global),
|
||||||
|
lastMessage,
|
||||||
|
lastMessageId,
|
||||||
|
currentUserId: global.currentUserId!,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(Chat));
|
)(Chat));
|
||||||
|
|||||||
@ -21,12 +21,13 @@ type OwnProps = {
|
|||||||
wasTopicOpened?: boolean;
|
wasTopicOpened?: boolean;
|
||||||
isPinned?: boolean;
|
isPinned?: boolean;
|
||||||
isMuted?: boolean;
|
isMuted?: boolean;
|
||||||
|
isSavedDialog?: boolean;
|
||||||
shouldShowOnlyMostImportant?: boolean;
|
shouldShowOnlyMostImportant?: boolean;
|
||||||
forceHidden?: boolean | Signal<boolean>;
|
forceHidden?: boolean | Signal<boolean>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ChatBadge: FC<OwnProps> = ({
|
const ChatBadge: FC<OwnProps> = ({
|
||||||
topic, chat, isPinned, isMuted, shouldShowOnlyMostImportant, wasTopicOpened, forceHidden,
|
topic, chat, isPinned, isMuted, shouldShowOnlyMostImportant, wasTopicOpened, forceHidden, isSavedDialog,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
unreadMentionsCount = 0, unreadReactionsCount = 0,
|
unreadMentionsCount = 0, unreadReactionsCount = 0,
|
||||||
@ -64,7 +65,7 @@ const ChatBadge: FC<OwnProps> = ({
|
|||||||
|| isTopicUnopened,
|
|| isTopicUnopened,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isUnread = Boolean(unreadCount || hasUnreadMark);
|
const isUnread = Boolean((unreadCount || hasUnreadMark) && !isSavedDialog);
|
||||||
const className = buildClassName(
|
const className = buildClassName(
|
||||||
'ChatBadge',
|
'ChatBadge',
|
||||||
shouldBeMuted && 'muted',
|
shouldBeMuted && 'muted',
|
||||||
@ -95,16 +96,21 @@ const ChatBadge: FC<OwnProps> = ({
|
|||||||
</div>
|
</div>
|
||||||
) : undefined;
|
) : undefined;
|
||||||
|
|
||||||
const pinnedElement = isPinned && !unreadCountElement && !unreadMentionsElement && !unreadReactionsElement && (
|
const pinnedElement = isPinned && (
|
||||||
<div className={className}>
|
<div className={className}>
|
||||||
<i className="icon icon-pinned-chat" />
|
<i className="icon icon-pinned-chat" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const visiblePinnedElement = !unreadCountElement && !unreadMentionsElement && !unreadReactionsElement
|
||||||
|
&& pinnedElement;
|
||||||
|
|
||||||
const elements = [
|
const elements = [
|
||||||
unopenedTopicElement, unreadReactionsElement, unreadMentionsElement, unreadCountElement, pinnedElement,
|
unopenedTopicElement, unreadReactionsElement, unreadMentionsElement, unreadCountElement, visiblePinnedElement,
|
||||||
].filter(Boolean);
|
].filter(Boolean);
|
||||||
|
|
||||||
|
if (isSavedDialog) return pinnedElement;
|
||||||
|
|
||||||
if (elements.length === 0) return undefined;
|
if (elements.length === 0) return undefined;
|
||||||
|
|
||||||
if (elements.length === 1) return elements[0];
|
if (elements.length === 1) return elements[0];
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
CHAT_HEIGHT_PX,
|
CHAT_HEIGHT_PX,
|
||||||
CHAT_LIST_SLICE,
|
CHAT_LIST_SLICE,
|
||||||
FRESH_AUTH_PERIOD,
|
FRESH_AUTH_PERIOD,
|
||||||
|
SAVED_FOLDER_ID,
|
||||||
} from '../../../config';
|
} from '../../../config';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { getOrderKey, getPinnedChatsCount } from '../../../util/folderManager';
|
import { getOrderKey, getPinnedChatsCount } from '../../../util/folderManager';
|
||||||
@ -41,16 +42,17 @@ import EmptyFolder from './EmptyFolder';
|
|||||||
import UnconfirmedSession from './UnconfirmedSession';
|
import UnconfirmedSession from './UnconfirmedSession';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
folderType: 'all' | 'archived' | 'folder';
|
className?: string;
|
||||||
|
folderType: 'all' | 'archived' | 'saved' | 'folder';
|
||||||
folderId?: number;
|
folderId?: number;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
canDisplayArchive?: boolean;
|
canDisplayArchive?: boolean;
|
||||||
archiveSettings: GlobalState['archiveSettings'];
|
archiveSettings?: GlobalState['archiveSettings'];
|
||||||
isForumPanelOpen?: boolean;
|
isForumPanelOpen?: boolean;
|
||||||
sessions?: Record<string, ApiSession>;
|
sessions?: Record<string, ApiSession>;
|
||||||
foldersDispatch: FolderEditDispatch;
|
foldersDispatch?: FolderEditDispatch;
|
||||||
onSettingsScreenSelect: (screen: SettingsScreens) => void;
|
onSettingsScreenSelect?: (screen: SettingsScreens) => void;
|
||||||
onLeftColumnContentChange: (content: LeftColumnContent) => void;
|
onLeftColumnContentChange?: (content: LeftColumnContent) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const INTERSECTION_THROTTLE = 200;
|
const INTERSECTION_THROTTLE = 200;
|
||||||
@ -58,6 +60,7 @@ const DRAG_ENTER_DEBOUNCE = 500;
|
|||||||
const RESERVED_HOTKEYS = new Set(['9', '0']);
|
const RESERVED_HOTKEYS = new Set(['9', '0']);
|
||||||
|
|
||||||
const ChatList: FC<OwnProps> = ({
|
const ChatList: FC<OwnProps> = ({
|
||||||
|
className,
|
||||||
folderType,
|
folderType,
|
||||||
folderId,
|
folderId,
|
||||||
isActive,
|
isActive,
|
||||||
@ -82,18 +85,19 @@ const ChatList: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const isArchived = folderType === 'archived';
|
const isArchived = folderType === 'archived';
|
||||||
const isAllFolder = folderType === 'all';
|
const isAllFolder = folderType === 'all';
|
||||||
|
const isSaved = folderType === 'saved';
|
||||||
const resolvedFolderId = (
|
const resolvedFolderId = (
|
||||||
isAllFolder ? ALL_FOLDER_ID : isArchived ? ARCHIVED_FOLDER_ID : folderId!
|
isAllFolder ? ALL_FOLDER_ID : isArchived ? ARCHIVED_FOLDER_ID : isSaved ? SAVED_FOLDER_ID : folderId!
|
||||||
);
|
);
|
||||||
|
|
||||||
const shouldDisplayArchive = isAllFolder && canDisplayArchive;
|
const shouldDisplayArchive = isAllFolder && canDisplayArchive && archiveSettings;
|
||||||
|
|
||||||
const orderedIds = useFolderManagerForOrderedIds(resolvedFolderId);
|
const orderedIds = useFolderManagerForOrderedIds(resolvedFolderId);
|
||||||
usePeerStoriesPolling(orderedIds);
|
usePeerStoriesPolling(orderedIds);
|
||||||
|
|
||||||
const chatsHeight = (orderedIds?.length || 0) * CHAT_HEIGHT_PX;
|
const chatsHeight = (orderedIds?.length || 0) * CHAT_HEIGHT_PX;
|
||||||
const archiveHeight = shouldDisplayArchive
|
const archiveHeight = shouldDisplayArchive
|
||||||
? archiveSettings.isMinimized ? ARCHIVE_MINIMIZED_HEIGHT : CHAT_HEIGHT_PX : 0;
|
? archiveSettings?.isMinimized ? ARCHIVE_MINIMIZED_HEIGHT : CHAT_HEIGHT_PX : 0;
|
||||||
|
|
||||||
const { orderDiffById, getAnimationType } = useOrderDiff(orderedIds);
|
const { orderDiffById, getAnimationType } = useOrderDiff(orderedIds);
|
||||||
|
|
||||||
@ -125,7 +129,7 @@ const ChatList: FC<OwnProps> = ({
|
|||||||
|
|
||||||
// Support <Cmd>+<Digit> to navigate between chats
|
// Support <Cmd>+<Digit> to navigate between chats
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isActive || !orderedIds || !IS_APP) {
|
if (!isActive || isSaved || !orderedIds || !IS_APP) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,13 +138,13 @@ const ChatList: FC<OwnProps> = ({
|
|||||||
const [, digit] = e.code.match(/Digit(\d)/) || [];
|
const [, digit] = e.code.match(/Digit(\d)/) || [];
|
||||||
if (!digit || RESERVED_HOTKEYS.has(digit)) return;
|
if (!digit || RESERVED_HOTKEYS.has(digit)) return;
|
||||||
|
|
||||||
const isArchiveInList = shouldDisplayArchive && !archiveSettings.isMinimized;
|
const isArchiveInList = shouldDisplayArchive && archiveSettings && !archiveSettings.isMinimized;
|
||||||
|
|
||||||
const shift = isArchiveInList ? -1 : 0;
|
const shift = isArchiveInList ? -1 : 0;
|
||||||
const position = Number(digit) + shift - 1;
|
const position = Number(digit) + shift - 1;
|
||||||
|
|
||||||
if (isArchiveInList && position === -1) {
|
if (isArchiveInList && position === -1) {
|
||||||
onLeftColumnContentChange(LeftColumnContent.Archived);
|
onLeftColumnContentChange?.(LeftColumnContent.Archived);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,7 +159,10 @@ const ChatList: FC<OwnProps> = ({
|
|||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('keydown', handleKeyDown);
|
document.removeEventListener('keydown', handleKeyDown);
|
||||||
};
|
};
|
||||||
}, [archiveSettings, isActive, onLeftColumnContentChange, openChat, openNextChat, orderedIds, shouldDisplayArchive]);
|
}, [
|
||||||
|
archiveSettings, isSaved, isActive, onLeftColumnContentChange, openChat, openNextChat, orderedIds,
|
||||||
|
shouldDisplayArchive,
|
||||||
|
]);
|
||||||
|
|
||||||
const { observe } = useIntersectionObserver({
|
const { observe } = useIntersectionObserver({
|
||||||
rootRef: containerRef,
|
rootRef: containerRef,
|
||||||
@ -163,7 +170,7 @@ const ChatList: FC<OwnProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleArchivedClick = useLastCallback(() => {
|
const handleArchivedClick = useLastCallback(() => {
|
||||||
onLeftColumnContentChange(LeftColumnContent.Archived);
|
onLeftColumnContentChange!(LeftColumnContent.Archived);
|
||||||
closeForumPanel();
|
closeForumPanel();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -199,7 +206,7 @@ const ChatList: FC<OwnProps> = ({
|
|||||||
toggleStoryRibbon({ isShown: false, isArchived });
|
toggleStoryRibbon({ isShown: false, isArchived });
|
||||||
});
|
});
|
||||||
|
|
||||||
const renderedOverflowTrigger = useTopOverscroll(containerRef, handleShowStoryRibbon, handleHideStoryRibbon);
|
const renderedOverflowTrigger = useTopOverscroll(containerRef, handleShowStoryRibbon, handleHideStoryRibbon, isSaved);
|
||||||
|
|
||||||
function renderChats() {
|
function renderChats() {
|
||||||
const viewportOffset = orderedIds!.indexOf(viewportIds![0]);
|
const viewportOffset = orderedIds!.indexOf(viewportIds![0]);
|
||||||
@ -213,10 +220,11 @@ const ChatList: FC<OwnProps> = ({
|
|||||||
return (
|
return (
|
||||||
<Chat
|
<Chat
|
||||||
key={id}
|
key={id}
|
||||||
teactOrderKey={isPinned ? i : getOrderKey(id)}
|
teactOrderKey={isPinned ? i : getOrderKey(id, isSaved)}
|
||||||
chatId={id}
|
chatId={id}
|
||||||
isPinned={isPinned}
|
isPinned={isPinned}
|
||||||
folderId={folderId}
|
folderId={folderId}
|
||||||
|
isSavedDialog={isSaved}
|
||||||
animationType={getAnimationType(id)}
|
animationType={getAnimationType(id)}
|
||||||
orderDiff={orderDiffById[id]}
|
orderDiff={orderDiffById[id]}
|
||||||
offsetTop={offsetTop}
|
offsetTop={offsetTop}
|
||||||
@ -229,7 +237,7 @@ const ChatList: FC<OwnProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<InfiniteScroll
|
<InfiniteScroll
|
||||||
className={buildClassName('chat-list custom-scroll', isForumPanelOpen && 'forum-panel-open')}
|
className={buildClassName('chat-list custom-scroll', isForumPanelOpen && 'forum-panel-open', className)}
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
items={viewportIds}
|
items={viewportIds}
|
||||||
itemSelector=".ListItem:not(.chat-item-archive)"
|
itemSelector=".ListItem:not(.chat-item-archive)"
|
||||||
@ -257,13 +265,13 @@ const ChatList: FC<OwnProps> = ({
|
|||||||
)}
|
)}
|
||||||
{viewportIds?.length ? (
|
{viewportIds?.length ? (
|
||||||
renderChats()
|
renderChats()
|
||||||
) : viewportIds && !viewportIds.length ? (
|
) : viewportIds && !viewportIds.length && !isSaved ? (
|
||||||
(
|
(
|
||||||
<EmptyFolder
|
<EmptyFolder
|
||||||
folderId={folderId}
|
folderId={folderId}
|
||||||
folderType={folderType}
|
folderType={folderType}
|
||||||
foldersDispatch={foldersDispatch}
|
foldersDispatch={foldersDispatch!}
|
||||||
onSettingsScreenSelect={onSettingsScreenSelect}
|
onSettingsScreenSelect={onSettingsScreenSelect!}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -18,7 +18,7 @@ import styles from './EmptyFolder.module.scss';
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
folderId?: number;
|
folderId?: number;
|
||||||
folderType: 'all' | 'archived' | 'folder';
|
folderType: 'all' | 'archived' | 'saved' | 'folder';
|
||||||
foldersDispatch: FolderEditDispatch;
|
foldersDispatch: FolderEditDispatch;
|
||||||
onSettingsScreenSelect: (screen: SettingsScreens) => void;
|
onSettingsScreenSelect: (screen: SettingsScreens) => void;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -288,7 +288,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
chat,
|
chat,
|
||||||
currentTopicId: chatId === currentChatId ? currentThreadId : undefined,
|
currentTopicId: chatId === currentChatId ? Number(currentThreadId) : undefined,
|
||||||
withInterfaceAnimations: selectCanAnimateInterface(global),
|
withInterfaceAnimations: selectCanAnimateInterface(global),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -58,6 +58,7 @@ export default function useChatListEntry({
|
|||||||
orderDiff,
|
orderDiff,
|
||||||
withInterfaceAnimations,
|
withInterfaceAnimations,
|
||||||
isTopic,
|
isTopic,
|
||||||
|
isSavedDialog,
|
||||||
}: {
|
}: {
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
lastMessage?: ApiMessage;
|
lastMessage?: ApiMessage;
|
||||||
@ -71,6 +72,7 @@ export default function useChatListEntry({
|
|||||||
actionTargetChatId?: string;
|
actionTargetChatId?: string;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
isTopic?: boolean;
|
isTopic?: boolean;
|
||||||
|
isSavedDialog?: boolean;
|
||||||
|
|
||||||
animationType: ChatAnimationTypes;
|
animationType: ChatAnimationTypes;
|
||||||
orderDiff: number;
|
orderDiff: number;
|
||||||
@ -102,14 +104,15 @@ export default function useChatListEntry({
|
|||||||
}, [actionTargetUserIds]);
|
}, [actionTargetUserIds]);
|
||||||
|
|
||||||
const renderLastMessageOrTyping = useCallback(() => {
|
const renderLastMessageOrTyping = useCallback(() => {
|
||||||
if (typingStatus && lastMessage && typingStatus.timestamp > lastMessage.date * 1000) {
|
if (!isSavedDialog && typingStatus && lastMessage && typingStatus.timestamp > lastMessage.date * 1000) {
|
||||||
return <TypingStatus typingStatus={typingStatus} />;
|
return <TypingStatus typingStatus={typingStatus} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDraftReplyToTopic = draft && draft.replyInfo?.replyToMsgId === lastMessageTopic?.id;
|
const isDraftReplyToTopic = draft && draft.replyInfo?.replyToMsgId === lastMessageTopic?.id;
|
||||||
const isEmptyLocalReply = draft?.replyInfo && !draft.text && draft.isLocal;
|
const isEmptyLocalReply = draft?.replyInfo && !draft.text && draft.isLocal;
|
||||||
|
|
||||||
const canDisplayDraft = !chat?.isForum && draft && !isEmptyLocalReply && (!isTopic || !isDraftReplyToTopic);
|
const canDisplayDraft = !chat?.isForum && !isSavedDialog && draft && !isEmptyLocalReply
|
||||||
|
&& (!isTopic || !isDraftReplyToTopic);
|
||||||
|
|
||||||
if (canDisplayDraft) {
|
if (canDisplayDraft) {
|
||||||
return (
|
return (
|
||||||
@ -169,7 +172,7 @@ export default function useChatListEntry({
|
|||||||
<span className="colon">:</span>
|
<span className="colon">:</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{lastMessage.forwardInfo && (<i className="icon icon-share-filled chat-prefix-icon" />)}
|
{!isSavedDialog && lastMessage.forwardInfo && (<i className="icon icon-share-filled chat-prefix-icon" />)}
|
||||||
{lastMessage.replyInfo?.type === 'story' && (<i className="icon icon-story-reply chat-prefix-icon" />)}
|
{lastMessage.replyInfo?.type === 'story' && (<i className="icon icon-story-reply chat-prefix-icon" />)}
|
||||||
{renderSummary(lang, lastMessage, observeIntersection, mediaBlobUrl || mediaThumbnail, isRoundVideo)}
|
{renderSummary(lang, lastMessage, observeIntersection, mediaBlobUrl || mediaThumbnail, isRoundVideo)}
|
||||||
</p>
|
</p>
|
||||||
@ -177,7 +180,7 @@ export default function useChatListEntry({
|
|||||||
}, [
|
}, [
|
||||||
actionTargetChatId, actionTargetMessage, actionTargetUsers, chat, chatId, draft, isAction,
|
actionTargetChatId, actionTargetMessage, actionTargetUsers, chat, chatId, draft, isAction,
|
||||||
isRoundVideo, isTopic, lang, lastMessage, lastMessageSender, lastMessageTopic, mediaBlobUrl, mediaThumbnail,
|
isRoundVideo, isTopic, lang, lastMessage, lastMessageSender, lastMessageTopic, mediaBlobUrl, mediaThumbnail,
|
||||||
observeIntersection, typingStatus,
|
observeIntersection, typingStatus, isSavedDialog,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function renderSubtitle() {
|
function renderSubtitle() {
|
||||||
|
|||||||
@ -2,11 +2,10 @@ import type { FC } from '../../../lib/teact/teact';
|
|||||||
import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact';
|
import React, { memo, useCallback, useMemo } from '../../../lib/teact/teact';
|
||||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiChat } from '../../../api/types';
|
import { filterUsersByName, isUserBot } from '../../../global/helpers';
|
||||||
|
|
||||||
import { filterUsersByName, isUserBot, sortChatIds } from '../../../global/helpers';
|
|
||||||
import { selectTabState } from '../../../global/selectors';
|
import { selectTabState } from '../../../global/selectors';
|
||||||
import { unique } from '../../../util/iteratees';
|
import { unique } from '../../../util/iteratees';
|
||||||
|
import sortChatIds from '../../common/helpers/sortChatIds';
|
||||||
|
|
||||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
@ -25,7 +24,6 @@ export type OwnProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
chatsById: Record<string, ApiChat>;
|
|
||||||
localContactIds?: string[];
|
localContactIds?: string[];
|
||||||
searchQuery?: string;
|
searchQuery?: string;
|
||||||
isSearching?: boolean;
|
isSearching?: boolean;
|
||||||
@ -40,7 +38,6 @@ const NewChatStep1: FC<OwnProps & StateProps> = ({
|
|||||||
onSelectedMemberIdsChange,
|
onSelectedMemberIdsChange,
|
||||||
onNextStep,
|
onNextStep,
|
||||||
onReset,
|
onReset,
|
||||||
chatsById,
|
|
||||||
localContactIds,
|
localContactIds,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
isSearching,
|
isSearching,
|
||||||
@ -80,11 +77,10 @@ const NewChatStep1: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return !user.isSelf && (user.canBeInvitedToGroup || !isUserBot(user));
|
return !user.isSelf && (user.canBeInvitedToGroup || !isUserBot(user));
|
||||||
}),
|
}),
|
||||||
chatsById,
|
|
||||||
false,
|
false,
|
||||||
selectedMemberIds,
|
selectedMemberIds,
|
||||||
);
|
);
|
||||||
}, [localContactIds, chatsById, searchQuery, localUserIds, globalUserIds, selectedMemberIds]);
|
}, [localContactIds, searchQuery, localUserIds, globalUserIds, selectedMemberIds]);
|
||||||
|
|
||||||
const handleNextStep = useCallback(() => {
|
const handleNextStep = useCallback(() => {
|
||||||
setGlobalSearchQuery({ query: '' });
|
setGlobalSearchQuery({ query: '' });
|
||||||
@ -133,7 +129,6 @@ const NewChatStep1: FC<OwnProps & StateProps> = ({
|
|||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
const { userIds: localContactIds } = global.contactList || {};
|
const { userIds: localContactIds } = global.contactList || {};
|
||||||
const { byId: chatsById } = global.chats;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
query: searchQuery,
|
query: searchQuery,
|
||||||
@ -145,7 +140,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const { userIds: localUserIds } = localResults || {};
|
const { userIds: localUserIds } = localResults || {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chatsById,
|
|
||||||
localContactIds,
|
localContactIds,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
isSearching: fetchingStatus?.chats,
|
isSearching: fetchingStatus?.chats,
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import { ALL_FOLDER_ID } from '../../../config';
|
|||||||
import {
|
import {
|
||||||
filterChatsByName,
|
filterChatsByName,
|
||||||
filterUsersByName,
|
filterUsersByName,
|
||||||
sortChatIds,
|
|
||||||
} from '../../../global/helpers';
|
} from '../../../global/helpers';
|
||||||
import { selectTabState } from '../../../global/selectors';
|
import { selectTabState } from '../../../global/selectors';
|
||||||
import { getOrderedIds } from '../../../util/folderManager';
|
import { getOrderedIds } from '../../../util/folderManager';
|
||||||
@ -19,6 +18,7 @@ import { unique } from '../../../util/iteratees';
|
|||||||
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
|
||||||
import { throttle } from '../../../util/schedulers';
|
import { throttle } from '../../../util/schedulers';
|
||||||
import { renderMessageSummary } from '../../common/helpers/renderMessageText';
|
import { renderMessageSummary } from '../../common/helpers/renderMessageText';
|
||||||
|
import sortChatIds from '../../common/helpers/sortChatIds';
|
||||||
|
|
||||||
import useAppLayout from '../../../hooks/useAppLayout';
|
import useAppLayout from '../../../hooks/useAppLayout';
|
||||||
import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
|
import useHorizontalScroll from '../../../hooks/useHorizontalScroll';
|
||||||
@ -147,8 +147,8 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
|||||||
].filter((accountPeerId) => !localPeerIds.includes(accountPeerId)));
|
].filter((accountPeerId) => !localPeerIds.includes(accountPeerId)));
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...sortChatIds(localPeerIds, chatsById, undefined, currentUserId ? [currentUserId] : undefined),
|
...sortChatIds(localPeerIds, undefined, currentUserId ? [currentUserId] : undefined),
|
||||||
...sortChatIds(accountPeerIds, chatsById),
|
...sortChatIds(accountPeerIds),
|
||||||
];
|
];
|
||||||
}, [searchQuery, currentUserId, contactIds, lang, accountChatIds, accountUserIds, chatsById]);
|
}, [searchQuery, currentUserId, contactIds, lang, accountChatIds, accountUserIds, chatsById]);
|
||||||
|
|
||||||
@ -161,10 +161,9 @@ const ChatResults: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return sortChatIds(
|
return sortChatIds(
|
||||||
unique([...globalChatIds, ...globalUserIds]),
|
unique([...globalChatIds, ...globalUserIds]),
|
||||||
chatsById,
|
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
}, [chatsById, globalChatIds, globalUserIds, searchQuery]);
|
}, [globalChatIds, globalUserIds, searchQuery]);
|
||||||
|
|
||||||
const foundMessages = useMemo(() => {
|
const foundMessages = useMemo(() => {
|
||||||
if ((!searchQuery && !searchDate) || !foundIds || foundIds.length === 0) {
|
if ((!searchQuery && !searchDate) || !foundIds || foundIds.length === 0) {
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import React, {
|
|||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
import type { TabState } from '../../global/types';
|
import type { TabState } from '../../global/types';
|
||||||
|
import type { ThreadId } from '../../types';
|
||||||
|
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
@ -33,7 +34,7 @@ const DraftRecipientPicker: FC<OwnProps> = ({
|
|||||||
}
|
}
|
||||||
}, [isOpen, markIsShown]);
|
}, [isOpen, markIsShown]);
|
||||||
|
|
||||||
const handleSelectRecipient = useCallback((recipientId: string, threadId?: number) => {
|
const handleSelectRecipient = useCallback((recipientId: string, threadId?: ThreadId) => {
|
||||||
openChatWithDraft({
|
openChatWithDraft({
|
||||||
chatId: recipientId,
|
chatId: recipientId,
|
||||||
threadId,
|
threadId,
|
||||||
|
|||||||
@ -4,6 +4,8 @@ import React, {
|
|||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||||
|
|
||||||
|
import type { ThreadId } from '../../types';
|
||||||
|
|
||||||
import { getChatTitle, getUserFirstOrLastName, isUserId } from '../../global/helpers';
|
import { getChatTitle, getUserFirstOrLastName, isUserId } from '../../global/helpers';
|
||||||
import { selectChat, selectTabState, selectUser } from '../../global/selectors';
|
import { selectChat, selectTabState, selectUser } from '../../global/selectors';
|
||||||
|
|
||||||
@ -47,7 +49,7 @@ const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}, [isOpen, markIsShown]);
|
}, [isOpen, markIsShown]);
|
||||||
|
|
||||||
const handleSelectRecipient = useCallback((recipientId: string, threadId?: number) => {
|
const handleSelectRecipient = useCallback((recipientId: string, threadId?: ThreadId) => {
|
||||||
const isSelf = recipientId === currentUserId;
|
const isSelf = recipientId === currentUserId;
|
||||||
if (isStory) {
|
if (isStory) {
|
||||||
forwardStory({ toChatId: recipientId });
|
forwardStory({ toChatId: recipientId });
|
||||||
@ -82,7 +84,7 @@ const ForwardRecipientPicker: FC<OwnProps & StateProps> = ({
|
|||||||
forwardToSavedMessages();
|
forwardToSavedMessages();
|
||||||
showNotification({ message });
|
showNotification({ message });
|
||||||
} else {
|
} else {
|
||||||
setForwardChatOrTopic({ chatId: recipientId, topicId: threadId });
|
setForwardChatOrTopic({ chatId: recipientId, topicId: Number(threadId) });
|
||||||
}
|
}
|
||||||
}, [currentUserId, isManyMessages, isStory, lang]);
|
}, [currentUserId, isManyMessages, isStory, lang]);
|
||||||
|
|
||||||
|
|||||||
@ -227,6 +227,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
isSynced,
|
isSynced,
|
||||||
inviteViaLinkModal,
|
inviteViaLinkModal,
|
||||||
oneTimeMediaModal,
|
oneTimeMediaModal,
|
||||||
|
currentUserId,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
initMain,
|
initMain,
|
||||||
@ -420,7 +421,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const parsedLocationHash = parseLocationHash();
|
const parsedLocationHash = parseLocationHash(currentUserId);
|
||||||
if (!parsedLocationHash) return;
|
if (!parsedLocationHash) return;
|
||||||
|
|
||||||
openThread({
|
openThread({
|
||||||
@ -428,7 +429,7 @@ const Main: FC<OwnProps & StateProps> = ({
|
|||||||
threadId: parsedLocationHash.threadId,
|
threadId: parsedLocationHash.threadId,
|
||||||
type: parsedLocationHash.type,
|
type: parsedLocationHash.type,
|
||||||
});
|
});
|
||||||
}, []);
|
}, [currentUserId]);
|
||||||
|
|
||||||
// Restore Transition slide class after async rendering
|
// Restore Transition slide class after async rendering
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
|
|||||||
@ -86,7 +86,9 @@ const PREMIUM_BOTTOM_VIDEOS: string[] = [
|
|||||||
'translations',
|
'translations',
|
||||||
];
|
];
|
||||||
|
|
||||||
type ApiLimitTypeWithoutUpload = Exclude<ApiLimitType, 'uploadMaxFileparts' | 'chatlistInvites' | 'chatlistJoined'>;
|
type ApiLimitTypeWithoutUpload = Exclude<ApiLimitType,
|
||||||
|
'uploadMaxFileparts' | 'chatlistInvites' | 'chatlistJoined' | 'savedDialogsPinned'
|
||||||
|
>;
|
||||||
|
|
||||||
const LIMITS_ORDER: ApiLimitTypeWithoutUpload[] = [
|
const LIMITS_ORDER: ApiLimitTypeWithoutUpload[] = [
|
||||||
'channels',
|
'channels',
|
||||||
|
|||||||
@ -31,6 +31,7 @@ const LIMIT_DESCRIPTION: Record<ApiLimitTypeWithModal, string> = {
|
|||||||
channels: 'LimitReachedCommunities',
|
channels: 'LimitReachedCommunities',
|
||||||
chatlistInvites: 'LimitReachedFolderLinks',
|
chatlistInvites: 'LimitReachedFolderLinks',
|
||||||
chatlistJoined: 'LimitReachedSharedFolders',
|
chatlistJoined: 'LimitReachedSharedFolders',
|
||||||
|
savedDialogsPinned: 'LimitReachedPinSavedDialogs',
|
||||||
};
|
};
|
||||||
|
|
||||||
const LIMIT_DESCRIPTION_BLOCKED: Record<ApiLimitTypeWithModal, string> = {
|
const LIMIT_DESCRIPTION_BLOCKED: Record<ApiLimitTypeWithModal, string> = {
|
||||||
@ -42,6 +43,7 @@ const LIMIT_DESCRIPTION_BLOCKED: Record<ApiLimitTypeWithModal, string> = {
|
|||||||
channels: 'LimitReachedCommunitiesLocked',
|
channels: 'LimitReachedCommunitiesLocked',
|
||||||
chatlistInvites: 'LimitReachedFolderLinksLocked',
|
chatlistInvites: 'LimitReachedFolderLinksLocked',
|
||||||
chatlistJoined: 'LimitReachedSharedFoldersLocked',
|
chatlistJoined: 'LimitReachedSharedFoldersLocked',
|
||||||
|
savedDialogsPinned: 'LimitReachedPinSavedDialogsLocked',
|
||||||
};
|
};
|
||||||
|
|
||||||
const LIMIT_DESCRIPTION_PREMIUM: Record<ApiLimitTypeWithModal, string> = {
|
const LIMIT_DESCRIPTION_PREMIUM: Record<ApiLimitTypeWithModal, string> = {
|
||||||
@ -53,6 +55,7 @@ const LIMIT_DESCRIPTION_PREMIUM: Record<ApiLimitTypeWithModal, string> = {
|
|||||||
channels: 'LimitReachedCommunitiesPremium',
|
channels: 'LimitReachedCommunitiesPremium',
|
||||||
chatlistInvites: 'LimitReachedFolderLinksPremium',
|
chatlistInvites: 'LimitReachedFolderLinksPremium',
|
||||||
chatlistJoined: 'LimitReachedSharedFoldersPremium',
|
chatlistJoined: 'LimitReachedSharedFoldersPremium',
|
||||||
|
savedDialogsPinned: 'LimitReachedPinSavedDialogsPremium',
|
||||||
};
|
};
|
||||||
|
|
||||||
const LIMIT_ICON: Record<ApiLimitTypeWithModal, IconName> = {
|
const LIMIT_ICON: Record<ApiLimitTypeWithModal, IconName> = {
|
||||||
@ -64,6 +67,7 @@ const LIMIT_ICON: Record<ApiLimitTypeWithModal, IconName> = {
|
|||||||
channels: 'chats-badge',
|
channels: 'chats-badge',
|
||||||
chatlistInvites: 'link-badge',
|
chatlistInvites: 'link-badge',
|
||||||
chatlistJoined: 'folder-badge',
|
chatlistJoined: 'folder-badge',
|
||||||
|
savedDialogsPinned: 'pin-badge',
|
||||||
};
|
};
|
||||||
|
|
||||||
const LIMIT_VALUE_FORMATTER: Partial<Record<ApiLimitTypeWithModal, (...args: any[]) => string>> = {
|
const LIMIT_VALUE_FORMATTER: Partial<Record<ApiLimitTypeWithModal, (...args: any[]) => string>> = {
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { getActions, withGlobal } from '../../global';
|
|||||||
import type {
|
import type {
|
||||||
ApiMessage, ApiPeer, ApiPhoto, ApiUser,
|
ApiMessage, ApiPeer, ApiPhoto, ApiUser,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import { MediaViewerOrigin } from '../../types';
|
import { MediaViewerOrigin, type ThreadId } from '../../types';
|
||||||
|
|
||||||
import { ANIMATION_END_DELAY } from '../../config';
|
import { ANIMATION_END_DELAY } from '../../config';
|
||||||
import { getChatMediaMessageIds, isChatAdmin, isUserId } from '../../global/helpers';
|
import { getChatMediaMessageIds, isChatAdmin, isUserId } from '../../global/helpers';
|
||||||
@ -57,7 +57,7 @@ import './MediaViewer.scss';
|
|||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
mediaId?: number;
|
mediaId?: number;
|
||||||
senderId?: string;
|
senderId?: string;
|
||||||
isChatWithSelf?: boolean;
|
isChatWithSelf?: boolean;
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { withGlobal } from '../../global';
|
|||||||
import type {
|
import type {
|
||||||
ApiDimensions, ApiMessage, ApiPeer,
|
ApiDimensions, ApiMessage, ApiPeer,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import { MediaViewerOrigin } from '../../types';
|
import { MediaViewerOrigin, type ThreadId } from '../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
selectChat, selectChatMessage, selectIsMessageProtected, selectScheduledMessage, selectTabState, selectUser,
|
selectChat, selectChatMessage, selectIsMessageProtected, selectScheduledMessage, selectTabState, selectUser,
|
||||||
@ -31,7 +31,7 @@ import './MediaViewerContent.scss';
|
|||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
mediaId?: number;
|
mediaId?: number;
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
avatarOwnerId?: string;
|
avatarOwnerId?: string;
|
||||||
origin?: MediaViewerOrigin;
|
origin?: MediaViewerOrigin;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
@ -45,7 +45,7 @@ type StateProps = {
|
|||||||
chatId?: string;
|
chatId?: string;
|
||||||
mediaId?: number;
|
mediaId?: number;
|
||||||
senderId?: string;
|
senderId?: string;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
avatarOwner?: ApiPeer;
|
avatarOwner?: ApiPeer;
|
||||||
message?: ApiMessage;
|
message?: ApiMessage;
|
||||||
origin?: MediaViewerOrigin;
|
origin?: MediaViewerOrigin;
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import React, {
|
|||||||
memo, useEffect, useLayoutEffect, useRef, useState,
|
memo, useEffect, useLayoutEffect, useRef, useState,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
|
|
||||||
import type { MediaViewerOrigin } from '../../types';
|
import type { MediaViewerOrigin, ThreadId } from '../../types';
|
||||||
import type { RealTouchEvent } from '../../util/captureEvents';
|
import type { RealTouchEvent } from '../../util/captureEvents';
|
||||||
|
|
||||||
import { animateNumber, timingFunctions } from '../../util/animation';
|
import { animateNumber, timingFunctions } from '../../util/animation';
|
||||||
@ -46,7 +46,7 @@ type OwnProps = {
|
|||||||
isOpen?: boolean;
|
isOpen?: boolean;
|
||||||
selectMedia: (id?: number) => void;
|
selectMedia: (id?: number) => void;
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
avatarOwnerId?: string;
|
avatarOwnerId?: string;
|
||||||
origin?: MediaViewerOrigin;
|
origin?: MediaViewerOrigin;
|
||||||
withAnimation?: boolean;
|
withAnimation?: boolean;
|
||||||
|
|||||||
@ -9,7 +9,7 @@ import type {
|
|||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { MessageListType } from '../../global/types';
|
import type { MessageListType } from '../../global/types';
|
||||||
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
||||||
import type { FocusDirection } from '../../types';
|
import type { FocusDirection, ThreadId } from '../../types';
|
||||||
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
|
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -45,7 +45,7 @@ import ContextMenuContainer from './message/ContextMenuContainer.async';
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
message: ApiMessage;
|
message: ApiMessage;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
messageListType?: MessageListType;
|
messageListType?: MessageListType;
|
||||||
observeIntersectionForReading?: ObserveFn;
|
observeIntersectionForReading?: ObserveFn;
|
||||||
observeIntersectionForLoading?: ObserveFn;
|
observeIntersectionForLoading?: ObserveFn;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import type { ApiSticker, ApiUpdateConnectionStateType } from '../../api/types';
|
|||||||
import type { MessageList } from '../../global/types';
|
import type { MessageList } from '../../global/types';
|
||||||
|
|
||||||
import { getPeerIdDividend } from '../../global/helpers';
|
import { getPeerIdDividend } from '../../global/helpers';
|
||||||
import { selectChat, selectCurrentMessageList } from '../../global/selectors';
|
import { selectChat, selectChatLastMessage, selectCurrentMessageList } from '../../global/selectors';
|
||||||
|
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
@ -101,10 +101,12 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lastMessage = selectChatLastMessage(global, chat.id);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sticker,
|
sticker,
|
||||||
lastUnreadMessageId: chat.lastMessage && chat.lastMessage.id !== chat.lastReadInboxMessageId
|
lastUnreadMessageId: lastMessage && lastMessage.id !== chat.lastReadInboxMessageId
|
||||||
? chat.lastMessage.id
|
? lastMessage.id
|
||||||
: undefined,
|
: undefined,
|
||||||
connectionState: global.connectionState,
|
connectionState: global.connectionState,
|
||||||
currentMessageList: selectCurrentMessageList(global),
|
currentMessageList: selectCurrentMessageList(global),
|
||||||
|
|||||||
@ -5,13 +5,14 @@ import React, {
|
|||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { MessageListType } from '../../global/types';
|
import type { MessageListType } from '../../global/types';
|
||||||
import type { IAnchorPosition } from '../../types';
|
import type { IAnchorPosition, ThreadId } from '../../types';
|
||||||
import { MAIN_THREAD_ID } from '../../api/types';
|
import { MAIN_THREAD_ID } from '../../api/types';
|
||||||
import { ManagementScreens } from '../../types';
|
import { ManagementScreens } from '../../types';
|
||||||
|
|
||||||
import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterdom';
|
import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterdom';
|
||||||
import {
|
import {
|
||||||
getHasAdminRight,
|
getHasAdminRight,
|
||||||
|
isAnonymousForwardsChat,
|
||||||
isChatBasicGroup, isChatChannel, isChatSuperGroup, isUserId,
|
isChatBasicGroup, isChatChannel, isChatSuperGroup, isUserId,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import {
|
import {
|
||||||
@ -44,7 +45,7 @@ import HeaderMenuContainer from './HeaderMenuContainer.async';
|
|||||||
|
|
||||||
interface OwnProps {
|
interface OwnProps {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId: number;
|
threadId: ThreadId;
|
||||||
messageListType: MessageListType;
|
messageListType: MessageListType;
|
||||||
canExpandActions: boolean;
|
canExpandActions: boolean;
|
||||||
isForForum?: boolean;
|
isForForum?: boolean;
|
||||||
@ -485,7 +486,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
(isMainThread || chat.isForum) && (isChannel || isChatSuperGroup(chat)) && chat.isNotJoined,
|
(isMainThread || chat.isForum) && (isChannel || isChatSuperGroup(chat)) && chat.isNotJoined,
|
||||||
);
|
);
|
||||||
const canSearch = isMainThread || isDiscussionThread;
|
const canSearch = isMainThread || isDiscussionThread;
|
||||||
const canCall = ARE_CALLS_SUPPORTED && isUserId(chat.id) && !isChatWithSelf && !bot;
|
const canCall = ARE_CALLS_SUPPORTED && isUserId(chat.id) && !isChatWithSelf && !bot && !chat.isSupport
|
||||||
|
&& !isAnonymousForwardsChat(chat.id);
|
||||||
const canMute = isMainThread && !isChatWithSelf && !canSubscribe;
|
const canMute = isMainThread && !isChatWithSelf && !canSubscribe;
|
||||||
const canLeave = isMainThread && !canSubscribe;
|
const canLeave = isMainThread && !canSubscribe;
|
||||||
const canEnterVoiceChat = ARE_CALLS_SUPPORTED && isMainThread && chat.isCallActive;
|
const canEnterVoiceChat = ARE_CALLS_SUPPORTED && isMainThread && chat.isCallActive;
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import React, {
|
|||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { ApiBotCommand, ApiChat } from '../../api/types';
|
import type { ApiBotCommand, ApiChat } from '../../api/types';
|
||||||
import type { IAnchorPosition } from '../../types';
|
import type { IAnchorPosition, ThreadId } from '../../types';
|
||||||
import type { IconName } from '../../types/icons';
|
import type { IconName } from '../../types/icons';
|
||||||
import { MAIN_THREAD_ID } from '../../api/types';
|
import { MAIN_THREAD_ID } from '../../api/types';
|
||||||
|
|
||||||
@ -71,7 +71,7 @@ const BOT_BUTTONS: Record<string, { icon: IconName; label: string }> = {
|
|||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId: number;
|
threadId: ThreadId;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
withExtraActions: boolean;
|
withExtraActions: boolean;
|
||||||
anchor: IAnchorPosition;
|
anchor: IAnchorPosition;
|
||||||
@ -276,7 +276,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleEditTopicClick = useLastCallback(() => {
|
const handleEditTopicClick = useLastCallback(() => {
|
||||||
openEditTopicPanel({ chatId, topicId: threadId });
|
openEditTopicPanel({ chatId, topicId: Number(threadId) });
|
||||||
setShouldCloseFast(!isRightColumnShown);
|
setShouldCloseFast(!isRightColumnShown);
|
||||||
closeMenu();
|
closeMenu();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -77,7 +77,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.select-mode-active,
|
&.select-mode-active,
|
||||||
&.type-pinned {
|
&.type-pinned,
|
||||||
|
&.saved-dialog {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|
||||||
.last-in-list {
|
.last-in-list {
|
||||||
|
|||||||
@ -15,16 +15,19 @@ import type { MessageListType } from '../../global/types';
|
|||||||
import type { Signal } from '../../util/signals';
|
import type { Signal } from '../../util/signals';
|
||||||
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
|
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
|
||||||
import { MAIN_THREAD_ID } from '../../api/types';
|
import { MAIN_THREAD_ID } from '../../api/types';
|
||||||
import { LoadMoreDirection } from '../../types';
|
import { LoadMoreDirection, type ThreadId } from '../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ANIMATION_END_DELAY,
|
ANIMATION_END_DELAY,
|
||||||
|
ANONYMOUS_USER_ID,
|
||||||
MESSAGE_LIST_SLICE,
|
MESSAGE_LIST_SLICE,
|
||||||
SERVICE_NOTIFICATIONS_USER_ID,
|
SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
} from '../../config';
|
} from '../../config';
|
||||||
import { forceMeasure, requestForcedReflow, requestMeasure } from '../../lib/fasterdom/fasterdom';
|
import { forceMeasure, requestForcedReflow, requestMeasure } from '../../lib/fasterdom/fasterdom';
|
||||||
import {
|
import {
|
||||||
|
getIsSavedDialog,
|
||||||
getMessageHtmlId,
|
getMessageHtmlId,
|
||||||
|
isAnonymousForwardsChat,
|
||||||
isChatChannel,
|
isChatChannel,
|
||||||
isChatGroup,
|
isChatGroup,
|
||||||
isChatWithRepliesBot,
|
isChatWithRepliesBot,
|
||||||
@ -35,6 +38,7 @@ import {
|
|||||||
selectBot,
|
selectBot,
|
||||||
selectChat,
|
selectChat,
|
||||||
selectChatFullInfo,
|
selectChatFullInfo,
|
||||||
|
selectChatLastMessage,
|
||||||
selectChatMessages,
|
selectChatMessages,
|
||||||
selectChatScheduledMessages,
|
selectChatScheduledMessages,
|
||||||
selectCurrentMessageIds,
|
selectCurrentMessageIds,
|
||||||
@ -80,7 +84,7 @@ import './MessageList.scss';
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId: number;
|
threadId: ThreadId;
|
||||||
type: MessageListType;
|
type: MessageListType;
|
||||||
isComments?: boolean;
|
isComments?: boolean;
|
||||||
canPost: boolean;
|
canPost: boolean;
|
||||||
@ -101,6 +105,7 @@ type StateProps = {
|
|||||||
isGroupChat?: boolean;
|
isGroupChat?: boolean;
|
||||||
isChatWithSelf?: boolean;
|
isChatWithSelf?: boolean;
|
||||||
isRepliesChat?: boolean;
|
isRepliesChat?: boolean;
|
||||||
|
isAnonymousForwards?: boolean;
|
||||||
isCreator?: boolean;
|
isCreator?: boolean;
|
||||||
isBot?: boolean;
|
isBot?: boolean;
|
||||||
isSynced?: boolean;
|
isSynced?: boolean;
|
||||||
@ -119,6 +124,7 @@ type StateProps = {
|
|||||||
isServiceNotificationsChat?: boolean;
|
isServiceNotificationsChat?: boolean;
|
||||||
isEmptyThread?: boolean;
|
isEmptyThread?: boolean;
|
||||||
isForum?: boolean;
|
isForum?: boolean;
|
||||||
|
currentUserId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const MESSAGE_REACTIONS_POLLING_INTERVAL = 20 * 1000;
|
const MESSAGE_REACTIONS_POLLING_INTERVAL = 20 * 1000;
|
||||||
@ -152,6 +158,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
isReady,
|
isReady,
|
||||||
isChatWithSelf,
|
isChatWithSelf,
|
||||||
isRepliesChat,
|
isRepliesChat,
|
||||||
|
isAnonymousForwards,
|
||||||
isCreator,
|
isCreator,
|
||||||
isBot,
|
isBot,
|
||||||
messageIds,
|
messageIds,
|
||||||
@ -171,8 +178,9 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
topic,
|
topic,
|
||||||
noMessageSendingAnimation,
|
noMessageSendingAnimation,
|
||||||
isServiceNotificationsChat,
|
isServiceNotificationsChat,
|
||||||
onPinnedIntersectionChange,
|
currentUserId,
|
||||||
getForceNextPinnedInHeader,
|
getForceNextPinnedInHeader,
|
||||||
|
onPinnedIntersectionChange,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
loadViewportMessages, setScrollOffset, loadSponsoredMessages, loadMessageReactions, copyMessagesByIds,
|
loadViewportMessages, setScrollOffset, loadSponsoredMessages, loadMessageReactions, copyMessagesByIds,
|
||||||
@ -199,6 +207,9 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
const isScrollTopJustUpdatedRef = useRef(false);
|
const isScrollTopJustUpdatedRef = useRef(false);
|
||||||
const shouldAnimateAppearanceRef = useRef(Boolean(lastMessage));
|
const shouldAnimateAppearanceRef = useRef(Boolean(lastMessage));
|
||||||
|
|
||||||
|
const isSavedDialog = getIsSavedDialog(chatId, threadId, currentUserId);
|
||||||
|
const hasOpenChatButton = isSavedDialog && threadId !== ANONYMOUS_USER_ID;
|
||||||
|
|
||||||
const areMessagesLoaded = Boolean(messageIds);
|
const areMessagesLoaded = Boolean(messageIds);
|
||||||
|
|
||||||
useSyncEffect(() => {
|
useSyncEffect(() => {
|
||||||
@ -250,7 +261,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
? groupMessages(
|
? groupMessages(
|
||||||
orderBy(listedMessages, orderRule),
|
orderBy(listedMessages, orderRule),
|
||||||
memoUnreadDividerBeforeIdRef.current,
|
memoUnreadDividerBeforeIdRef.current,
|
||||||
!isForum ? threadId : undefined,
|
!isForum ? Number(threadId) : undefined,
|
||||||
isChatWithSelf,
|
isChatWithSelf,
|
||||||
)
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
@ -547,9 +558,9 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
}, [isSelectModeActive]);
|
}, [isSelectModeActive]);
|
||||||
|
|
||||||
const isPrivate = Boolean(chatId && isUserId(chatId));
|
const isPrivate = Boolean(chatId && isUserId(chatId));
|
||||||
const withUsers = Boolean((!isPrivate && !isChannelChat) || isChatWithSelf || isRepliesChat);
|
const withUsers = Boolean((!isPrivate && !isChannelChat) || isChatWithSelf || isRepliesChat || isAnonymousForwards);
|
||||||
const noAvatars = Boolean(!withUsers || isChannelChat);
|
const noAvatars = Boolean(!withUsers || isChannelChat);
|
||||||
const shouldRenderGreeting = isUserId(chatId) && !isChatWithSelf && !isBot
|
const shouldRenderGreeting = isUserId(chatId) && !isChatWithSelf && !isBot && !isAnonymousForwards
|
||||||
&& (
|
&& (
|
||||||
(
|
(
|
||||||
!messageGroups && !lastMessage && messageIds
|
!messageGroups && !lastMessage && messageIds
|
||||||
@ -575,6 +586,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
isSelectModeActive && 'select-mode-active',
|
isSelectModeActive && 'select-mode-active',
|
||||||
isScrolled && 'scrolled',
|
isScrolled && 'scrolled',
|
||||||
!isReady && 'is-animating',
|
!isReady && 'is-animating',
|
||||||
|
hasOpenChatButton && 'saved-dialog',
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasMessages = (messageIds && messageGroups) || lastMessage;
|
const hasMessages = (messageIds && messageGroups) || lastMessage;
|
||||||
@ -610,6 +622,7 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
chatId={chatId}
|
chatId={chatId}
|
||||||
isComments={isComments}
|
isComments={isComments}
|
||||||
isChannelChat={isChannelChat}
|
isChannelChat={isChannelChat}
|
||||||
|
isSavedDialog={isSavedDialog}
|
||||||
messageIds={messageIds || [lastMessage!.id]}
|
messageIds={messageIds || [lastMessage!.id]}
|
||||||
messageGroups={messageGroups || groupMessages([lastMessage!])}
|
messageGroups={messageGroups || groupMessages([lastMessage!])}
|
||||||
getContainerHeight={getContainerHeight}
|
getContainerHeight={getContainerHeight}
|
||||||
@ -642,9 +655,10 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { chatId, threadId, type }): StateProps => {
|
(global, { chatId, threadId, type }): StateProps => {
|
||||||
|
const currentUserId = global.currentUserId!;
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
return {};
|
return { currentUserId };
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageIds = selectCurrentMessageIds(global, chatId, threadId, type);
|
const messageIds = selectCurrentMessageIds(global, chatId, threadId, type);
|
||||||
@ -652,14 +666,17 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
? selectChatScheduledMessages(global, chatId)
|
? selectChatScheduledMessages(global, chatId)
|
||||||
: selectChatMessages(global, chatId);
|
: selectChatMessages(global, chatId);
|
||||||
|
|
||||||
|
const isSavedDialog = getIsSavedDialog(chatId, threadId, currentUserId);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
threadId !== MAIN_THREAD_ID && !chat?.isForum
|
threadId !== MAIN_THREAD_ID && !isSavedDialog && !chat?.isForum
|
||||||
&& !(messagesById && threadId && messagesById[threadId])
|
&& !(messagesById && threadId && messagesById[Number(threadId)])
|
||||||
) {
|
) {
|
||||||
return {};
|
return { currentUserId };
|
||||||
}
|
}
|
||||||
|
|
||||||
const { isRestricted, restrictionReason, lastMessage } = chat;
|
const { isRestricted, restrictionReason } = chat;
|
||||||
|
const lastMessage = selectChatLastMessage(global, chatId, isSavedDialog ? 'saved' : 'all');
|
||||||
const focusingId = selectFocusedMessageId(global, chatId);
|
const focusingId = selectFocusedMessageId(global, chatId);
|
||||||
|
|
||||||
const withLastMessageWhenPreloading = (
|
const withLastMessageWhenPreloading = (
|
||||||
@ -683,6 +700,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isCreator: chat.isCreator,
|
isCreator: chat.isCreator,
|
||||||
isChatWithSelf: selectIsChatWithSelf(global, chatId),
|
isChatWithSelf: selectIsChatWithSelf(global, chatId),
|
||||||
isRepliesChat: isChatWithRepliesBot(chatId),
|
isRepliesChat: isChatWithRepliesBot(chatId),
|
||||||
|
isAnonymousForwards: isAnonymousForwardsChat(chatId),
|
||||||
isBot: Boolean(chatBot),
|
isBot: Boolean(chatBot),
|
||||||
isSynced: global.isSynced,
|
isSynced: global.isSynced,
|
||||||
messageIds,
|
messageIds,
|
||||||
@ -697,6 +715,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isServiceNotificationsChat: chatId === SERVICE_NOTIFICATIONS_USER_ID,
|
isServiceNotificationsChat: chatId === SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
isForum: chat.isForum,
|
isForum: chat.isForum,
|
||||||
isEmptyThread,
|
isEmptyThread,
|
||||||
|
currentUserId,
|
||||||
...(withLastMessageWhenPreloading && { lastMessage }),
|
...(withLastMessageWhenPreloading && { lastMessage }),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import React, { memo } from '../../lib/teact/teact';
|
|||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
import type { MessageListType } from '../../global/types';
|
import type { MessageListType } from '../../global/types';
|
||||||
|
import type { ThreadId } from '../../types';
|
||||||
import type { Signal } from '../../util/signals';
|
import type { Signal } from '../../util/signals';
|
||||||
import type { MessageDateGroup } from './helpers/groupMessages';
|
import type { MessageDateGroup } from './helpers/groupMessages';
|
||||||
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
|
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
|
||||||
@ -33,7 +34,7 @@ import MessageListBotInfo from './MessageListBotInfo';
|
|||||||
interface OwnProps {
|
interface OwnProps {
|
||||||
isCurrentUserPremium?: boolean;
|
isCurrentUserPremium?: boolean;
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId: number;
|
threadId: ThreadId;
|
||||||
messageIds: number[];
|
messageIds: number[];
|
||||||
messageGroups: MessageDateGroup[];
|
messageGroups: MessageDateGroup[];
|
||||||
getContainerHeight: Signal<number | undefined>;
|
getContainerHeight: Signal<number | undefined>;
|
||||||
@ -54,6 +55,7 @@ interface OwnProps {
|
|||||||
isSchedule: boolean;
|
isSchedule: boolean;
|
||||||
shouldRenderBotInfo?: boolean;
|
shouldRenderBotInfo?: boolean;
|
||||||
noAppearanceAnimation: boolean;
|
noAppearanceAnimation: boolean;
|
||||||
|
isSavedDialog?: boolean;
|
||||||
onFabToggle: AnyToVoidFunction;
|
onFabToggle: AnyToVoidFunction;
|
||||||
onNotchToggle: AnyToVoidFunction;
|
onNotchToggle: AnyToVoidFunction;
|
||||||
onPinnedIntersectionChange: PinnedIntersectionChangedCallback;
|
onPinnedIntersectionChange: PinnedIntersectionChangedCallback;
|
||||||
@ -85,6 +87,7 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
isSchedule,
|
isSchedule,
|
||||||
shouldRenderBotInfo,
|
shouldRenderBotInfo,
|
||||||
noAppearanceAnimation,
|
noAppearanceAnimation,
|
||||||
|
isSavedDialog,
|
||||||
onFabToggle,
|
onFabToggle,
|
||||||
onNotchToggle,
|
onNotchToggle,
|
||||||
onPinnedIntersectionChange,
|
onPinnedIntersectionChange,
|
||||||
@ -92,6 +95,7 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
const { openHistoryCalendar } = getActions();
|
const { openHistoryCalendar } = getActions();
|
||||||
|
|
||||||
const getIsReady = useDerivedSignal(isReady);
|
const getIsReady = useDerivedSignal(isReady);
|
||||||
|
const areDatesClickable = !isSavedDialog && !isSchedule;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
observeIntersectionForReading,
|
observeIntersectionForReading,
|
||||||
@ -162,7 +166,7 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
message={message}
|
message={message}
|
||||||
threadId={threadId}
|
threadId={threadId}
|
||||||
messageListType={type}
|
messageListType={type}
|
||||||
isInsideTopic={Boolean(threadId && threadId !== MAIN_THREAD_ID)}
|
isInsideTopic={Boolean(threadId && threadId !== MAIN_THREAD_ID && !isSavedDialog)}
|
||||||
observeIntersectionForReading={observeIntersectionForReading}
|
observeIntersectionForReading={observeIntersectionForReading}
|
||||||
observeIntersectionForLoading={observeIntersectionForLoading}
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||||
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||||
@ -261,10 +265,10 @@ const MessageListContent: FC<OwnProps> = ({
|
|||||||
teactFastList
|
teactFastList
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={buildClassName('sticky-date', !isSchedule && 'interactive')}
|
className={buildClassName('sticky-date', areDatesClickable && 'interactive')}
|
||||||
key="date-header"
|
key="date-header"
|
||||||
onMouseDown={preventMessageInputBlur}
|
onMouseDown={preventMessageInputBlur}
|
||||||
onClick={!isSchedule ? () => openHistoryCalendar({ selectedAt: dateGroup.datetime }) : undefined}
|
onClick={areDatesClickable ? () => openHistoryCalendar({ selectedAt: dateGroup.datetime }) : undefined}
|
||||||
>
|
>
|
||||||
<span dir="auto">
|
<span dir="auto">
|
||||||
{isSchedule && dateGroup.originalDate === SCHEDULED_WHEN_ONLINE && (
|
{isSchedule && dateGroup.originalDate === SCHEDULED_WHEN_ONLINE && (
|
||||||
|
|||||||
@ -254,8 +254,7 @@
|
|||||||
|
|
||||||
.Composer,
|
.Composer,
|
||||||
.MessageSelectToolbar,
|
.MessageSelectToolbar,
|
||||||
.unpin-all-button,
|
.composer-button,
|
||||||
.join-subscribe-button,
|
|
||||||
.messaging-disabled {
|
.messaging-disabled {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -264,8 +263,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.MessageSelectToolbar-inner,
|
.MessageSelectToolbar-inner,
|
||||||
.unpin-all-button,
|
.composer-button,
|
||||||
.join-subscribe-button,
|
|
||||||
.messaging-disabled {
|
.messaging-disabled {
|
||||||
.mask-image-disabled & {
|
.mask-image-disabled & {
|
||||||
box-shadow: 0 0.25rem 0.5rem 0.125rem var(--color-default-shadow);
|
box-shadow: 0 0.25rem 0.5rem 0.125rem var(--color-default-shadow);
|
||||||
@ -310,11 +308,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.join-subscribe-button,
|
.composer-button {
|
||||||
.unpin-all-button {
|
|
||||||
height: 3.5rem;
|
height: 3.5rem;
|
||||||
transform: scaleX(1);
|
transform: scaleX(1);
|
||||||
transition: transform var(--select-transition), background-color 0.15s, color 0.15s;
|
transition: transform var(--select-transition), background-color 0.15s, color 0.15s;
|
||||||
|
color: var(--color-primary);
|
||||||
|
|
||||||
.select-mode-active + .middle-column-footer & {
|
.select-mode-active + .middle-column-footer & {
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
|
|||||||
@ -9,11 +9,12 @@ import type {
|
|||||||
ActiveEmojiInteraction,
|
ActiveEmojiInteraction,
|
||||||
MessageListType,
|
MessageListType,
|
||||||
} from '../../global/types';
|
} from '../../global/types';
|
||||||
import type { ThemeKey } from '../../types';
|
import type { ThemeKey, ThreadId } from '../../types';
|
||||||
import { MAIN_THREAD_ID } from '../../api/types';
|
import { MAIN_THREAD_ID } from '../../api/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ANIMATION_END_DELAY,
|
ANIMATION_END_DELAY,
|
||||||
|
ANONYMOUS_USER_ID,
|
||||||
EDITABLE_INPUT_CSS_SELECTOR,
|
EDITABLE_INPUT_CSS_SELECTOR,
|
||||||
EDITABLE_INPUT_ID,
|
EDITABLE_INPUT_ID,
|
||||||
GENERAL_TOPIC_ID,
|
GENERAL_TOPIC_ID,
|
||||||
@ -29,6 +30,7 @@ import {
|
|||||||
getCanPostInChat,
|
getCanPostInChat,
|
||||||
getForumComposerPlaceholder,
|
getForumComposerPlaceholder,
|
||||||
getHasAdminRight,
|
getHasAdminRight,
|
||||||
|
getIsSavedDialog,
|
||||||
getMessageSendingRestrictionReason,
|
getMessageSendingRestrictionReason,
|
||||||
isChatChannel,
|
isChatChannel,
|
||||||
isChatGroup,
|
isChatGroup,
|
||||||
@ -101,7 +103,7 @@ interface OwnProps {
|
|||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
isComments?: boolean;
|
isComments?: boolean;
|
||||||
messageListType?: MessageListType;
|
messageListType?: MessageListType;
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
@ -145,6 +147,8 @@ type StateProps = {
|
|||||||
topMessageId?: number;
|
topMessageId?: number;
|
||||||
canUnpin?: boolean;
|
canUnpin?: boolean;
|
||||||
canUnblock?: boolean;
|
canUnblock?: boolean;
|
||||||
|
isSavedDialog?: boolean;
|
||||||
|
canShowOpenChatButton?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function isImage(item: DataTransferItem) {
|
function isImage(item: DataTransferItem) {
|
||||||
@ -201,6 +205,8 @@ function MiddleColumn({
|
|||||||
topMessageId,
|
topMessageId,
|
||||||
canUnpin,
|
canUnpin,
|
||||||
canUnblock,
|
canUnblock,
|
||||||
|
isSavedDialog,
|
||||||
|
canShowOpenChatButton,
|
||||||
}: OwnProps & StateProps) {
|
}: OwnProps & StateProps) {
|
||||||
const {
|
const {
|
||||||
openChat,
|
openChat,
|
||||||
@ -378,6 +384,10 @@ function MiddleColumn({
|
|||||||
setIsUnpinModalOpen(false);
|
setIsUnpinModalOpen(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleOpenChatFromSaved = useLastCallback(() => {
|
||||||
|
openChat({ id: String(threadId) });
|
||||||
|
});
|
||||||
|
|
||||||
const handleUnpinAllMessages = useLastCallback(() => {
|
const handleUnpinAllMessages = useLastCallback(() => {
|
||||||
unpinAllMessages({ chatId: chatId!, threadId: threadId! });
|
unpinAllMessages({ chatId: chatId!, threadId: threadId! });
|
||||||
closeUnpinModal();
|
closeUnpinModal();
|
||||||
@ -465,12 +475,12 @@ function MiddleColumn({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const isMessagingDisabled = Boolean(
|
const isMessagingDisabled = Boolean(
|
||||||
!isPinnedMessageList && !renderingCanPost && !renderingCanRestartBot && !renderingCanStartBot
|
!isPinnedMessageList && !isSavedDialog && !renderingCanPost && !renderingCanRestartBot && !renderingCanStartBot
|
||||||
&& !renderingCanSubscribe && composerRestrictionMessage,
|
&& !renderingCanSubscribe && composerRestrictionMessage,
|
||||||
);
|
);
|
||||||
const withMessageListBottomShift = Boolean(
|
const withMessageListBottomShift = Boolean(
|
||||||
renderingCanRestartBot || renderingCanSubscribe || renderingShouldSendJoinRequest || renderingCanStartBot
|
renderingCanRestartBot || renderingCanSubscribe || renderingShouldSendJoinRequest || renderingCanStartBot
|
||||||
|| isPinnedMessageList || renderingCanUnblock,
|
|| isPinnedMessageList || canShowOpenChatButton || renderingCanUnblock,
|
||||||
);
|
);
|
||||||
const withExtraShift = Boolean(isMessagingDisabled || isSelectModeActive || isPinnedMessageList);
|
const withExtraShift = Boolean(isMessagingDisabled || isSelectModeActive || isPinnedMessageList);
|
||||||
|
|
||||||
@ -563,7 +573,7 @@ function MiddleColumn({
|
|||||||
size="tiny"
|
size="tiny"
|
||||||
fluid
|
fluid
|
||||||
color="secondary"
|
color="secondary"
|
||||||
className="unpin-all-button"
|
className="composer-button unpin-all-button"
|
||||||
onClick={handleOpenUnpinModal}
|
onClick={handleOpenUnpinModal}
|
||||||
>
|
>
|
||||||
<i className="icon icon-unpin" />
|
<i className="icon icon-unpin" />
|
||||||
@ -571,6 +581,19 @@ function MiddleColumn({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{canShowOpenChatButton && (
|
||||||
|
<div className="middle-column-footer-button-container" dir={lang.isRtl ? 'rtl' : undefined}>
|
||||||
|
<Button
|
||||||
|
size="tiny"
|
||||||
|
fluid
|
||||||
|
color="secondary"
|
||||||
|
className="composer-button open-chat-button"
|
||||||
|
onClick={handleOpenChatFromSaved}
|
||||||
|
>
|
||||||
|
<span>{lang('SavedOpenChat')}</span>
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{isMessagingDisabled && (
|
{isMessagingDisabled && (
|
||||||
<div className={messagingDisabledClassName}>
|
<div className={messagingDisabledClassName}>
|
||||||
<div className="messaging-disabled-inner">
|
<div className="messaging-disabled-inner">
|
||||||
@ -588,7 +611,7 @@ function MiddleColumn({
|
|||||||
size="tiny"
|
size="tiny"
|
||||||
fluid
|
fluid
|
||||||
ripple
|
ripple
|
||||||
className="join-subscribe-button"
|
className="composer-button join-subscribe-button"
|
||||||
onClick={handleSubscribeClick}
|
onClick={handleSubscribeClick}
|
||||||
>
|
>
|
||||||
{lang(renderingIsChannel ? 'ProfileJoinChannel' : 'ProfileJoinGroup')}
|
{lang(renderingIsChannel ? 'ProfileJoinChannel' : 'ProfileJoinGroup')}
|
||||||
@ -601,7 +624,7 @@ function MiddleColumn({
|
|||||||
size="tiny"
|
size="tiny"
|
||||||
fluid
|
fluid
|
||||||
ripple
|
ripple
|
||||||
className="join-subscribe-button"
|
className="composer-button join-subscribe-button"
|
||||||
onClick={handleSubscribeClick}
|
onClick={handleSubscribeClick}
|
||||||
>
|
>
|
||||||
{lang('ChannelJoinRequest')}
|
{lang('ChannelJoinRequest')}
|
||||||
@ -614,7 +637,7 @@ function MiddleColumn({
|
|||||||
size="tiny"
|
size="tiny"
|
||||||
fluid
|
fluid
|
||||||
ripple
|
ripple
|
||||||
className="join-subscribe-button"
|
className="composer-button join-subscribe-button"
|
||||||
onClick={handleStartBot}
|
onClick={handleStartBot}
|
||||||
>
|
>
|
||||||
{lang('BotStart')}
|
{lang('BotStart')}
|
||||||
@ -627,7 +650,7 @@ function MiddleColumn({
|
|||||||
size="tiny"
|
size="tiny"
|
||||||
fluid
|
fluid
|
||||||
ripple
|
ripple
|
||||||
className="join-subscribe-button"
|
className="composer-button join-subscribe-button"
|
||||||
onClick={handleRestartBot}
|
onClick={handleRestartBot}
|
||||||
>
|
>
|
||||||
{lang('BotRestart')}
|
{lang('BotRestart')}
|
||||||
@ -640,7 +663,7 @@ function MiddleColumn({
|
|||||||
size="tiny"
|
size="tiny"
|
||||||
fluid
|
fluid
|
||||||
ripple
|
ripple
|
||||||
className="join-subscribe-button"
|
className="composer-button join-subscribe-button"
|
||||||
onClick={handleUnblock}
|
onClick={handleUnblock}
|
||||||
>
|
>
|
||||||
{lang('Unblock')}
|
{lang('Unblock')}
|
||||||
@ -763,8 +786,11 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
? selectChatMessage(global, audioChatId, audioMessageId)
|
? selectChatMessage(global, audioChatId, audioMessageId)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const isCommentThread = threadId !== MAIN_THREAD_ID && !chat?.isForum;
|
const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId);
|
||||||
const topMessageId = isCommentThread ? threadId : undefined;
|
const canShowOpenChatButton = isSavedDialog && threadId !== ANONYMOUS_USER_ID;
|
||||||
|
|
||||||
|
const isCommentThread = threadId !== MAIN_THREAD_ID && !isSavedDialog && !chat?.isForum;
|
||||||
|
const topMessageId = isCommentThread ? Number(threadId) : undefined;
|
||||||
|
|
||||||
const canUnpin = chat && (
|
const canUnpin = chat && (
|
||||||
isPrivate || (
|
isPrivate || (
|
||||||
@ -787,7 +813,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
&& (!chat || canPost)
|
&& (!chat || canPost)
|
||||||
&& !isBotNotStarted
|
&& !isBotNotStarted
|
||||||
&& !(shouldJoinToSend && chat?.isNotJoined)
|
&& !(shouldJoinToSend && chat?.isNotJoined)
|
||||||
&& !shouldBlockSendInForum,
|
&& !shouldBlockSendInForum
|
||||||
|
&& !isSavedDialog,
|
||||||
isPinnedMessageList,
|
isPinnedMessageList,
|
||||||
currentUserBannedRights: chat?.currentUserBannedRights,
|
currentUserBannedRights: chat?.currentUserBannedRights,
|
||||||
defaultBannedRights: chat?.defaultBannedRights,
|
defaultBannedRights: chat?.defaultBannedRights,
|
||||||
@ -807,6 +834,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
topMessageId,
|
topMessageId,
|
||||||
canUnpin,
|
canUnpin,
|
||||||
canUnblock,
|
canUnblock,
|
||||||
|
isSavedDialog,
|
||||||
|
canShowOpenChatButton,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(MiddleColumn));
|
)(MiddleColumn));
|
||||||
|
|||||||
@ -243,6 +243,17 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.saved-dialog-avatar {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.overlay-avatar {
|
||||||
|
margin-left: 2.125rem;
|
||||||
|
.inner {
|
||||||
|
outline: 2px solid var(--color-background);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.status,
|
.status,
|
||||||
.typing-status {
|
.typing-status {
|
||||||
display: inline;
|
display: inline;
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import type {
|
|||||||
import type { GlobalState, MessageListType } from '../../global/types';
|
import type { GlobalState, MessageListType } from '../../global/types';
|
||||||
import type { Signal } from '../../util/signals';
|
import type { Signal } from '../../util/signals';
|
||||||
import { MAIN_THREAD_ID } from '../../api/types';
|
import { MAIN_THREAD_ID } from '../../api/types';
|
||||||
import { StoryViewerOrigin } from '../../types';
|
import { StoryViewerOrigin, type ThreadId } from '../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EDITABLE_INPUT_CSS_SELECTOR,
|
EDITABLE_INPUT_CSS_SELECTOR,
|
||||||
@ -24,6 +24,7 @@ import {
|
|||||||
import { requestMutation } from '../../lib/fasterdom/fasterdom';
|
import { requestMutation } from '../../lib/fasterdom/fasterdom';
|
||||||
import {
|
import {
|
||||||
getChatTitle,
|
getChatTitle,
|
||||||
|
getIsSavedDialog,
|
||||||
getMessageKey,
|
getMessageKey,
|
||||||
getSenderTitle,
|
getSenderTitle,
|
||||||
isChatChannel,
|
isChatChannel,
|
||||||
@ -83,7 +84,7 @@ const EMOJI_STATUS_SIZE = 22;
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId: number;
|
threadId: ThreadId;
|
||||||
messageListType: MessageListType;
|
messageListType: MessageListType;
|
||||||
isComments?: boolean;
|
isComments?: boolean;
|
||||||
isReady?: boolean;
|
isReady?: boolean;
|
||||||
@ -98,6 +99,7 @@ type StateProps = {
|
|||||||
pinnedMessageIds?: number[] | number;
|
pinnedMessageIds?: number[] | number;
|
||||||
messagesById?: Record<number, ApiMessage>;
|
messagesById?: Record<number, ApiMessage>;
|
||||||
canUnpin?: boolean;
|
canUnpin?: boolean;
|
||||||
|
isSavedDialog?: boolean;
|
||||||
topMessageSender?: ApiPeer;
|
topMessageSender?: ApiPeer;
|
||||||
typingStatus?: ApiTypingStatus;
|
typingStatus?: ApiTypingStatus;
|
||||||
isSelectModeActive?: boolean;
|
isSelectModeActive?: boolean;
|
||||||
@ -145,6 +147,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
|||||||
getCurrentPinnedIndexes,
|
getCurrentPinnedIndexes,
|
||||||
getLoadingPinnedId,
|
getLoadingPinnedId,
|
||||||
emojiStatusSticker,
|
emojiStatusSticker,
|
||||||
|
isSavedDialog,
|
||||||
onFocusPinnedMessage,
|
onFocusPinnedMessage,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
@ -352,7 +355,7 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
function renderInfo() {
|
function renderInfo() {
|
||||||
if (messageListType === 'thread') {
|
if (messageListType === 'thread') {
|
||||||
if (threadId === MAIN_THREAD_ID || chat?.isForum) {
|
if (threadId === MAIN_THREAD_ID || isSavedDialog || chat?.isForum) {
|
||||||
return renderChatInfo();
|
return renderChatInfo();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -377,25 +380,30 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderChatInfo() {
|
function renderChatInfo() {
|
||||||
|
// TODO Implement count
|
||||||
|
const savedMessagesStatus = isSavedDialog ? lang('SavedMessages') : undefined;
|
||||||
|
|
||||||
|
const realChatId = isSavedDialog ? String(threadId) : chatId;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{(isLeftColumnHideable || currentTransitionKey > 0) && renderBackButton(shouldShowCloseButton, true)}
|
{(isLeftColumnHideable || currentTransitionKey > 0) && renderBackButton(shouldShowCloseButton, !isSavedDialog)}
|
||||||
<div
|
<div
|
||||||
className="chat-info-wrapper"
|
className="chat-info-wrapper"
|
||||||
onClick={handleHeaderClick}
|
onClick={handleHeaderClick}
|
||||||
onMouseDown={handleHeaderMouseDown}
|
onMouseDown={handleHeaderMouseDown}
|
||||||
>
|
>
|
||||||
{isUserId(chatId) ? (
|
{isUserId(realChatId) ? (
|
||||||
<PrivateChatInfo
|
<PrivateChatInfo
|
||||||
key={chatId}
|
key={realChatId}
|
||||||
userId={chatId}
|
userId={realChatId}
|
||||||
typingStatus={typingStatus}
|
typingStatus={typingStatus}
|
||||||
status={connectionStatusText}
|
status={connectionStatusText || savedMessagesStatus}
|
||||||
withDots={Boolean(connectionStatusText)}
|
withDots={Boolean(connectionStatusText)}
|
||||||
withFullInfo
|
withFullInfo
|
||||||
withMediaViewer
|
withMediaViewer
|
||||||
withStory={!isChatWithSelf}
|
withStory={!isChatWithSelf}
|
||||||
withUpdatingStatus
|
withUpdatingStatus
|
||||||
|
isSavedDialog={isSavedDialog}
|
||||||
storyViewerOrigin={StoryViewerOrigin.MiddleHeaderAvatar}
|
storyViewerOrigin={StoryViewerOrigin.MiddleHeaderAvatar}
|
||||||
emojiStatusSize={EMOJI_STATUS_SIZE}
|
emojiStatusSize={EMOJI_STATUS_SIZE}
|
||||||
noRtl
|
noRtl
|
||||||
@ -403,16 +411,17 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<GroupChatInfo
|
<GroupChatInfo
|
||||||
key={chatId}
|
key={realChatId}
|
||||||
chatId={chatId}
|
chatId={realChatId}
|
||||||
threadId={threadId}
|
threadId={!isSavedDialog ? threadId : undefined}
|
||||||
typingStatus={typingStatus}
|
typingStatus={typingStatus}
|
||||||
status={connectionStatusText}
|
status={connectionStatusText || savedMessagesStatus}
|
||||||
withDots={Boolean(connectionStatusText)}
|
withDots={Boolean(connectionStatusText)}
|
||||||
withMediaViewer={threadId === MAIN_THREAD_ID}
|
withMediaViewer={threadId === MAIN_THREAD_ID}
|
||||||
withFullInfo={threadId === MAIN_THREAD_ID}
|
withFullInfo={threadId === MAIN_THREAD_ID}
|
||||||
withUpdatingStatus
|
withUpdatingStatus
|
||||||
withStory
|
withStory
|
||||||
|
isSavedDialog={isSavedDialog}
|
||||||
storyViewerOrigin={StoryViewerOrigin.MiddleHeaderAvatar}
|
storyViewerOrigin={StoryViewerOrigin.MiddleHeaderAvatar}
|
||||||
emojiStatusSize={EMOJI_STATUS_SIZE}
|
emojiStatusSize={EMOJI_STATUS_SIZE}
|
||||||
onEmojiStatusClick={handleChannelStatusClick}
|
onEmojiStatusClick={handleChannelStatusClick}
|
||||||
@ -552,6 +561,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const emojiStatus = chat?.emojiStatus;
|
const emojiStatus = chat?.emojiStatus;
|
||||||
const emojiStatusSticker = emojiStatus && global.customEmojis.byId[emojiStatus.documentId];
|
const emojiStatusSticker = emojiStatus && global.customEmojis.byId[emojiStatus.documentId];
|
||||||
|
|
||||||
|
const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId);
|
||||||
|
|
||||||
const state: StateProps = {
|
const state: StateProps = {
|
||||||
typingStatus,
|
typingStatus,
|
||||||
isLeftColumnShown,
|
isLeftColumnShown,
|
||||||
@ -569,6 +580,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isFetchingDifference: global.isFetchingDifference,
|
isFetchingDifference: global.isFetchingDifference,
|
||||||
emojiStatusSticker,
|
emojiStatusSticker,
|
||||||
hasButtonInHeader: canStartBot || canRestartBot || canSubscribe || shouldSendJoinRequest,
|
hasButtonInHeader: canStartBot || canRestartBot || canSubscribe || shouldSendJoinRequest,
|
||||||
|
isSavedDialog,
|
||||||
};
|
};
|
||||||
|
|
||||||
const messagesById = selectChatMessages(global, chatId);
|
const messagesById = selectChatMessages(global, chatId);
|
||||||
@ -576,8 +588,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (threadId !== MAIN_THREAD_ID && !chat?.isForum) {
|
if (threadId !== MAIN_THREAD_ID && !isSavedDialog && !chat?.isForum) {
|
||||||
const pinnedMessageId = threadId;
|
const pinnedMessageId = Number(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;
|
||||||
|
|
||||||
@ -590,7 +602,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const pinnedMessageIds = selectPinnedIds(global, chatId, threadId);
|
const pinnedMessageIds = !isSavedDialog ? selectPinnedIds(global, chatId, threadId) : undefined;
|
||||||
if (pinnedMessageIds?.length) {
|
if (pinnedMessageIds?.length) {
|
||||||
const firstPinnedMessage = messagesById[pinnedMessageIds[0]];
|
const firstPinnedMessage = messagesById[pinnedMessageIds[0]];
|
||||||
const {
|
const {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import React, {
|
|||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { ApiChat } from '../../api/types';
|
import type { ApiChat } from '../../api/types';
|
||||||
|
import type { ThreadId } from '../../types';
|
||||||
|
|
||||||
import { requestMutation } from '../../lib/fasterdom/fasterdom';
|
import { requestMutation } from '../../lib/fasterdom/fasterdom';
|
||||||
import {
|
import {
|
||||||
@ -32,7 +33,7 @@ export type OwnProps = {
|
|||||||
type StateProps = {
|
type StateProps = {
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
query?: string;
|
query?: string;
|
||||||
totalCount?: number;
|
totalCount?: number;
|
||||||
foundIds?: number[];
|
foundIds?: number[];
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import React, {
|
|||||||
import { getActions } from '../../../global';
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
import type { ApiAttachBot } from '../../../api/types';
|
import type { ApiAttachBot } from '../../../api/types';
|
||||||
import type { IAnchorPosition, ISettings } from '../../../types';
|
import type { IAnchorPosition, ISettings, ThreadId } from '../../../types';
|
||||||
|
|
||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
@ -20,7 +20,7 @@ type OwnProps = {
|
|||||||
theme: ISettings['theme'];
|
theme: ISettings['theme'];
|
||||||
isInSideMenu?: true;
|
isInSideMenu?: true;
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
canShowNew?: boolean;
|
canShowNew?: boolean;
|
||||||
onMenuOpened: VoidFunction;
|
onMenuOpened: VoidFunction;
|
||||||
onMenuClosed: VoidFunction;
|
onMenuClosed: VoidFunction;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import React, {
|
|||||||
|
|
||||||
import type { ApiAttachMenuPeerType } from '../../../api/types';
|
import type { ApiAttachMenuPeerType } from '../../../api/types';
|
||||||
import type { GlobalState } from '../../../global/types';
|
import type { GlobalState } from '../../../global/types';
|
||||||
import type { ISettings } from '../../../types';
|
import type { ISettings, ThreadId } from '../../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CONTENT_TYPES_WITH_PREVIEW, DEBUG_LOG_FILENAME, SUPPORTED_AUDIO_CONTENT_TYPES,
|
CONTENT_TYPES_WITH_PREVIEW, DEBUG_LOG_FILENAME, SUPPORTED_AUDIO_CONTENT_TYPES,
|
||||||
@ -32,7 +32,7 @@ import './AttachMenu.scss';
|
|||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
isButtonVisible: boolean;
|
isButtonVisible: boolean;
|
||||||
canAttachMedia: boolean;
|
canAttachMedia: boolean;
|
||||||
canAttachPolls: boolean;
|
canAttachPolls: boolean;
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import type {
|
|||||||
ApiAttachment, ApiChatMember, ApiSticker,
|
ApiAttachment, ApiChatMember, ApiSticker,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import type { GlobalState } from '../../../global/types';
|
import type { GlobalState } from '../../../global/types';
|
||||||
|
import type { ThreadId } from '../../../types';
|
||||||
import type { Signal } from '../../../util/signals';
|
import type { Signal } from '../../../util/signals';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -59,7 +60,7 @@ import styles from './AttachmentModal.module.scss';
|
|||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId: number;
|
threadId: ThreadId;
|
||||||
attachments: ApiAttachment[];
|
attachments: ApiAttachment[];
|
||||||
getHtml: Signal<string>;
|
getHtml: Signal<string>;
|
||||||
canShowCustomSendMenu?: boolean;
|
canShowCustomSendMenu?: boolean;
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import React, {
|
|||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiInputMessageReplyInfo } from '../../../api/types';
|
import type { ApiInputMessageReplyInfo } from '../../../api/types';
|
||||||
import type { IAnchorPosition, ISettings } from '../../../types';
|
import type { IAnchorPosition, ISettings, ThreadId } from '../../../types';
|
||||||
import type { Signal } from '../../../util/signals';
|
import type { Signal } from '../../../util/signals';
|
||||||
|
|
||||||
import { EDITABLE_INPUT_ID } from '../../../config';
|
import { EDITABLE_INPUT_ID } from '../../../config';
|
||||||
@ -48,7 +48,7 @@ type OwnProps = {
|
|||||||
ref?: RefObject<HTMLDivElement>;
|
ref?: RefObject<HTMLDivElement>;
|
||||||
id: string;
|
id: string;
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId: number;
|
threadId: ThreadId;
|
||||||
isAttachmentModalInput?: boolean;
|
isAttachmentModalInput?: boolean;
|
||||||
isStoryInput?: boolean;
|
isStoryInput?: boolean;
|
||||||
customEmojiPrefix: string;
|
customEmojiPrefix: string;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import React, {
|
|||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiChat, ApiSticker, ApiStickerSet } from '../../../api/types';
|
import type { ApiChat, ApiSticker, ApiStickerSet } from '../../../api/types';
|
||||||
import type { StickerSetOrReactionsSetOrRecent } from '../../../types';
|
import type { StickerSetOrReactionsSetOrRecent, ThreadId } from '../../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
CHAT_STICKER_SET_ID,
|
CHAT_STICKER_SET_ID,
|
||||||
@ -48,7 +48,7 @@ import styles from './StickerPicker.module.scss';
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
className: string;
|
className: string;
|
||||||
isHidden?: boolean;
|
isHidden?: boolean;
|
||||||
isTranslucent?: boolean;
|
isTranslucent?: boolean;
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import React, { memo, useEffect, useRef } from '../../../lib/teact/teact';
|
|||||||
import { withGlobal } from '../../../global';
|
import { withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiSticker } from '../../../api/types';
|
import type { ApiSticker } from '../../../api/types';
|
||||||
|
import type { ThreadId } from '../../../types';
|
||||||
|
|
||||||
import { STICKER_SIZE_PICKER } from '../../../config';
|
import { STICKER_SIZE_PICKER } from '../../../config';
|
||||||
import { selectIsChatWithSelf, selectIsCurrentUserPremium } from '../../../global/selectors';
|
import { selectIsChatWithSelf, selectIsCurrentUserPremium } from '../../../global/selectors';
|
||||||
@ -21,7 +22,7 @@ import './StickerTooltip.scss';
|
|||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
onStickerSelect: (sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean) => void;
|
onStickerSelect: (sticker: ApiSticker, isSilent?: boolean, shouldSchedule?: boolean) => void;
|
||||||
onClose: NoneToVoidFunction;
|
onClose: NoneToVoidFunction;
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { withGlobal } from '../../../global';
|
|||||||
|
|
||||||
import type { ApiSticker, ApiVideo } from '../../../api/types';
|
import type { ApiSticker, ApiVideo } from '../../../api/types';
|
||||||
import type { GlobalActions } from '../../../global';
|
import type { GlobalActions } from '../../../global';
|
||||||
|
import type { ThreadId } from '../../../types';
|
||||||
|
|
||||||
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
|
import { requestMutation } from '../../../lib/fasterdom/fasterdom';
|
||||||
import { selectIsContextMenuTranslucent, selectTabState } from '../../../global/selectors';
|
import { selectIsContextMenuTranslucent, selectTabState } from '../../../global/selectors';
|
||||||
@ -35,7 +36,7 @@ const STICKERS_TAB_INDEX = 2;
|
|||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
canSendStickers?: boolean;
|
canSendStickers?: boolean;
|
||||||
canSendGifs?: boolean;
|
canSendGifs?: boolean;
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import React, { memo, useRef, useState } from '../../../lib/teact/teact';
|
|||||||
import { getActions } from '../../../global';
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
import type { ApiSticker, ApiVideo } from '../../../api/types';
|
import type { ApiSticker, ApiVideo } from '../../../api/types';
|
||||||
import type { IAnchorPosition } from '../../../types';
|
import type { IAnchorPosition, ThreadId } from '../../../types';
|
||||||
|
|
||||||
import { EDITABLE_INPUT_CSS_SELECTOR, EDITABLE_INPUT_MODAL_CSS_SELECTOR } from '../../../config';
|
import { EDITABLE_INPUT_CSS_SELECTOR, EDITABLE_INPUT_MODAL_CSS_SELECTOR } from '../../../config';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
@ -21,7 +21,7 @@ const MOBILE_KEYBOARD_HIDE_DELAY_MS = 100;
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
isMobile?: boolean;
|
isMobile?: boolean;
|
||||||
isReady?: boolean;
|
isReady?: boolean;
|
||||||
isSymbolMenuOpen?: boolean;
|
isSymbolMenuOpen?: boolean;
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { getActions, withGlobal } from '../../../global';
|
|||||||
import type {
|
import type {
|
||||||
ApiFormattedText, ApiMessage, ApiMessageEntityTextUrl, ApiWebPage,
|
ApiFormattedText, ApiMessage, ApiMessageEntityTextUrl, ApiWebPage,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import type { ISettings } from '../../../types';
|
import type { ISettings, ThreadId } from '../../../types';
|
||||||
import type { Signal } from '../../../util/signals';
|
import type { Signal } from '../../../util/signals';
|
||||||
import { ApiMessageEntityTypes } from '../../../api/types';
|
import { ApiMessageEntityTypes } from '../../../api/types';
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ import './WebPagePreview.scss';
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId: number;
|
threadId: ThreadId;
|
||||||
getHtml: Signal<string>;
|
getHtml: Signal<string>;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { getActions } from '../../../../global';
|
|||||||
|
|
||||||
import type { ApiMessage } from '../../../../api/types';
|
import type { ApiMessage } from '../../../../api/types';
|
||||||
import type { ApiDraft } from '../../../../global/types';
|
import type { ApiDraft } from '../../../../global/types';
|
||||||
|
import type { ThreadId } from '../../../../types';
|
||||||
import type { Signal } from '../../../../util/signals';
|
import type { Signal } from '../../../../util/signals';
|
||||||
import { ApiMessageEntityTypes } from '../../../../api/types';
|
import { ApiMessageEntityTypes } from '../../../../api/types';
|
||||||
|
|
||||||
@ -43,7 +44,7 @@ const useDraft = ({
|
|||||||
} : {
|
} : {
|
||||||
draft?: ApiDraft;
|
draft?: ApiDraft;
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId: number;
|
threadId: ThreadId;
|
||||||
getHtml: Signal<string>;
|
getHtml: Signal<string>;
|
||||||
setHtml: (html: string) => void;
|
setHtml: (html: string) => void;
|
||||||
editedMessage?: ApiMessage;
|
editedMessage?: ApiMessage;
|
||||||
@ -68,7 +69,7 @@ const useDraft = ({
|
|||||||
|
|
||||||
const isEditing = Boolean(editedMessage);
|
const isEditing = Boolean(editedMessage);
|
||||||
|
|
||||||
const updateDraft = useLastCallback((prevState: { chatId?: string; threadId?: number } = {}) => {
|
const updateDraft = useLastCallback((prevState: { chatId?: string; threadId?: ThreadId } = {}) => {
|
||||||
if (isDisabled || isEditing || !isTouchedRef.current) return;
|
if (isDisabled || isEditing || !isTouchedRef.current) return;
|
||||||
|
|
||||||
const html = getHtml();
|
const html = getHtml();
|
||||||
|
|||||||
@ -3,6 +3,7 @@ import { getActions } from '../../../../global';
|
|||||||
|
|
||||||
import type { ApiFormattedText, ApiMessage } from '../../../../api/types';
|
import type { ApiFormattedText, ApiMessage } from '../../../../api/types';
|
||||||
import type { ApiDraft, MessageListType } from '../../../../global/types';
|
import type { ApiDraft, MessageListType } from '../../../../global/types';
|
||||||
|
import type { ThreadId } from '../../../../types';
|
||||||
import type { Signal } from '../../../../util/signals';
|
import type { Signal } from '../../../../util/signals';
|
||||||
import { ApiMessageEntityTypes } from '../../../../api/types';
|
import { ApiMessageEntityTypes } from '../../../../api/types';
|
||||||
|
|
||||||
@ -30,7 +31,7 @@ const useEditing = (
|
|||||||
resetComposer: (shouldPreserveInput?: boolean) => void,
|
resetComposer: (shouldPreserveInput?: boolean) => void,
|
||||||
openDeleteModal: () => void,
|
openDeleteModal: () => void,
|
||||||
chatId: string,
|
chatId: string,
|
||||||
threadId: number,
|
threadId: ThreadId,
|
||||||
type: MessageListType,
|
type: MessageListType,
|
||||||
draft?: ApiDraft,
|
draft?: ApiDraft,
|
||||||
editingDraft?: ApiFormattedText,
|
editingDraft?: ApiFormattedText,
|
||||||
|
|||||||
@ -85,7 +85,7 @@ export function groupMessages(
|
|||||||
|| (lastSenderGroupItem
|
|| (lastSenderGroupItem
|
||||||
&& 'mainMessage' in lastSenderGroupItem && lastSenderGroupItem.mainMessage?.id === topMessageId))
|
&& 'mainMessage' in lastSenderGroupItem && lastSenderGroupItem.mainMessage?.id === topMessageId))
|
||||||
&& nextMessage.id !== topMessageId)
|
&& nextMessage.id !== topMessageId)
|
||||||
|| (isChatWithSelf && message.forwardInfo?.senderUserId !== nextMessage.forwardInfo?.senderUserId)
|
|| (isChatWithSelf && message.forwardInfo?.fromId !== nextMessage.forwardInfo?.fromId)
|
||||||
) {
|
) {
|
||||||
currentSenderGroup = [];
|
currentSenderGroup = [];
|
||||||
currentDateGroup.senderGroups.push(currentSenderGroup);
|
currentDateGroup.senderGroups.push(currentSenderGroup);
|
||||||
|
|||||||
@ -1,6 +1,8 @@
|
|||||||
import { useEffect, useRef } from '../../../lib/teact/teact';
|
import { useEffect, useRef } from '../../../lib/teact/teact';
|
||||||
import { getGlobal } from '../../../global';
|
import { getGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { ThreadId } from '../../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
selectFocusedMessageId,
|
selectFocusedMessageId,
|
||||||
selectListedIds,
|
selectListedIds,
|
||||||
@ -24,7 +26,7 @@ type PinnedIntersectionChangedParams = {
|
|||||||
export type PinnedIntersectionChangedCallback = (params: PinnedIntersectionChangedParams) => void;
|
export type PinnedIntersectionChangedCallback = (params: PinnedIntersectionChangedParams) => void;
|
||||||
|
|
||||||
export default function usePinnedMessage(
|
export default function usePinnedMessage(
|
||||||
chatId?: string, threadId?: number, pinnedIds?: number[], topMessageId?: number,
|
chatId?: string, threadId?: ThreadId, pinnedIds?: number[], topMessageId?: number,
|
||||||
) {
|
) {
|
||||||
const [getCurrentPinnedIndexes, setCurrentPinnedIndexes] = useSignal<Record<string, number>>({});
|
const [getCurrentPinnedIndexes, setCurrentPinnedIndexes] = useSignal<Record<string, number>>({});
|
||||||
const [getForceNextPinnedInHeader, setForceNextPinnedInHeader] = useSignal<boolean | undefined>();
|
const [getForceNextPinnedInHeader, setForceNextPinnedInHeader] = useSignal<boolean | undefined>();
|
||||||
|
|||||||
@ -23,7 +23,9 @@ import type {
|
|||||||
MessageListType,
|
MessageListType,
|
||||||
} from '../../../global/types';
|
} from '../../../global/types';
|
||||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||||
import type { FocusDirection, IAlbum, ISettings } from '../../../types';
|
import type {
|
||||||
|
FocusDirection, IAlbum, ISettings, ThreadId,
|
||||||
|
} from '../../../types';
|
||||||
import type { Signal } from '../../../util/signals';
|
import type { Signal } from '../../../util/signals';
|
||||||
import type { PinnedIntersectionChangedCallback } from '../hooks/usePinnedMessage';
|
import type { PinnedIntersectionChangedCallback } from '../hooks/usePinnedMessage';
|
||||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||||
@ -32,6 +34,7 @@ import { AudioOrigin } from '../../../types';
|
|||||||
import { EMOJI_STATUS_LOOP_LIMIT, GENERAL_TOPIC_ID } from '../../../config';
|
import { EMOJI_STATUS_LOOP_LIMIT, GENERAL_TOPIC_ID } from '../../../config';
|
||||||
import {
|
import {
|
||||||
areReactionsEmpty,
|
areReactionsEmpty,
|
||||||
|
getIsSavedDialog,
|
||||||
getMessageContent,
|
getMessageContent,
|
||||||
getMessageCustomShape,
|
getMessageCustomShape,
|
||||||
getMessageHtmlId,
|
getMessageHtmlId,
|
||||||
@ -41,6 +44,7 @@ import {
|
|||||||
getSenderTitle,
|
getSenderTitle,
|
||||||
hasMessageText,
|
hasMessageText,
|
||||||
hasMessageTtl,
|
hasMessageTtl,
|
||||||
|
isAnonymousForwardsChat,
|
||||||
isAnonymousOwnMessage,
|
isAnonymousOwnMessage,
|
||||||
isChatChannel,
|
isChatChannel,
|
||||||
isChatGroup,
|
isChatGroup,
|
||||||
@ -191,7 +195,7 @@ type OwnProps =
|
|||||||
noAvatars?: boolean;
|
noAvatars?: boolean;
|
||||||
withAvatar?: boolean;
|
withAvatar?: boolean;
|
||||||
withSenderName?: boolean;
|
withSenderName?: boolean;
|
||||||
threadId: number;
|
threadId: ThreadId;
|
||||||
messageListType: MessageListType;
|
messageListType: MessageListType;
|
||||||
noComments: boolean;
|
noComments: boolean;
|
||||||
noReplies: boolean;
|
noReplies: boolean;
|
||||||
@ -232,6 +236,7 @@ type StateProps = {
|
|||||||
isForwarding?: boolean;
|
isForwarding?: boolean;
|
||||||
isChatWithSelf?: boolean;
|
isChatWithSelf?: boolean;
|
||||||
isRepliesChat?: boolean;
|
isRepliesChat?: boolean;
|
||||||
|
isAnonymousForwards?: boolean;
|
||||||
isChannel?: boolean;
|
isChannel?: boolean;
|
||||||
isGroup?: boolean;
|
isGroup?: boolean;
|
||||||
canReply?: boolean;
|
canReply?: boolean;
|
||||||
@ -243,7 +248,7 @@ type StateProps = {
|
|||||||
isSelected?: boolean;
|
isSelected?: boolean;
|
||||||
isGroupSelected?: boolean;
|
isGroupSelected?: boolean;
|
||||||
isDownloading?: boolean;
|
isDownloading?: boolean;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
isPinnedList?: boolean;
|
isPinnedList?: boolean;
|
||||||
isPinned?: boolean;
|
isPinned?: boolean;
|
||||||
canAutoLoadMedia?: boolean;
|
canAutoLoadMedia?: boolean;
|
||||||
@ -274,6 +279,7 @@ type StateProps = {
|
|||||||
isConnected: boolean;
|
isConnected: boolean;
|
||||||
isLoadingComments?: boolean;
|
isLoadingComments?: boolean;
|
||||||
shouldWarnAboutSvg?: boolean;
|
shouldWarnAboutSvg?: boolean;
|
||||||
|
isInSavedDialog?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type MetaPosition =
|
type MetaPosition =
|
||||||
@ -347,6 +353,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
isForwarding,
|
isForwarding,
|
||||||
isChatWithSelf,
|
isChatWithSelf,
|
||||||
isRepliesChat,
|
isRepliesChat,
|
||||||
|
isAnonymousForwards,
|
||||||
isChannel,
|
isChannel,
|
||||||
isGroup,
|
isGroup,
|
||||||
canReply,
|
canReply,
|
||||||
@ -387,6 +394,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
isConnected,
|
isConnected,
|
||||||
getIsMessageListReady,
|
getIsMessageListReady,
|
||||||
shouldWarnAboutSvg,
|
shouldWarnAboutSvg,
|
||||||
|
isInSavedDialog,
|
||||||
onPinnedIntersectionChange,
|
onPinnedIntersectionChange,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
@ -483,6 +491,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
forwardInfo
|
forwardInfo
|
||||||
&& (!isChatWithSelf || isScheduled)
|
&& (!isChatWithSelf || isScheduled)
|
||||||
&& !isRepliesChat
|
&& !isRepliesChat
|
||||||
|
&& !isAnonymousForwards
|
||||||
&& !forwardInfo.isLinkedChannelPost
|
&& !forwardInfo.isLinkedChannelPost
|
||||||
&& !isCustomShape
|
&& !isCustomShape
|
||||||
) || Boolean(message.content.storyData && !message.content.storyData.isMention);
|
) || Boolean(message.content.storyData && !message.content.storyData.isMention);
|
||||||
@ -500,7 +509,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
const canForward = isChannel && !isScheduled && message.isForwardingAllowed && !isChatProtected;
|
const canForward = isChannel && !isScheduled && message.isForwardingAllowed && !isChatProtected;
|
||||||
const canFocus = Boolean(isPinnedList
|
const canFocus = Boolean(isPinnedList
|
||||||
|| (forwardInfo
|
|| (forwardInfo
|
||||||
&& (forwardInfo.isChannelPost || (isChatWithSelf && !isOwn) || isRepliesChat)
|
&& (forwardInfo.isChannelPost || (isChatWithSelf && !isOwn) || isRepliesChat || isAnonymousForwards)
|
||||||
&& forwardInfo.fromMessageId
|
&& forwardInfo.fromMessageId
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -520,7 +529,8 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
const messageSender = canShowSender ? sender : undefined;
|
const messageSender = canShowSender ? sender : undefined;
|
||||||
const withVoiceTranscription = Boolean(!isTranscriptionHidden && (isTranscriptionError || transcribedText));
|
const withVoiceTranscription = Boolean(!isTranscriptionHidden && (isTranscriptionError || transcribedText));
|
||||||
|
|
||||||
const shouldPreferOriginSender = forwardInfo && (isChatWithSelf || isRepliesChat || !messageSender);
|
const shouldPreferOriginSender = forwardInfo
|
||||||
|
&& (isChatWithSelf || isRepliesChat || isAnonymousForwards || !messageSender);
|
||||||
const avatarPeer = shouldPreferOriginSender ? originSender : messageSender;
|
const avatarPeer = shouldPreferOriginSender ? originSender : messageSender;
|
||||||
const messageColorPeer = originSender || sender;
|
const messageColorPeer = originSender || sender;
|
||||||
const senderPeer = (forwardInfo || message.content.storyData) ? originSender : messageSender;
|
const senderPeer = (forwardInfo || message.content.storyData) ? originSender : messageSender;
|
||||||
@ -914,6 +924,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
<MessageMeta
|
<MessageMeta
|
||||||
message={message}
|
message={message}
|
||||||
isPinned={isPinned}
|
isPinned={isPinned}
|
||||||
|
isInSavedDialog={isInSavedDialog}
|
||||||
noReplies={noReplies}
|
noReplies={noReplies}
|
||||||
repliesThreadInfo={repliesThreadInfo}
|
repliesThreadInfo={repliesThreadInfo}
|
||||||
outgoingStatus={outgoingStatus}
|
outgoingStatus={outgoingStatus}
|
||||||
@ -1461,6 +1472,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
const isChatWithSelf = selectIsChatWithSelf(global, chatId);
|
const isChatWithSelf = selectIsChatWithSelf(global, chatId);
|
||||||
const isRepliesChat = isChatWithRepliesBot(chatId);
|
const isRepliesChat = isChatWithRepliesBot(chatId);
|
||||||
|
const isAnonymousForwards = isAnonymousForwardsChat(chatId);
|
||||||
const isChannel = chat && isChatChannel(chat);
|
const isChannel = chat && isChatChannel(chat);
|
||||||
const isGroup = chat && isChatGroup(chat);
|
const isGroup = chat && isChatGroup(chat);
|
||||||
const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined;
|
const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined;
|
||||||
@ -1487,11 +1499,12 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const shouldHideReply = replyToMsgId && replyToMsgId === threadId;
|
const shouldHideReply = replyToMsgId && replyToMsgId === threadId;
|
||||||
const replyMessage = replyToMsgId ? selectChatMessage(global, replyToPeerId || chatId, replyToMsgId) : undefined;
|
const replyMessage = replyToMsgId ? 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)
|
||||||
? selectSenderFromHeader(global, forwardHeader) : undefined;
|
: forwardHeader && !isRepliesChat && !isAnonymousForwards
|
||||||
|
? selectSenderFromHeader(global, forwardHeader) : undefined;
|
||||||
const replyMessageForwardSender = replyMessage && selectForwardedSender(global, replyMessage);
|
const replyMessageForwardSender = replyMessage && selectForwardedSender(global, replyMessage);
|
||||||
const replyMessageChat = replyToPeerId ? selectChat(global, replyToPeerId) : undefined;
|
const replyMessageChat = replyToPeerId ? selectChat(global, replyToPeerId) : undefined;
|
||||||
const isReplyPrivate = !isRepliesChat && replyMessageChat && !isChatPublic(replyMessageChat)
|
const isReplyPrivate = !isRepliesChat && !isAnonymousForwards && replyMessageChat && !isChatPublic(replyMessageChat)
|
||||||
&& (replyMessageChat.isNotJoined || replyMessageChat.isRestricted);
|
&& (replyMessageChat.isNotJoined || replyMessageChat.isRestricted);
|
||||||
const isReplyToTopicStart = replyMessage?.content.action?.type === 'topicCreate';
|
const isReplyToTopicStart = replyMessage?.content.action?.type === 'topicCreate';
|
||||||
const replyStory = storyReplyId && storyReplyUserId
|
const replyStory = storyReplyId && storyReplyUserId
|
||||||
@ -1554,6 +1567,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const hasActiveReactions = Boolean(reactionMessage && activeReactions[getMessageKey(reactionMessage)]?.length);
|
const hasActiveReactions = Boolean(reactionMessage && activeReactions[getMessageKey(reactionMessage)]?.length);
|
||||||
|
|
||||||
|
const isInSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
theme: selectTheme(global),
|
theme: selectTheme(global),
|
||||||
forceSenderName,
|
forceSenderName,
|
||||||
@ -1578,6 +1593,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
reactionMessage,
|
reactionMessage,
|
||||||
isChatWithSelf,
|
isChatWithSelf,
|
||||||
isRepliesChat,
|
isRepliesChat,
|
||||||
|
isAnonymousForwards,
|
||||||
isChannel,
|
isChannel,
|
||||||
isGroup,
|
isGroup,
|
||||||
canReply,
|
canReply,
|
||||||
@ -1621,6 +1637,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
withStickerEffects: selectPerformanceSettingsValue(global, 'stickerEffects'),
|
withStickerEffects: selectPerformanceSettingsValue(global, 'stickerEffects'),
|
||||||
webPageStory,
|
webPageStory,
|
||||||
isConnected,
|
isConnected,
|
||||||
|
isInSavedDialog,
|
||||||
isLoadingComments: repliesThreadInfo?.isCommentsInfo
|
isLoadingComments: repliesThreadInfo?.isCommentsInfo
|
||||||
&& loadingThread?.loadingChatId === repliesThreadInfo?.originChannelId
|
&& loadingThread?.loadingChatId === repliesThreadInfo?.originChannelId
|
||||||
&& loadingThread?.loadingMessageId === repliesThreadInfo?.originMessageId,
|
&& loadingThread?.loadingMessageId === repliesThreadInfo?.originMessageId,
|
||||||
|
|||||||
@ -29,6 +29,7 @@ type OwnProps = {
|
|||||||
repliesThreadInfo?: ApiThreadInfo;
|
repliesThreadInfo?: ApiThreadInfo;
|
||||||
isTranslated?: boolean;
|
isTranslated?: boolean;
|
||||||
isPinned?: boolean;
|
isPinned?: boolean;
|
||||||
|
isInSavedDialog?: boolean;
|
||||||
onClick: (e: React.MouseEvent<HTMLDivElement>) => void;
|
onClick: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||||
onTranslationClick: (e: React.MouseEvent<HTMLDivElement>) => void;
|
onTranslationClick: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||||
renderQuickReactionButton?: () => TeactNode | undefined;
|
renderQuickReactionButton?: () => TeactNode | undefined;
|
||||||
@ -45,6 +46,7 @@ const MessageMeta: FC<OwnProps> = ({
|
|||||||
noReplies,
|
noReplies,
|
||||||
isTranslated,
|
isTranslated,
|
||||||
isPinned,
|
isPinned,
|
||||||
|
isInSavedDialog,
|
||||||
onClick,
|
onClick,
|
||||||
onTranslationClick,
|
onTranslationClick,
|
||||||
onOpenThread,
|
onOpenThread,
|
||||||
@ -72,7 +74,12 @@ const MessageMeta: FC<OwnProps> = ({
|
|||||||
const editDateTime = message.isEdited
|
const editDateTime = message.isEdited
|
||||||
&& formatDateTimeToString(message.editDate! * 1000, lang.code, undefined, lang.timeFormat);
|
&& formatDateTimeToString(message.editDate! * 1000, lang.code, undefined, lang.timeFormat);
|
||||||
const forwardedDateTime = message.forwardInfo
|
const forwardedDateTime = message.forwardInfo
|
||||||
&& formatDateTimeToString(message.forwardInfo.date * 1000, lang.code, undefined, lang.timeFormat);
|
&& formatDateTimeToString(
|
||||||
|
(message.forwardInfo.savedDate || message.forwardInfo.date) * 1000,
|
||||||
|
lang.code,
|
||||||
|
undefined,
|
||||||
|
lang.timeFormat,
|
||||||
|
);
|
||||||
|
|
||||||
let text = createDateTime;
|
let text = createDateTime;
|
||||||
if (editDateTime) {
|
if (editDateTime) {
|
||||||
@ -137,7 +144,9 @@ const MessageMeta: FC<OwnProps> = ({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{message.isEdited && `${lang('EditedMessage')} `}
|
{message.isEdited && `${lang('EditedMessage')} `}
|
||||||
{formatTime(lang, message.date * 1000)}
|
{isInSavedDialog
|
||||||
|
? formatDateTimeToString((message.forwardInfo?.date || message.date) * 1000, lang.code, true)
|
||||||
|
: formatTime(lang, message.date * 1000)}
|
||||||
</span>
|
</span>
|
||||||
{outgoingStatus && (
|
{outgoingStatus && (
|
||||||
<MessageOutgoingStatus status={outgoingStatus} />
|
<MessageOutgoingStatus status={outgoingStatus} />
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import type {
|
|||||||
ApiMessage, ApiPeer, ApiStory, ApiTopic, ApiUser,
|
ApiMessage, ApiPeer, ApiStory, ApiTopic, ApiUser,
|
||||||
} from '../../../../api/types';
|
} from '../../../../api/types';
|
||||||
import type { LangFn } from '../../../../hooks/useLang';
|
import type { LangFn } from '../../../../hooks/useLang';
|
||||||
import type { IAlbum } from '../../../../types';
|
import type { IAlbum, ThreadId } from '../../../../types';
|
||||||
import { MAIN_THREAD_ID } from '../../../../api/types';
|
import { MAIN_THREAD_ID } from '../../../../api/types';
|
||||||
import { MediaViewerOrigin } from '../../../../types';
|
import { MediaViewerOrigin } from '../../../../types';
|
||||||
|
|
||||||
@ -18,7 +18,7 @@ export default function useInnerHandlers(
|
|||||||
selectMessage: (e: React.MouseEvent<HTMLDivElement, MouseEvent>, groupedId?: string) => void,
|
selectMessage: (e: React.MouseEvent<HTMLDivElement, MouseEvent>, groupedId?: string) => void,
|
||||||
message: ApiMessage,
|
message: ApiMessage,
|
||||||
chatId: string,
|
chatId: string,
|
||||||
threadId: number,
|
threadId: ThreadId,
|
||||||
isInDocumentGroup: boolean,
|
isInDocumentGroup: boolean,
|
||||||
asForwarded?: boolean,
|
asForwarded?: boolean,
|
||||||
isScheduled?: boolean,
|
isScheduled?: boolean,
|
||||||
|
|||||||
@ -5,15 +5,16 @@ import React, {
|
|||||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiChat, ApiChatMember,
|
ApiChatMember,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import { NewChatMembersProgress } from '../../types';
|
import { NewChatMembersProgress } from '../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
filterUsersByName, isChatChannel, isUserBot, sortChatIds,
|
filterUsersByName, isChatChannel, isUserBot,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import { selectChat, selectChatFullInfo, selectTabState } from '../../global/selectors';
|
import { selectChat, selectChatFullInfo, selectTabState } from '../../global/selectors';
|
||||||
import { unique } from '../../util/iteratees';
|
import { unique } from '../../util/iteratees';
|
||||||
|
import sortChatIds from '../common/helpers/sortChatIds';
|
||||||
|
|
||||||
import useHistoryBack from '../../hooks/useHistoryBack';
|
import useHistoryBack from '../../hooks/useHistoryBack';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
@ -36,7 +37,6 @@ type StateProps = {
|
|||||||
isChannel?: boolean;
|
isChannel?: boolean;
|
||||||
members?: ApiChatMember[];
|
members?: ApiChatMember[];
|
||||||
currentUserId?: string;
|
currentUserId?: string;
|
||||||
chatsById: Record<string, ApiChat>;
|
|
||||||
localContactIds?: string[];
|
localContactIds?: string[];
|
||||||
searchQuery?: string;
|
searchQuery?: string;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
@ -50,7 +50,6 @@ const AddChatMembers: FC<OwnProps & StateProps> = ({
|
|||||||
members,
|
members,
|
||||||
onNextStep,
|
onNextStep,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
chatsById,
|
|
||||||
localContactIds,
|
localContactIds,
|
||||||
isLoading,
|
isLoading,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
@ -104,11 +103,8 @@ const AddChatMembers: FC<OwnProps & StateProps> = ({
|
|||||||
&& (!user || !isUserBot(user) || (!isChannel && user.canBeInvitedToGroup))
|
&& (!user || !isUserBot(user) || (!isChannel && user.canBeInvitedToGroup))
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
chatsById,
|
|
||||||
);
|
);
|
||||||
}, [
|
}, [localContactIds, searchQuery, localUserIds, globalUserIds, currentUserId, memberIds, isChannel]);
|
||||||
localContactIds, chatsById, searchQuery, localUserIds, globalUserIds, currentUserId, memberIds, isChannel,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleNextStep = useCallback(() => {
|
const handleNextStep = useCallback(() => {
|
||||||
if (selectedMemberIds.length) {
|
if (selectedMemberIds.length) {
|
||||||
@ -154,7 +150,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
(global, { chatId }): StateProps => {
|
(global, { chatId }): StateProps => {
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
const { userIds: localContactIds } = global.contactList || {};
|
const { userIds: localContactIds } = global.contactList || {};
|
||||||
const { byId: chatsById } = global.chats;
|
|
||||||
const { newChatMembersProgress } = selectTabState(global);
|
const { newChatMembersProgress } = selectTabState(global);
|
||||||
const { currentUserId } = global;
|
const { currentUserId } = global;
|
||||||
const isChannel = chat && isChatChannel(chat);
|
const isChannel = chat && isChatChannel(chat);
|
||||||
@ -170,7 +165,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isChannel,
|
isChannel,
|
||||||
members: selectChatFullInfo(global, chatId)?.members,
|
members: selectChatFullInfo(global, chatId)?.members,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
chatsById,
|
|
||||||
localContactIds,
|
localContactIds,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
isSearching: fetchingStatus,
|
isSearching: fetchingStatus,
|
||||||
|
|||||||
@ -63,6 +63,10 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.saved-dialogs {
|
||||||
|
height: 100% !important;
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
&.empty-list {
|
&.empty-list {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import type {
|
|||||||
ApiUserStatus,
|
ApiUserStatus,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type {
|
import type {
|
||||||
ISettings, ProfileState, ProfileTabType, SharedMediaType,
|
ISettings, ProfileState, ProfileTabType, SharedMediaType, ThreadId,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import { MAIN_THREAD_ID } from '../../api/types';
|
import { MAIN_THREAD_ID } from '../../api/types';
|
||||||
import { AudioOrigin, MediaViewerOrigin, NewChatMembersProgress } from '../../types';
|
import { AudioOrigin, MediaViewerOrigin, NewChatMembersProgress } from '../../types';
|
||||||
@ -26,7 +26,7 @@ import {
|
|||||||
SLIDE_TRANSITION_DURATION,
|
SLIDE_TRANSITION_DURATION,
|
||||||
} from '../../config';
|
} from '../../config';
|
||||||
import {
|
import {
|
||||||
getHasAdminRight, isChatAdmin, isChatChannel, isChatGroup, isUserBot, isUserId, isUserRightBanned,
|
getHasAdminRight, getIsSavedDialog, isChatAdmin, isChatChannel, isChatGroup, isUserBot, isUserId, isUserRightBanned,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import {
|
import {
|
||||||
selectActiveDownloads,
|
selectActiveDownloads,
|
||||||
@ -70,6 +70,7 @@ import NothingFound from '../common/NothingFound';
|
|||||||
import PrivateChatInfo from '../common/PrivateChatInfo';
|
import PrivateChatInfo from '../common/PrivateChatInfo';
|
||||||
import ProfileInfo from '../common/ProfileInfo';
|
import ProfileInfo from '../common/ProfileInfo';
|
||||||
import WebLink from '../common/WebLink';
|
import WebLink from '../common/WebLink';
|
||||||
|
import ChatList from '../left/main/ChatList';
|
||||||
import MediaStory from '../story/MediaStory';
|
import MediaStory from '../story/MediaStory';
|
||||||
import Button from '../ui/Button';
|
import Button from '../ui/Button';
|
||||||
import FloatingActionButton from '../ui/FloatingActionButton';
|
import FloatingActionButton from '../ui/FloatingActionButton';
|
||||||
@ -84,7 +85,7 @@ import './Profile.scss';
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
topicId?: number;
|
threadId?: ThreadId;
|
||||||
profileState: ProfileState;
|
profileState: ProfileState;
|
||||||
isMobile?: boolean;
|
isMobile?: boolean;
|
||||||
onProfileStateChange: (state: ProfileState) => void;
|
onProfileStateChange: (state: ProfileState) => void;
|
||||||
@ -122,9 +123,16 @@ type StateProps = {
|
|||||||
similarChannels?: string[];
|
similarChannels?: string[];
|
||||||
isCurrentUserPremium?: boolean;
|
isCurrentUserPremium?: boolean;
|
||||||
limitSimilarChannels: number;
|
limitSimilarChannels: number;
|
||||||
|
isTopicInfo?: boolean;
|
||||||
|
isSavedDialog?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TABS = [
|
type TabProps = {
|
||||||
|
type: ProfileTabType;
|
||||||
|
title: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const TABS: TabProps[] = [
|
||||||
{ type: 'media', title: 'SharedMediaTab2' },
|
{ type: 'media', title: 'SharedMediaTab2' },
|
||||||
{ type: 'documents', title: 'SharedFilesTab2' },
|
{ type: 'documents', title: 'SharedFilesTab2' },
|
||||||
{ type: 'links', title: 'SharedLinksTab2' },
|
{ type: 'links', title: 'SharedLinksTab2' },
|
||||||
@ -136,7 +144,7 @@ const INTERSECTION_THROTTLE = 500;
|
|||||||
|
|
||||||
const Profile: FC<OwnProps & StateProps> = ({
|
const Profile: FC<OwnProps & StateProps> = ({
|
||||||
chatId,
|
chatId,
|
||||||
topicId,
|
threadId,
|
||||||
profileState,
|
profileState,
|
||||||
onProfileStateChange,
|
onProfileStateChange,
|
||||||
theme,
|
theme,
|
||||||
@ -170,6 +178,8 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
similarChannels,
|
similarChannels,
|
||||||
isCurrentUserPremium,
|
isCurrentUserPremium,
|
||||||
limitSimilarChannels,
|
limitSimilarChannels,
|
||||||
|
isTopicInfo,
|
||||||
|
isSavedDialog,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
setLocalMediaSearchType,
|
setLocalMediaSearchType,
|
||||||
@ -195,29 +205,33 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
const [deletingUserId, setDeletingUserId] = useState<string | undefined>();
|
const [deletingUserId, setDeletingUserId] = useState<string | undefined>();
|
||||||
|
|
||||||
|
const profileId = isSavedDialog ? String(threadId) : (resolvedUserId || chatId);
|
||||||
|
const isSavedMessages = profileId === currentUserId && !isSavedDialog;
|
||||||
|
|
||||||
const tabs = useMemo(() => ([
|
const tabs = useMemo(() => ([
|
||||||
...(hasStoriesTab ? [{ type: 'stories', title: 'ProfileStories' }] : []),
|
...(isSavedMessages && !isSavedDialog ? [{ type: 'dialogs' as const, title: 'SavedDialogsTab' }] : []),
|
||||||
...(hasStoriesTab && currentUserId === chatId ? [{ type: 'storiesArchive', title: 'ProfileStoriesArchive' }] : []),
|
...(hasStoriesTab ? [{ type: 'stories' as const, title: 'ProfileStories' }] : []),
|
||||||
|
...(hasStoriesTab && isSavedMessages ? [{ type: 'storiesArchive' as const, title: 'ProfileStoriesArchive' }] : []),
|
||||||
...(hasMembersTab ? [{
|
...(hasMembersTab ? [{
|
||||||
type: 'members', title: isChannel ? 'ChannelSubscribers' : 'GroupMembers',
|
type: 'members' as const, title: isChannel ? 'ChannelSubscribers' : 'GroupMembers',
|
||||||
}] : []),
|
}] : []),
|
||||||
...TABS,
|
...TABS,
|
||||||
// TODO The filter for voice messages currently does not work
|
// TODO The filter for voice messages currently does not work
|
||||||
// in forum topics. Return it when it's fixed on the server side.
|
// in forum topics. Return it when it's fixed on the server side.
|
||||||
...(!topicId ? [{ type: 'voice', title: 'SharedVoiceTab2' }] : []),
|
...(!isTopicInfo ? [{ type: 'voice' as const, title: 'SharedVoiceTab2' }] : []),
|
||||||
...(hasCommonChatsTab ? [{ type: 'commonChats', title: 'SharedGroupsTab2' }] : []),
|
...(hasCommonChatsTab ? [{ type: 'commonChats' as const, title: 'SharedGroupsTab2' }] : []),
|
||||||
...(isChannel && similarChannels?.length
|
...(isChannel && similarChannels?.length
|
||||||
? [{ type: 'similarChannels', title: 'SimilarChannelsTab' }]
|
? [{ type: 'similarChannels' as const, title: 'SimilarChannelsTab' }]
|
||||||
: []),
|
: []),
|
||||||
]), [
|
]), [
|
||||||
chatId,
|
|
||||||
currentUserId,
|
|
||||||
hasCommonChatsTab,
|
hasCommonChatsTab,
|
||||||
hasMembersTab,
|
hasMembersTab,
|
||||||
hasStoriesTab,
|
hasStoriesTab,
|
||||||
isChannel,
|
isChannel,
|
||||||
topicId,
|
isTopicInfo,
|
||||||
similarChannels,
|
similarChannels,
|
||||||
|
isSavedMessages,
|
||||||
|
isSavedDialog,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const initialTab = useMemo(() => {
|
const initialTab = useMemo(() => {
|
||||||
@ -269,12 +283,13 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
chatsById,
|
chatsById,
|
||||||
messagesById,
|
messagesById,
|
||||||
foundIds,
|
foundIds,
|
||||||
topicId,
|
threadId,
|
||||||
storyIds,
|
storyIds,
|
||||||
archiveStoryIds,
|
archiveStoryIds,
|
||||||
similarChannels,
|
similarChannels,
|
||||||
);
|
);
|
||||||
const isFirstTab = (hasStoriesTab && resultType === 'stories')
|
const isFirstTab = (isSavedMessages && resultType === 'dialogs')
|
||||||
|
|| (hasStoriesTab && resultType === 'stories')
|
||||||
|| resultType === 'members'
|
|| resultType === 'members'
|
||||||
|| (!hasMembersTab && resultType === 'media');
|
|| (!hasMembersTab && resultType === 'media');
|
||||||
const activeKey = tabs.findIndex(({ type }) => type === resultType);
|
const activeKey = tabs.findIndex(({ type }) => type === resultType);
|
||||||
@ -304,9 +319,7 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
// Update search type when switching tabs or forum topics
|
// Update search type when switching tabs or forum topics
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLocalMediaSearchType({ mediaType: tabType as SharedMediaType });
|
setLocalMediaSearchType({ mediaType: tabType as SharedMediaType });
|
||||||
}, [setLocalMediaSearchType, tabType, topicId]);
|
}, [setLocalMediaSearchType, tabType, threadId]);
|
||||||
|
|
||||||
const profileId = resolvedUserId || chatId;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadProfilePhotos({ profileId });
|
loadProfilePhotos({ profileId });
|
||||||
@ -376,7 +389,7 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
} else if (!viewportIds) {
|
} else if (!viewportIds) {
|
||||||
renderingDelay = SLIDE_TRANSITION_DURATION;
|
renderingDelay = SLIDE_TRANSITION_DURATION;
|
||||||
}
|
}
|
||||||
const canRenderContent = useAsyncRendering([chatId, topicId, resultType, renderingActiveTab], renderingDelay);
|
const canRenderContent = useAsyncRendering([chatId, threadId, resultType, renderingActiveTab], renderingDelay);
|
||||||
|
|
||||||
function getMemberContextAction(memberId: string): MenuItemContextAction[] | undefined {
|
function getMemberContextAction(memberId: string): MenuItemContextAction[] | undefined {
|
||||||
return memberId === currentUserId || !canDeleteMembers ? undefined : [{
|
return memberId === currentUserId || !canDeleteMembers ? undefined : [{
|
||||||
@ -389,6 +402,12 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderContent() {
|
function renderContent() {
|
||||||
|
if (resultType === 'dialogs') {
|
||||||
|
return (
|
||||||
|
<ChatList className="saved-dialogs" folderType="saved" isActive />
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if (!viewportIds || !canRenderContent || !messagesById) {
|
if (!viewportIds || !canRenderContent || !messagesById) {
|
||||||
const noSpinner = isFirstTab && !canRenderContent;
|
const noSpinner = isFirstTab && !canRenderContent;
|
||||||
const forceRenderHiddenMembers = Boolean(resultType === 'members' && areMembersHidden);
|
const forceRenderHiddenMembers = Boolean(resultType === 'members' && areMembersHidden);
|
||||||
@ -594,7 +613,9 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
onLoadMore={getMore}
|
onLoadMore={getMore}
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
>
|
>
|
||||||
{!noProfileInfo && renderProfileInfo(chatId, resolvedUserId, isRightColumnShown && canRenderContent)}
|
{!noProfileInfo && !isSavedMessages && (
|
||||||
|
renderProfileInfo(profileId, isRightColumnShown && canRenderContent, isSavedDialog)
|
||||||
|
)}
|
||||||
{!isRestricted && (
|
{!isRestricted && (
|
||||||
<div
|
<div
|
||||||
className="shared-media"
|
className="shared-media"
|
||||||
@ -635,29 +656,35 @@ const Profile: FC<OwnProps & StateProps> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function renderProfileInfo(chatId: string, resolvedUserId: string | undefined, isReady: boolean) {
|
function renderProfileInfo(profileId: string, isReady: boolean, isSavedDialog?: boolean) {
|
||||||
return (
|
return (
|
||||||
<div className="profile-info">
|
<div className="profile-info">
|
||||||
<ProfileInfo userId={resolvedUserId || chatId} canPlayVideo={isReady} />
|
<ProfileInfo userId={profileId} canPlayVideo={isReady} />
|
||||||
<ChatExtra chatOrUserId={resolvedUserId || chatId} />
|
<ChatExtra chatOrUserId={profileId} isSavedDialog={isSavedDialog} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { chatId, topicId, isMobile }): StateProps => {
|
(global, {
|
||||||
|
chatId, threadId, isMobile,
|
||||||
|
}): StateProps => {
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
const chatFullInfo = selectChatFullInfo(global, chatId);
|
const chatFullInfo = selectChatFullInfo(global, chatId);
|
||||||
const messagesById = selectChatMessages(global, chatId);
|
const messagesById = selectChatMessages(global, chatId);
|
||||||
const { currentType: mediaSearchType, resultsByType } = selectCurrentMediaSearch(global) || {};
|
const { currentType: mediaSearchType, resultsByType } = selectCurrentMediaSearch(global) || {};
|
||||||
const { foundIds } = (resultsByType && mediaSearchType && resultsByType[mediaSearchType]) || {};
|
const { foundIds } = (resultsByType && mediaSearchType && resultsByType[mediaSearchType]) || {};
|
||||||
|
|
||||||
|
const isTopicInfo = Boolean(chat?.isForum && threadId && threadId !== MAIN_THREAD_ID);
|
||||||
|
|
||||||
const { byId: usersById, statusesById: userStatusesById } = global.users;
|
const { byId: usersById, statusesById: userStatusesById } = global.users;
|
||||||
const { byId: chatsById } = global.chats;
|
const { byId: chatsById } = global.chats;
|
||||||
|
|
||||||
|
const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId);
|
||||||
|
|
||||||
const isGroup = chat && isChatGroup(chat);
|
const isGroup = chat && isChatGroup(chat);
|
||||||
const isChannel = chat && isChatChannel(chat);
|
const isChannel = chat && isChatChannel(chat);
|
||||||
const hasMembersTab = !topicId && (isGroup || (isChannel && isChatAdmin(chat!)));
|
const hasMembersTab = !isTopicInfo && !isSavedDialog && (isGroup || (isChannel && isChatAdmin(chat!)));
|
||||||
const members = chatFullInfo?.members;
|
const members = chatFullInfo?.members;
|
||||||
const adminMembersById = chatFullInfo?.adminMembersById;
|
const adminMembersById = chatFullInfo?.adminMembersById;
|
||||||
const areMembersHidden = hasMembersTab && chat
|
const areMembersHidden = hasMembersTab && chat
|
||||||
@ -675,12 +702,13 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
if (isUserId(chatId)) {
|
if (isUserId(chatId)) {
|
||||||
resolvedUserId = chatId;
|
resolvedUserId = chatId;
|
||||||
user = selectUser(global, resolvedUserId);
|
user = selectUser(global, resolvedUserId);
|
||||||
hasCommonChatsTab = user && !user.isSelf && !isUserBot(user);
|
hasCommonChatsTab = user && !user.isSelf && !isUserBot(user) && !isSavedDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
const peer = user || chat;
|
const peer = user || chat;
|
||||||
const peerFullInfo = selectPeerFullInfo(global, chatId);
|
const peerFullInfo = selectPeerFullInfo(global, chatId);
|
||||||
const hasStoriesTab = peer && (user?.isSelf || (!peer.areStoriesHidden && peerFullInfo?.hasPinnedStories));
|
const hasStoriesTab = peer && (user?.isSelf || (!peer.areStoriesHidden && peerFullInfo?.hasPinnedStories))
|
||||||
|
&& !isSavedDialog;
|
||||||
const peerStories = hasStoriesTab ? selectPeerStories(global, peer.id) : undefined;
|
const peerStories = hasStoriesTab ? selectPeerStories(global, peer.id) : undefined;
|
||||||
const storyIds = peerStories?.pinnedIds;
|
const storyIds = peerStories?.pinnedIds;
|
||||||
const storyByIds = peerStories?.byId;
|
const storyByIds = peerStories?.byId;
|
||||||
@ -714,6 +742,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
shouldWarnAboutSvg: global.settings.byKey.shouldWarnAboutSvg,
|
shouldWarnAboutSvg: global.settings.byKey.shouldWarnAboutSvg,
|
||||||
similarChannels,
|
similarChannels,
|
||||||
isCurrentUserPremium,
|
isCurrentUserPremium,
|
||||||
|
isTopicInfo,
|
||||||
|
isSavedDialog,
|
||||||
limitSimilarChannels: selectPremiumLimit(global, 'recommendedChannels'),
|
limitSimilarChannels: selectPremiumLimit(global, 'recommendedChannels'),
|
||||||
...(hasMembersTab && members && { members, adminMembersById }),
|
...(hasMembersTab && members && { members, adminMembersById }),
|
||||||
...(hasCommonChatsTab && user && { commonChatIds: user.commonChats?.ids }),
|
...(hasCommonChatsTab && user && { commonChatIds: user.commonChats?.ids }),
|
||||||
|
|||||||
@ -2,15 +2,19 @@ import type { FC } from '../../lib/teact/teact';
|
|||||||
import React, { memo, useEffect, useState } from '../../lib/teact/teact';
|
import React, { memo, useEffect, useState } from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { ProfileTabType } from '../../types';
|
import type { ProfileTabType, ThreadId } from '../../types';
|
||||||
import { MAIN_THREAD_ID } from '../../api/types';
|
|
||||||
import {
|
import {
|
||||||
ManagementScreens, NewChatMembersProgress, ProfileState, RightColumnContent,
|
ManagementScreens, NewChatMembersProgress, ProfileState, RightColumnContent,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { ANIMATION_END_DELAY, MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN } from '../../config';
|
import { ANIMATION_END_DELAY, MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN } from '../../config';
|
||||||
|
import { getIsSavedDialog } from '../../global/helpers';
|
||||||
import {
|
import {
|
||||||
selectAreActiveChatsLoaded, selectChat, selectCurrentMessageList, selectRightColumnContentKey, selectTabState,
|
selectAreActiveChatsLoaded,
|
||||||
|
selectCurrentMessageList,
|
||||||
|
selectIsChatWithSelf,
|
||||||
|
selectRightColumnContentKey,
|
||||||
|
selectTabState,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../util/captureEscKeyListener';
|
||||||
|
|
||||||
@ -45,12 +49,14 @@ interface OwnProps {
|
|||||||
type StateProps = {
|
type StateProps = {
|
||||||
contentKey?: RightColumnContent;
|
contentKey?: RightColumnContent;
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
isInsideTopic?: boolean;
|
isInsideTopic?: boolean;
|
||||||
isChatSelected: boolean;
|
isChatSelected: boolean;
|
||||||
shouldSkipHistoryAnimations?: boolean;
|
shouldSkipHistoryAnimations?: boolean;
|
||||||
nextManagementScreen?: ManagementScreens;
|
nextManagementScreen?: ManagementScreens;
|
||||||
nextProfileTab?: ProfileTabType;
|
nextProfileTab?: ProfileTabType;
|
||||||
|
isSavedMessages?: boolean;
|
||||||
|
isSavedDialog?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const ANIMATION_DURATION = 450 + ANIMATION_END_DELAY;
|
const ANIMATION_DURATION = 450 + ANIMATION_END_DELAY;
|
||||||
@ -69,11 +75,12 @@ const RightColumn: FC<OwnProps & StateProps> = ({
|
|||||||
chatId,
|
chatId,
|
||||||
threadId,
|
threadId,
|
||||||
isMobile,
|
isMobile,
|
||||||
isInsideTopic,
|
|
||||||
isChatSelected,
|
isChatSelected,
|
||||||
shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations,
|
||||||
nextManagementScreen,
|
nextManagementScreen,
|
||||||
nextProfileTab,
|
nextProfileTab,
|
||||||
|
isSavedMessages,
|
||||||
|
isSavedDialog,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
toggleChatInfo,
|
toggleChatInfo,
|
||||||
@ -97,7 +104,9 @@ const RightColumn: FC<OwnProps & StateProps> = ({
|
|||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const { width: windowWidth } = useWindowSize();
|
const { width: windowWidth } = useWindowSize();
|
||||||
const [profileState, setProfileState] = useState<ProfileState>(ProfileState.Profile);
|
const [profileState, setProfileState] = useState<ProfileState>(
|
||||||
|
isSavedMessages && !isSavedDialog ? ProfileState.SavedDialogs : ProfileState.Profile,
|
||||||
|
);
|
||||||
const [managementScreen, setManagementScreen] = useState<ManagementScreens>(ManagementScreens.Initial);
|
const [managementScreen, setManagementScreen] = useState<ManagementScreens>(ManagementScreens.Initial);
|
||||||
const [selectedChatMemberId, setSelectedChatMemberId] = useState<string | undefined>();
|
const [selectedChatMemberId, setSelectedChatMemberId] = useState<string | undefined>();
|
||||||
const [isPromotedByCurrentUser, setIsPromotedByCurrentUser] = useState<boolean | undefined>();
|
const [isPromotedByCurrentUser, setIsPromotedByCurrentUser] = useState<boolean | undefined>();
|
||||||
@ -129,7 +138,7 @@ const RightColumn: FC<OwnProps & StateProps> = ({
|
|||||||
setNewChatMembersDialogState({ newChatMembersProgress: NewChatMembersProgress.Closed });
|
setNewChatMembersDialogState({ newChatMembersProgress: NewChatMembersProgress.Closed });
|
||||||
break;
|
break;
|
||||||
case RightColumnContent.ChatInfo:
|
case RightColumnContent.ChatInfo:
|
||||||
if (isScrolledDown && shouldScrollUp) {
|
if (isScrolledDown && shouldScrollUp && !isSavedMessages) {
|
||||||
setProfileState(ProfileState.Profile);
|
setProfileState(ProfileState.Profile);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -253,12 +262,14 @@ const RightColumn: FC<OwnProps & StateProps> = ({
|
|||||||
}, [isOverlaying]);
|
}, [isOverlaying]);
|
||||||
|
|
||||||
// We need to clear profile state and management screen state, when changing chats
|
// We need to clear profile state and management screen state, when changing chats
|
||||||
useLayoutEffectWithPrevDeps(([prevChatId]) => {
|
useLayoutEffectWithPrevDeps(([prevChatId, prevThreadId]) => {
|
||||||
if (prevChatId !== chatId) {
|
if (prevChatId !== chatId || prevThreadId !== threadId) {
|
||||||
setProfileState(ProfileState.Profile);
|
setProfileState(
|
||||||
|
isSavedMessages && !isSavedDialog ? ProfileState.SavedDialogs : ProfileState.Profile,
|
||||||
|
);
|
||||||
setManagementScreen(ManagementScreens.Initial);
|
setManagementScreen(ManagementScreens.Initial);
|
||||||
}
|
}
|
||||||
}, [chatId]);
|
}, [chatId, threadId, isSavedDialog, isSavedMessages]);
|
||||||
|
|
||||||
useHistoryBack({
|
useHistoryBack({
|
||||||
isActive: isChatSelected && (
|
isActive: isChatSelected && (
|
||||||
@ -289,9 +300,9 @@ const RightColumn: FC<OwnProps & StateProps> = ({
|
|||||||
case RightColumnContent.ChatInfo:
|
case RightColumnContent.ChatInfo:
|
||||||
return (
|
return (
|
||||||
<Profile
|
<Profile
|
||||||
key={`profile_${chatId!}`}
|
key={`profile_${chatId!}_${threadId}`}
|
||||||
chatId={chatId!}
|
chatId={chatId!}
|
||||||
topicId={isInsideTopic ? threadId : undefined}
|
threadId={threadId}
|
||||||
profileState={profileState}
|
profileState={profileState}
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
onProfileStateChange={setProfileState}
|
onProfileStateChange={setProfileState}
|
||||||
@ -400,18 +411,20 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const areActiveChatsLoaded = selectAreActiveChatsLoaded(global);
|
const areActiveChatsLoaded = selectAreActiveChatsLoaded(global);
|
||||||
const { management, shouldSkipHistoryAnimations, nextProfileTab } = selectTabState(global);
|
const { management, shouldSkipHistoryAnimations, nextProfileTab } = selectTabState(global);
|
||||||
const nextManagementScreen = chatId ? management.byChatId[chatId]?.nextScreen : undefined;
|
const nextManagementScreen = chatId ? management.byChatId[chatId]?.nextScreen : undefined;
|
||||||
const isForum = chatId ? selectChat(global, chatId)?.isForum : undefined;
|
|
||||||
const isInsideTopic = isForum && Boolean(threadId && threadId !== MAIN_THREAD_ID);
|
const isSavedMessages = chatId ? selectIsChatWithSelf(global, chatId) : undefined;
|
||||||
|
const isSavedDialog = chatId ? getIsSavedDialog(chatId, threadId, global.currentUserId) : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
contentKey: selectRightColumnContentKey(global, isMobile),
|
contentKey: selectRightColumnContentKey(global, isMobile),
|
||||||
chatId,
|
chatId,
|
||||||
threadId,
|
threadId,
|
||||||
isInsideTopic,
|
|
||||||
isChatSelected: Boolean(chatId && areActiveChatsLoaded),
|
isChatSelected: Boolean(chatId && areActiveChatsLoaded),
|
||||||
shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations,
|
||||||
nextManagementScreen,
|
nextManagementScreen,
|
||||||
nextProfileTab,
|
nextProfileTab,
|
||||||
|
isSavedMessages,
|
||||||
|
isSavedDialog,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(RightColumn));
|
)(RightColumn));
|
||||||
|
|||||||
@ -23,13 +23,27 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
h3 {
|
.title {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-left: 1.375rem;
|
margin-left: 1.375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
margin-left: 1.375rem;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 1rem;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
color: var(--color-text-secondary);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.tools {
|
.tools {
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
|
|||||||
@ -4,9 +4,9 @@ import { getActions, withGlobal } from '../../global';
|
|||||||
|
|
||||||
import type { ApiExportedInvite } from '../../api/types';
|
import type { ApiExportedInvite } from '../../api/types';
|
||||||
import { MAIN_THREAD_ID } from '../../api/types';
|
import { MAIN_THREAD_ID } from '../../api/types';
|
||||||
import { ManagementScreens, ProfileState } from '../../types';
|
import { ManagementScreens, ProfileState, type ThreadId } from '../../types';
|
||||||
|
|
||||||
import { ANIMATION_END_DELAY } from '../../config';
|
import { ANIMATION_END_DELAY, SAVED_FOLDER_ID } from '../../config';
|
||||||
import {
|
import {
|
||||||
getCanAddContact, getCanManageTopic, isChatChannel, isUserBot, isUserId,
|
getCanAddContact, getCanManageTopic, isChatChannel, isUserBot, isUserId,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
@ -17,6 +17,7 @@ import {
|
|||||||
selectCurrentGifSearch,
|
selectCurrentGifSearch,
|
||||||
selectCurrentStickerSearch,
|
selectCurrentStickerSearch,
|
||||||
selectCurrentTextSearch,
|
selectCurrentTextSearch,
|
||||||
|
selectIsChatWithSelf,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
selectUser,
|
selectUser,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
@ -28,6 +29,7 @@ import useAppLayout from '../../hooks/useAppLayout';
|
|||||||
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||||
import useElectronDrag from '../../hooks/useElectronDrag';
|
import useElectronDrag from '../../hooks/useElectronDrag';
|
||||||
import useFlag from '../../hooks/useFlag';
|
import useFlag from '../../hooks/useFlag';
|
||||||
|
import { useFolderManagerForChatsCount } from '../../hooks/useFolderManager';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../hooks/useLastCallback';
|
||||||
|
|
||||||
@ -41,7 +43,7 @@ import './RightHeader.scss';
|
|||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
threadId?: number;
|
threadId?: ThreadId;
|
||||||
isColumnOpen?: boolean;
|
isColumnOpen?: boolean;
|
||||||
isProfile?: boolean;
|
isProfile?: boolean;
|
||||||
isSearch?: boolean;
|
isSearch?: boolean;
|
||||||
@ -58,7 +60,7 @@ type OwnProps = {
|
|||||||
isAddingChatMembers?: boolean;
|
isAddingChatMembers?: boolean;
|
||||||
profileState?: ProfileState;
|
profileState?: ProfileState;
|
||||||
managementScreen?: ManagementScreens;
|
managementScreen?: ManagementScreens;
|
||||||
onClose: () => void;
|
onClose: (shouldScrollUp?: boolean) => void;
|
||||||
onScreenSelect: (screen: ManagementScreens) => void;
|
onScreenSelect: (screen: ManagementScreens) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -79,6 +81,7 @@ type StateProps = {
|
|||||||
canEditBot?: boolean;
|
canEditBot?: boolean;
|
||||||
isInsideTopic?: boolean;
|
isInsideTopic?: boolean;
|
||||||
canEditTopic?: boolean;
|
canEditTopic?: boolean;
|
||||||
|
isSavedMessages?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const COLUMN_ANIMATION_DURATION = 450 + ANIMATION_END_DELAY;
|
const COLUMN_ANIMATION_DURATION = 450 + ANIMATION_END_DELAY;
|
||||||
@ -121,6 +124,7 @@ enum HeaderContent {
|
|||||||
ManageJoinRequests,
|
ManageJoinRequests,
|
||||||
CreateTopic,
|
CreateTopic,
|
||||||
EditTopic,
|
EditTopic,
|
||||||
|
SavedDialogs,
|
||||||
}
|
}
|
||||||
|
|
||||||
const RightHeader: FC<OwnProps & StateProps> = ({
|
const RightHeader: FC<OwnProps & StateProps> = ({
|
||||||
@ -157,6 +161,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
isBot,
|
isBot,
|
||||||
isInsideTopic,
|
isInsideTopic,
|
||||||
canEditTopic,
|
canEditTopic,
|
||||||
|
isSavedMessages,
|
||||||
onClose,
|
onClose,
|
||||||
onScreenSelect,
|
onScreenSelect,
|
||||||
canEditBot,
|
canEditBot,
|
||||||
@ -178,6 +183,8 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag();
|
const [isDeleteDialogOpen, openDeleteDialog, closeDeleteDialog] = useFlag();
|
||||||
const { isMobile } = useAppLayout();
|
const { isMobile } = useAppLayout();
|
||||||
|
|
||||||
|
const foldersChatCount = useFolderManagerForChatsCount();
|
||||||
|
|
||||||
const handleEditInviteClick = useLastCallback(() => {
|
const handleEditInviteClick = useLastCallback(() => {
|
||||||
setEditingExportedInvite({ chatId: chatId!, invite: currentInviteInfo! });
|
setEditingExportedInvite({ chatId: chatId!, invite: currentInviteInfo! });
|
||||||
onScreenSelect(ManagementScreens.EditInvite);
|
onScreenSelect(ManagementScreens.EditInvite);
|
||||||
@ -211,7 +218,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const toggleEditTopic = useLastCallback(() => {
|
const toggleEditTopic = useLastCallback(() => {
|
||||||
if (!chatId || !threadId) return;
|
if (!chatId || !threadId) return;
|
||||||
openEditTopicPanel({ chatId, topicId: threadId });
|
openEditTopicPanel({ chatId, topicId: Number(threadId) });
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleToggleManagement = useLastCallback(() => {
|
const handleToggleManagement = useLastCallback(() => {
|
||||||
@ -222,6 +229,10 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
toggleStatistics();
|
toggleStatistics();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleClose = useLastCallback(() => {
|
||||||
|
onClose(!isSavedMessages);
|
||||||
|
});
|
||||||
|
|
||||||
const [shouldSkipTransition, setShouldSkipTransition] = useState(!isColumnOpen);
|
const [shouldSkipTransition, setShouldSkipTransition] = useState(!isColumnOpen);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -240,6 +251,8 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
HeaderContent.MemberList
|
HeaderContent.MemberList
|
||||||
) : profileState === ProfileState.StoryList ? (
|
) : profileState === ProfileState.StoryList ? (
|
||||||
HeaderContent.StoryList
|
HeaderContent.StoryList
|
||||||
|
) : profileState === ProfileState.SavedDialogs ? (
|
||||||
|
HeaderContent.SavedDialogs
|
||||||
) : -1 // Never reached
|
) : -1 // Never reached
|
||||||
) : isSearch ? (
|
) : isSearch ? (
|
||||||
HeaderContent.Search
|
HeaderContent.Search
|
||||||
@ -310,6 +323,10 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
const renderingContentKey = useCurrentOrPrev(contentKey, true) ?? -1;
|
const renderingContentKey = useCurrentOrPrev(contentKey, true) ?? -1;
|
||||||
|
|
||||||
function getHeaderTitle() {
|
function getHeaderTitle() {
|
||||||
|
if (isSavedMessages) {
|
||||||
|
return lang('SavedMessages');
|
||||||
|
}
|
||||||
|
|
||||||
if (isInsideTopic) {
|
if (isInsideTopic) {
|
||||||
return lang('AccDescrTopic');
|
return lang('AccDescrTopic');
|
||||||
}
|
}
|
||||||
@ -332,7 +349,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
switch (renderingContentKey) {
|
switch (renderingContentKey) {
|
||||||
case HeaderContent.PollResults:
|
case HeaderContent.PollResults:
|
||||||
return <h3>{lang('PollResults')}</h3>;
|
return <h3 className="title">{lang('PollResults')}</h3>;
|
||||||
case HeaderContent.Search:
|
case HeaderContent.Search:
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -354,39 +371,39 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
case HeaderContent.AddingMembers:
|
case HeaderContent.AddingMembers:
|
||||||
return <h3>{lang(isChannel ? 'ChannelAddSubscribers' : 'GroupAddMembers')}</h3>;
|
return <h3 className="title">{lang(isChannel ? 'ChannelAddSubscribers' : 'GroupAddMembers')}</h3>;
|
||||||
case HeaderContent.ManageInitial:
|
case HeaderContent.ManageInitial:
|
||||||
return <h3>{lang('Edit')}</h3>;
|
return <h3 className="title">{lang('Edit')}</h3>;
|
||||||
case HeaderContent.ManageChatPrivacyType:
|
case HeaderContent.ManageChatPrivacyType:
|
||||||
return <h3>{lang(isChannel ? 'ChannelTypeHeader' : 'GroupTypeHeader')}</h3>;
|
return <h3 className="title">{lang(isChannel ? 'ChannelTypeHeader' : 'GroupTypeHeader')}</h3>;
|
||||||
case HeaderContent.ManageDiscussion:
|
case HeaderContent.ManageDiscussion:
|
||||||
return <h3>{lang('Discussion')}</h3>;
|
return <h3 className="title">{lang('Discussion')}</h3>;
|
||||||
case HeaderContent.ManageChatAdministrators:
|
case HeaderContent.ManageChatAdministrators:
|
||||||
return <h3>{lang('ChannelAdministrators')}</h3>;
|
return <h3 className="title">{lang('ChannelAdministrators')}</h3>;
|
||||||
case HeaderContent.ManageGroupRecentActions:
|
case HeaderContent.ManageGroupRecentActions:
|
||||||
return <h3>{lang('Group.Info.AdminLog')}</h3>;
|
return <h3 className="title">{lang('Group.Info.AdminLog')}</h3>;
|
||||||
case HeaderContent.ManageGroupAdminRights:
|
case HeaderContent.ManageGroupAdminRights:
|
||||||
return <h3>{lang('EditAdminRights')}</h3>;
|
return <h3 className="title">{lang('EditAdminRights')}</h3>;
|
||||||
case HeaderContent.ManageGroupNewAdminRights:
|
case HeaderContent.ManageGroupNewAdminRights:
|
||||||
return <h3>{lang('SetAsAdmin')}</h3>;
|
return <h3 className="title">{lang('SetAsAdmin')}</h3>;
|
||||||
case HeaderContent.ManageGroupPermissions:
|
case HeaderContent.ManageGroupPermissions:
|
||||||
return <h3>{lang('ChannelPermissions')}</h3>;
|
return <h3 className="title">{lang('ChannelPermissions')}</h3>;
|
||||||
case HeaderContent.ManageGroupRemovedUsers:
|
case HeaderContent.ManageGroupRemovedUsers:
|
||||||
return <h3>{lang('BlockedUsers')}</h3>;
|
return <h3 className="title">{lang('BlockedUsers')}</h3>;
|
||||||
case HeaderContent.ManageChannelRemovedUsers:
|
case HeaderContent.ManageChannelRemovedUsers:
|
||||||
return <h3>{lang('ChannelBlockedUsers')}</h3>;
|
return <h3 className="title">{lang('ChannelBlockedUsers')}</h3>;
|
||||||
case HeaderContent.ManageGroupUserPermissionsCreate:
|
case HeaderContent.ManageGroupUserPermissionsCreate:
|
||||||
return <h3>{lang('ChannelAddException')}</h3>;
|
return <h3 className="title">{lang('ChannelAddException')}</h3>;
|
||||||
case HeaderContent.ManageGroupUserPermissions:
|
case HeaderContent.ManageGroupUserPermissions:
|
||||||
return <h3>{lang('UserRestrictions')}</h3>;
|
return <h3 className="title">{lang('UserRestrictions')}</h3>;
|
||||||
case HeaderContent.ManageInvites:
|
case HeaderContent.ManageInvites:
|
||||||
return <h3>{lang('lng_group_invite_title')}</h3>;
|
return <h3 className="title">{lang('lng_group_invite_title')}</h3>;
|
||||||
case HeaderContent.ManageEditInvite:
|
case HeaderContent.ManageEditInvite:
|
||||||
return <h3>{isEditingInvite ? lang('EditLink') : lang('NewLink')}</h3>;
|
return <h3 className="title">{isEditingInvite ? lang('EditLink') : lang('NewLink')}</h3>;
|
||||||
case HeaderContent.ManageInviteInfo:
|
case HeaderContent.ManageInviteInfo:
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h3>{lang('InviteLink')}</h3>
|
<h3 className="title">{lang('InviteLink')}</h3>
|
||||||
<section className="tools">
|
<section className="tools">
|
||||||
{currentInviteInfo && !currentInviteInfo.isRevoked && (
|
{currentInviteInfo && !currentInviteInfo.isRevoked && (
|
||||||
<Button
|
<Button
|
||||||
@ -425,9 +442,9 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
case HeaderContent.ManageJoinRequests:
|
case HeaderContent.ManageJoinRequests:
|
||||||
return <h3>{isChannel ? lang('SubscribeRequests') : lang('MemberRequests')}</h3>;
|
return <h3 className="title">{isChannel ? lang('SubscribeRequests') : lang('MemberRequests')}</h3>;
|
||||||
case HeaderContent.ManageGroupAddAdmins:
|
case HeaderContent.ManageGroupAddAdmins:
|
||||||
return <h3>{lang('Channel.Management.AddModerator')}</h3>;
|
return <h3 className="title">{lang('Channel.Management.AddModerator')}</h3>;
|
||||||
case HeaderContent.StickerSearch:
|
case HeaderContent.StickerSearch:
|
||||||
return (
|
return (
|
||||||
<SearchInput
|
<SearchInput
|
||||||
@ -447,32 +464,40 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
case HeaderContent.Statistics:
|
case HeaderContent.Statistics:
|
||||||
return <h3>{lang(isChannel ? 'ChannelStats.Title' : 'GroupStats.Title')}</h3>;
|
return <h3 className="title">{lang(isChannel ? 'ChannelStats.Title' : 'GroupStats.Title')}</h3>;
|
||||||
case HeaderContent.MessageStatistics:
|
case HeaderContent.MessageStatistics:
|
||||||
return <h3>{lang('Stats.MessageTitle')}</h3>;
|
return <h3 className="title">{lang('Stats.MessageTitle')}</h3>;
|
||||||
case HeaderContent.StoryStatistics:
|
case HeaderContent.StoryStatistics:
|
||||||
return <h3>{lang('Stats.StoryTitle')}</h3>;
|
return <h3 className="title">{lang('Stats.StoryTitle')}</h3>;
|
||||||
case HeaderContent.BoostStatistics:
|
case HeaderContent.BoostStatistics:
|
||||||
return <h3>{lang('Boosts')}</h3>;
|
return <h3 className="title">{lang('Boosts')}</h3>;
|
||||||
case HeaderContent.SharedMedia:
|
case HeaderContent.SharedMedia:
|
||||||
return <h3>{lang('SharedMedia')}</h3>;
|
return <h3 className="title">{lang('SharedMedia')}</h3>;
|
||||||
case HeaderContent.ManageChannelSubscribers:
|
case HeaderContent.ManageChannelSubscribers:
|
||||||
return <h3>{lang('ChannelSubscribers')}</h3>;
|
return <h3 className="title">{lang('ChannelSubscribers')}</h3>;
|
||||||
case HeaderContent.MemberList:
|
case HeaderContent.MemberList:
|
||||||
case HeaderContent.ManageGroupMembers:
|
case HeaderContent.ManageGroupMembers:
|
||||||
return <h3>{lang('GroupMembers')}</h3>;
|
return <h3 className="title">{lang('GroupMembers')}</h3>;
|
||||||
case HeaderContent.StoryList:
|
case HeaderContent.StoryList:
|
||||||
return <h3>{lang(isSelf ? 'Settings.MyStories' : 'PeerInfo.PaneStories')}</h3>;
|
return <h3 className="title">{lang(isSelf ? 'Settings.MyStories' : 'PeerInfo.PaneStories')}</h3>;
|
||||||
|
case HeaderContent.SavedDialogs:
|
||||||
|
return (
|
||||||
|
<div className="header">
|
||||||
|
<h3 className="title">{lang('SavedMessagesTab')}</h3>
|
||||||
|
<div className="subtitle">{lang('Chats', foldersChatCount[SAVED_FOLDER_ID])}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
case HeaderContent.ManageReactions:
|
case HeaderContent.ManageReactions:
|
||||||
return <h3>{lang('Reactions')}</h3>;
|
return <h3 className="title">{lang('Reactions')}</h3>;
|
||||||
case HeaderContent.CreateTopic:
|
case HeaderContent.CreateTopic:
|
||||||
return <h3>{lang('NewTopic')}</h3>;
|
return <h3 className="title">{lang('NewTopic')}</h3>;
|
||||||
case HeaderContent.EditTopic:
|
case HeaderContent.EditTopic:
|
||||||
return <h3>{lang('EditTopic')}</h3>;
|
return <h3 className="title">{lang('EditTopic')}</h3>;
|
||||||
default:
|
default:
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h3>{getHeaderTitle()}
|
<h3 className="title">
|
||||||
|
{getHeaderTitle()}
|
||||||
</h3>
|
</h3>
|
||||||
<section className="tools">
|
<section className="tools">
|
||||||
{canAddContact && (
|
{canAddContact && (
|
||||||
@ -536,15 +561,16 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const isBackButton = (
|
const isBackButton = isMobile || (
|
||||||
isMobile
|
!isSavedMessages && (
|
||||||
|| contentKey === HeaderContent.SharedMedia
|
contentKey === HeaderContent.SharedMedia
|
||||||
|| contentKey === HeaderContent.MemberList
|
|| contentKey === HeaderContent.MemberList
|
||||||
|| contentKey === HeaderContent.StoryList
|
|| contentKey === HeaderContent.StoryList
|
||||||
|| contentKey === HeaderContent.AddingMembers
|
|| contentKey === HeaderContent.AddingMembers
|
||||||
|| contentKey === HeaderContent.MessageStatistics
|
|| contentKey === HeaderContent.MessageStatistics
|
||||||
|| contentKey === HeaderContent.StoryStatistics
|
|| contentKey === HeaderContent.StoryStatistics
|
||||||
|| isManagement
|
|| isManagement
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const buttonClassName = buildClassName(
|
const buttonClassName = buildClassName(
|
||||||
@ -564,7 +590,7 @@ const RightHeader: FC<OwnProps & StateProps> = ({
|
|||||||
round
|
round
|
||||||
color="translucent"
|
color="translucent"
|
||||||
size="smaller"
|
size="smaller"
|
||||||
onClick={onClose}
|
onClick={handleClose}
|
||||||
ariaLabel={isBackButton ? lang('Common.Back') : lang('Common.Close')}
|
ariaLabel={isBackButton ? lang('Common.Back') : lang('Common.Close')}
|
||||||
>
|
>
|
||||||
<div className={buttonClassName} />
|
<div className={buttonClassName} />
|
||||||
@ -594,6 +620,7 @@ export default withGlobal<OwnProps>(
|
|||||||
const topic = isInsideTopic ? chat.topics?.[threadId!] : undefined;
|
const topic = isInsideTopic ? chat.topics?.[threadId!] : undefined;
|
||||||
const canEditTopic = isInsideTopic && topic && getCanManageTopic(chat, topic);
|
const canEditTopic = isInsideTopic && topic && getCanManageTopic(chat, topic);
|
||||||
const isBot = user && isUserBot(user);
|
const isBot = user && isUserBot(user);
|
||||||
|
const isSavedMessages = chatId ? selectIsChatWithSelf(global, chatId) : undefined;
|
||||||
const canEditBot = isBot && user?.canEditBot;
|
const canEditBot = isBot && user?.canEditBot;
|
||||||
|
|
||||||
const canAddContact = user && getCanAddContact(user);
|
const canAddContact = user && getCanAddContact(user);
|
||||||
@ -621,6 +648,7 @@ export default withGlobal<OwnProps>(
|
|||||||
gifSearchQuery,
|
gifSearchQuery,
|
||||||
isEditingInvite,
|
isEditingInvite,
|
||||||
currentInviteInfo,
|
currentInviteInfo,
|
||||||
|
isSavedMessages,
|
||||||
shouldSkipHistoryAnimations: tabState.shouldSkipHistoryAnimations,
|
shouldSkipHistoryAnimations: tabState.shouldSkipHistoryAnimations,
|
||||||
canEditBot,
|
canEditBot,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,12 +6,15 @@ import React, {
|
|||||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { ApiMessage, ApiPeer } from '../../api/types';
|
import type { ApiMessage, ApiPeer } from '../../api/types';
|
||||||
|
import type { ThreadId } from '../../types';
|
||||||
|
|
||||||
|
import { REPLIES_USER_ID } from '../../config';
|
||||||
import {
|
import {
|
||||||
selectChat,
|
|
||||||
selectChatMessages,
|
selectChatMessages,
|
||||||
selectCurrentTextSearch,
|
selectCurrentTextSearch,
|
||||||
selectUser,
|
selectForwardedSender,
|
||||||
|
selectIsChatWithSelf,
|
||||||
|
selectSender,
|
||||||
} from '../../global/selectors';
|
} from '../../global/selectors';
|
||||||
import { disableDirectTextInput, enableDirectTextInput } from '../../util/directInputManager';
|
import { disableDirectTextInput, enableDirectTextInput } from '../../util/directInputManager';
|
||||||
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
||||||
@ -32,7 +35,7 @@ import './RightSearch.scss';
|
|||||||
|
|
||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId: number;
|
threadId: ThreadId;
|
||||||
onClose: NoneToVoidFunction;
|
onClose: NoneToVoidFunction;
|
||||||
isActive: boolean;
|
isActive: boolean;
|
||||||
};
|
};
|
||||||
@ -42,6 +45,7 @@ type StateProps = {
|
|||||||
query?: string;
|
query?: string;
|
||||||
totalCount?: number;
|
totalCount?: number;
|
||||||
foundIds?: number[];
|
foundIds?: number[];
|
||||||
|
isSavedMessages?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const RightSearch: FC<OwnProps & StateProps> = ({
|
const RightSearch: FC<OwnProps & StateProps> = ({
|
||||||
@ -52,6 +56,7 @@ const RightSearch: FC<OwnProps & StateProps> = ({
|
|||||||
query,
|
query,
|
||||||
totalCount,
|
totalCount,
|
||||||
foundIds,
|
foundIds,
|
||||||
|
isSavedMessages,
|
||||||
onClose,
|
onClose,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
@ -97,15 +102,11 @@ const RightSearch: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const global = getGlobal();
|
const global = getGlobal();
|
||||||
|
|
||||||
let senderPeer = message.senderId
|
const originalSender = (isSavedMessages || chatId === REPLIES_USER_ID)
|
||||||
? selectUser(global, message.senderId) || selectChat(global, message.senderId)
|
? selectForwardedSender(global, message) : undefined;
|
||||||
: undefined;
|
const messageSender = selectSender(global, message);
|
||||||
|
|
||||||
if (!senderPeer && message.forwardInfo) {
|
const senderPeer = originalSender || messageSender;
|
||||||
const { isChannelPost, fromChatId } = message.forwardInfo;
|
|
||||||
const originalSender = isChannelPost && fromChatId ? selectChat(global, fromChatId) : undefined;
|
|
||||||
if (originalSender) senderPeer = originalSender;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!senderPeer) {
|
if (!senderPeer) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -113,11 +114,11 @@ const RightSearch: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
message,
|
message,
|
||||||
senderPeer: senderPeer!,
|
senderPeer,
|
||||||
onClick: () => focusMessage({ chatId, threadId, messageId: id }),
|
onClick: () => focusMessage({ chatId, threadId, messageId: id }),
|
||||||
};
|
};
|
||||||
}).filter(Boolean);
|
}).filter(Boolean);
|
||||||
}, [query, viewportIds, messagesById, focusMessage, chatId, threadId]);
|
}, [query, viewportIds, messagesById, isSavedMessages, chatId, threadId]);
|
||||||
|
|
||||||
const handleKeyDown = useKeyboardListNavigation(containerRef, true, (index) => {
|
const handleKeyDown = useKeyboardListNavigation(containerRef, true, (index) => {
|
||||||
const foundResult = viewportResults?.[index === -1 ? 0 : index];
|
const foundResult = viewportResults?.[index === -1 ? 0 : index];
|
||||||
@ -197,11 +198,14 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const { query, results } = selectCurrentTextSearch(global) || {};
|
const { query, results } = selectCurrentTextSearch(global) || {};
|
||||||
const { totalCount, foundIds } = results || {};
|
const { totalCount, foundIds } = results || {};
|
||||||
|
|
||||||
|
const isSavedMessages = selectIsChatWithSelf(global, chatId);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
messagesById,
|
messagesById,
|
||||||
query,
|
query,
|
||||||
totalCount,
|
totalCount,
|
||||||
foundIds,
|
foundIds,
|
||||||
|
isSavedMessages,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(RightSearch));
|
)(RightSearch));
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useEffect } from '../../../lib/teact/teact';
|
import { useEffect } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import { ProfileState } from '../../../types';
|
import { ProfileState, type ProfileTabType } from '../../../types';
|
||||||
|
|
||||||
import animateScroll from '../../../util/animateScroll';
|
import animateScroll from '../../../util/animateScroll';
|
||||||
import { throttle } from '../../../util/schedulers';
|
import { throttle } from '../../../util/schedulers';
|
||||||
@ -17,7 +17,7 @@ let isScrollingProgrammatically = false;
|
|||||||
|
|
||||||
export default function useProfileState(
|
export default function useProfileState(
|
||||||
containerRef: { current: HTMLDivElement | null },
|
containerRef: { current: HTMLDivElement | null },
|
||||||
tabType: string,
|
tabType: ProfileTabType,
|
||||||
profileState: ProfileState,
|
profileState: ProfileState,
|
||||||
onProfileStateChange: (state: ProfileState) => void,
|
onProfileStateChange: (state: ProfileState) => void,
|
||||||
) {
|
) {
|
||||||
@ -27,11 +27,7 @@ export default function useProfileState(
|
|||||||
const container = containerRef.current!;
|
const container = containerRef.current!;
|
||||||
const tabsEl = container.querySelector<HTMLDivElement>('.TabList')!;
|
const tabsEl = container.querySelector<HTMLDivElement>('.TabList')!;
|
||||||
if (container.scrollTop < tabsEl.offsetTop) {
|
if (container.scrollTop < tabsEl.offsetTop) {
|
||||||
onProfileStateChange(
|
onProfileStateChange(getStateFromTabType(tabType));
|
||||||
tabType === 'members'
|
|
||||||
? ProfileState.MemberList
|
|
||||||
: (tabType === 'stories' ? ProfileState.StoryList : ProfileState.SharedMedia),
|
|
||||||
);
|
|
||||||
isScrollingProgrammatically = true;
|
isScrollingProgrammatically = true;
|
||||||
animateScroll(container, tabsEl, 'start', undefined, undefined, undefined, TRANSITION_DURATION);
|
animateScroll(container, tabsEl, 'start', undefined, undefined, undefined, TRANSITION_DURATION);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
@ -69,9 +65,7 @@ export default function useProfileState(
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
isScrollingProgrammatically = false;
|
isScrollingProgrammatically = false;
|
||||||
}, PROGRAMMATIC_SCROLL_TIMEOUT_MS);
|
}, PROGRAMMATIC_SCROLL_TIMEOUT_MS);
|
||||||
|
}, [profileState, containerRef]);
|
||||||
onProfileStateChange(profileState);
|
|
||||||
}, [profileState, containerRef, onProfileStateChange]);
|
|
||||||
|
|
||||||
const determineProfileState = useLastCallback(() => {
|
const determineProfileState = useLastCallback(() => {
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
@ -86,9 +80,7 @@ export default function useProfileState(
|
|||||||
|
|
||||||
let state: ProfileState = ProfileState.Profile;
|
let state: ProfileState = ProfileState.Profile;
|
||||||
if (container.scrollTop >= tabListEl.offsetTop) {
|
if (container.scrollTop >= tabListEl.offsetTop) {
|
||||||
state = tabType === 'members'
|
state = getStateFromTabType(tabType);
|
||||||
? ProfileState.MemberList
|
|
||||||
: (tabType === 'stories' ? ProfileState.StoryList : ProfileState.SharedMedia);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onProfileStateChange(state);
|
onProfileStateChange(state);
|
||||||
@ -114,3 +106,16 @@ export default function useProfileState(
|
|||||||
|
|
||||||
return { handleScroll };
|
return { handleScroll };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getStateFromTabType(tabType: ProfileTabType) {
|
||||||
|
switch (tabType) {
|
||||||
|
case 'members':
|
||||||
|
return ProfileState.MemberList;
|
||||||
|
case 'stories':
|
||||||
|
return ProfileState.StoryList;
|
||||||
|
case 'dialogs':
|
||||||
|
return ProfileState.SavedDialogs;
|
||||||
|
default:
|
||||||
|
return ProfileState.SharedMedia;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -3,10 +3,11 @@ import { useMemo, useRef } from '../../../lib/teact/teact';
|
|||||||
import type {
|
import type {
|
||||||
ApiChat, ApiChatMember, ApiMessage, ApiUser, ApiUserStatus,
|
ApiChat, ApiChatMember, ApiMessage, ApiUser, ApiUserStatus,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import type { ProfileTabType, SharedMediaType } from '../../../types';
|
import type { ProfileTabType, SharedMediaType, ThreadId } from '../../../types';
|
||||||
|
|
||||||
import { MEMBERS_SLICE, MESSAGE_SEARCH_SLICE, SHARED_MEDIA_SLICE } from '../../../config';
|
import { MEMBERS_SLICE, MESSAGE_SEARCH_SLICE, SHARED_MEDIA_SLICE } from '../../../config';
|
||||||
import { getMessageContentIds, sortChatIds, sortUserIds } from '../../../global/helpers';
|
import { getMessageContentIds, sortUserIds } from '../../../global/helpers';
|
||||||
|
import sortChatIds from '../../common/helpers/sortChatIds';
|
||||||
|
|
||||||
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
||||||
import useSyncEffect from '../../../hooks/useSyncEffect';
|
import useSyncEffect from '../../../hooks/useSyncEffect';
|
||||||
@ -26,7 +27,7 @@ export default function useProfileViewportIds(
|
|||||||
chatsById?: Record<string, ApiChat>,
|
chatsById?: Record<string, ApiChat>,
|
||||||
chatMessages?: Record<number, ApiMessage>,
|
chatMessages?: Record<number, ApiMessage>,
|
||||||
foundIds?: number[],
|
foundIds?: number[],
|
||||||
topicId?: number,
|
threadId?: ThreadId,
|
||||||
storyIds?: number[],
|
storyIds?: number[],
|
||||||
archiveStoryIds?: number[],
|
archiveStoryIds?: number[],
|
||||||
similarChannels?: string[],
|
similarChannels?: string[],
|
||||||
@ -50,7 +51,7 @@ export default function useProfileViewportIds(
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sortChatIds(commonChatIds, chatsById, true);
|
return sortChatIds(commonChatIds, true);
|
||||||
}, [chatsById, commonChatIds]);
|
}, [chatsById, commonChatIds]);
|
||||||
|
|
||||||
const [memberViewportIds, getMoreMembers, noProfileInfoForMembers] = useInfiniteScrollForLoadableItems(
|
const [memberViewportIds, getMoreMembers, noProfileInfoForMembers] = useInfiniteScrollForLoadableItems(
|
||||||
@ -58,23 +59,23 @@ export default function useProfileViewportIds(
|
|||||||
);
|
);
|
||||||
|
|
||||||
const [mediaViewportIds, getMoreMedia, noProfileInfoForMedia] = useInfiniteScrollForSharedMedia(
|
const [mediaViewportIds, getMoreMedia, noProfileInfoForMedia] = useInfiniteScrollForSharedMedia(
|
||||||
'media', resultType, searchMessages, chatMessages, foundIds, topicId,
|
'media', resultType, searchMessages, chatMessages, foundIds, threadId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [documentViewportIds, getMoreDocuments, noProfileInfoForDocuments] = useInfiniteScrollForSharedMedia(
|
const [documentViewportIds, getMoreDocuments, noProfileInfoForDocuments] = useInfiniteScrollForSharedMedia(
|
||||||
'documents', resultType, searchMessages, chatMessages, foundIds, topicId,
|
'documents', resultType, searchMessages, chatMessages, foundIds, threadId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [linkViewportIds, getMoreLinks, noProfileInfoForLinks] = useInfiniteScrollForSharedMedia(
|
const [linkViewportIds, getMoreLinks, noProfileInfoForLinks] = useInfiniteScrollForSharedMedia(
|
||||||
'links', resultType, searchMessages, chatMessages, foundIds, topicId,
|
'links', resultType, searchMessages, chatMessages, foundIds, threadId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [audioViewportIds, getMoreAudio, noProfileInfoForAudio] = useInfiniteScrollForSharedMedia(
|
const [audioViewportIds, getMoreAudio, noProfileInfoForAudio] = useInfiniteScrollForSharedMedia(
|
||||||
'audio', resultType, searchMessages, chatMessages, foundIds, topicId,
|
'audio', resultType, searchMessages, chatMessages, foundIds, threadId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [voiceViewportIds, getMoreVoices, noProfileInfoForVoices] = useInfiniteScrollForSharedMedia(
|
const [voiceViewportIds, getMoreVoices, noProfileInfoForVoices] = useInfiniteScrollForSharedMedia(
|
||||||
'voice', resultType, searchMessages, chatMessages, foundIds, topicId,
|
'voice', resultType, searchMessages, chatMessages, foundIds, threadId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [commonChatViewportIds, getMoreCommonChats, noProfileInfoForCommonChats] = useInfiniteScrollForLoadableItems(
|
const [commonChatViewportIds, getMoreCommonChats, noProfileInfoForCommonChats] = useInfiniteScrollForLoadableItems(
|
||||||
@ -146,6 +147,9 @@ export default function useProfileViewportIds(
|
|||||||
case 'similarChannels':
|
case 'similarChannels':
|
||||||
viewportIds = similarChannels;
|
viewportIds = similarChannels;
|
||||||
break;
|
break;
|
||||||
|
case 'dialogs':
|
||||||
|
noProfileInfo = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
return [resultType, viewportIds, getMore, noProfileInfo] as const;
|
return [resultType, viewportIds, getMore, noProfileInfo] as const;
|
||||||
@ -173,13 +177,13 @@ function useInfiniteScrollForSharedMedia(
|
|||||||
handleLoadMore?: AnyToVoidFunction,
|
handleLoadMore?: AnyToVoidFunction,
|
||||||
chatMessages?: Record<number, ApiMessage>,
|
chatMessages?: Record<number, ApiMessage>,
|
||||||
foundIds?: number[],
|
foundIds?: number[],
|
||||||
topicId?: number,
|
threadId?: ThreadId,
|
||||||
) {
|
) {
|
||||||
const messageIdsRef = useRef<number[]>();
|
const messageIdsRef = useRef<number[]>();
|
||||||
|
|
||||||
useSyncEffect(() => {
|
useSyncEffect(() => {
|
||||||
messageIdsRef.current = undefined;
|
messageIdsRef.current = undefined;
|
||||||
}, [topicId]);
|
}, [threadId]);
|
||||||
|
|
||||||
useSyncEffect(() => {
|
useSyncEffect(() => {
|
||||||
if (currentResultType === forSharedMediaType && chatMessages && foundIds) {
|
if (currentResultType === forSharedMediaType && chatMessages && foundIds) {
|
||||||
|
|||||||
@ -9,10 +9,11 @@ import { ManagementScreens } from '../../../types';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
filterUsersByName, getHasAdminRight, isChatBasicGroup,
|
filterUsersByName, getHasAdminRight, isChatBasicGroup,
|
||||||
isChatChannel, isUserBot, sortChatIds, sortUserIds,
|
isChatChannel, isUserBot, sortUserIds,
|
||||||
} from '../../../global/helpers';
|
} from '../../../global/helpers';
|
||||||
import { selectChat, selectChatFullInfo, selectTabState } from '../../../global/selectors';
|
import { selectChat, selectChatFullInfo, selectTabState } from '../../../global/selectors';
|
||||||
import { unique } from '../../../util/iteratees';
|
import { unique } from '../../../util/iteratees';
|
||||||
|
import sortChatIds from '../../common/helpers/sortChatIds';
|
||||||
|
|
||||||
import usePeerStoriesPolling from '../../../hooks/polling/usePeerStoriesPolling';
|
import usePeerStoriesPolling from '../../../hooks/polling/usePeerStoriesPolling';
|
||||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
@ -111,7 +112,6 @@ const ManageGroupMembers: FC<OwnProps & StateProps> = ({
|
|||||||
const displayedIds = useMemo(() => {
|
const displayedIds = useMemo(() => {
|
||||||
// No need for expensive global updates on users, so we avoid them
|
// No need for expensive global updates on users, so we avoid them
|
||||||
const usersById = getGlobal().users.byId;
|
const usersById = getGlobal().users.byId;
|
||||||
const chatsById = getGlobal().chats.byId;
|
|
||||||
const shouldUseSearchResults = Boolean(searchQuery);
|
const shouldUseSearchResults = Boolean(searchQuery);
|
||||||
const listedIds = !shouldUseSearchResults
|
const listedIds = !shouldUseSearchResults
|
||||||
? memberIds
|
? memberIds
|
||||||
@ -131,7 +131,6 @@ const ManageGroupMembers: FC<OwnProps & StateProps> = ({
|
|||||||
return (isChannel || user.canBeInvitedToGroup || !isUserBot(user))
|
return (isChannel || user.canBeInvitedToGroup || !isUserBot(user))
|
||||||
&& (!noAdmins || !adminIds.includes(contactId));
|
&& (!noAdmins || !adminIds.includes(contactId));
|
||||||
}),
|
}),
|
||||||
chatsById,
|
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
}, [memberIds, localContactIds, searchQuery, localUserIds, globalUserIds, isChannel, noAdmins, adminIds]);
|
}, [memberIds, localContactIds, searchQuery, localUserIds, globalUserIds, isChannel, noAdmins, adminIds]);
|
||||||
|
|||||||
@ -40,7 +40,6 @@ export const GLOBAL_STATE_CACHE_DISABLED = false;
|
|||||||
export const GLOBAL_STATE_CACHE_KEY = 'tt-global-state';
|
export const GLOBAL_STATE_CACHE_KEY = 'tt-global-state';
|
||||||
export const GLOBAL_STATE_CACHE_USER_LIST_LIMIT = 500;
|
export const GLOBAL_STATE_CACHE_USER_LIST_LIMIT = 500;
|
||||||
export const GLOBAL_STATE_CACHE_CHAT_LIST_LIMIT = 200;
|
export const GLOBAL_STATE_CACHE_CHAT_LIST_LIMIT = 200;
|
||||||
export const GLOBAL_STATE_CACHE_CHATS_WITH_MESSAGES_LIMIT = 30;
|
|
||||||
export const GLOBAL_STATE_CACHE_CUSTOM_EMOJI_LIMIT = 150;
|
export const GLOBAL_STATE_CACHE_CUSTOM_EMOJI_LIMIT = 150;
|
||||||
|
|
||||||
export const MEDIA_CACHE_DISABLED = false;
|
export const MEDIA_CACHE_DISABLED = false;
|
||||||
@ -290,11 +289,13 @@ export const HEART_REACTION: ApiReactionEmoji = {
|
|||||||
// MTProto constants
|
// MTProto constants
|
||||||
export const SERVICE_NOTIFICATIONS_USER_ID = '777000';
|
export const SERVICE_NOTIFICATIONS_USER_ID = '777000';
|
||||||
export const REPLIES_USER_ID = '1271266957'; // TODO For Test connection ID must be equal to 708513
|
export const REPLIES_USER_ID = '1271266957'; // TODO For Test connection ID must be equal to 708513
|
||||||
|
export const ANONYMOUS_USER_ID = '2666000';
|
||||||
export const RESTRICTED_EMOJI_SET_ID = '7173162320003080';
|
export const RESTRICTED_EMOJI_SET_ID = '7173162320003080';
|
||||||
export const CHANNEL_ID_LENGTH = 14; // 14 symbols, including -100 prefix
|
export const CHANNEL_ID_LENGTH = 14; // 14 symbols, including -100 prefix
|
||||||
export const DEFAULT_GIF_SEARCH_BOT_USERNAME = 'gif';
|
export const DEFAULT_GIF_SEARCH_BOT_USERNAME = 'gif';
|
||||||
export const ALL_FOLDER_ID = 0;
|
export const ALL_FOLDER_ID = 0;
|
||||||
export const ARCHIVED_FOLDER_ID = 1;
|
export const ARCHIVED_FOLDER_ID = 1;
|
||||||
|
export const SAVED_FOLDER_ID = -1;
|
||||||
export const DELETED_COMMENTS_CHANNEL_ID = '-100777';
|
export const DELETED_COMMENTS_CHANNEL_ID = '-100777';
|
||||||
export const MAX_MEDIA_FILES_FOR_ALBUM = 10;
|
export const MAX_MEDIA_FILES_FOR_ALBUM = 10;
|
||||||
export const MAX_ACTIVE_PINNED_CHATS = 5;
|
export const MAX_ACTIVE_PINNED_CHATS = 5;
|
||||||
@ -339,4 +340,5 @@ export const DEFAULT_LIMITS: Record<ApiLimitType, readonly [number, number]> = {
|
|||||||
chatlistInvites: [3, 100],
|
chatlistInvites: [3, 100],
|
||||||
chatlistJoined: [2, 20],
|
chatlistJoined: [2, 20],
|
||||||
recommendedChannels: [10, 100],
|
recommendedChannels: [10, 100],
|
||||||
|
savedDialogsPinned: [5, 100],
|
||||||
};
|
};
|
||||||
|
|||||||
@ -26,8 +26,19 @@ import {
|
|||||||
import { replaceInlineBotSettings, replaceInlineBotsIsLoading } from '../../reducers/bots';
|
import { replaceInlineBotSettings, replaceInlineBotsIsLoading } from '../../reducers/bots';
|
||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import {
|
import {
|
||||||
selectBot, selectChat, selectChatMessage, selectCurrentChat, selectCurrentMessageList, selectDraft,
|
selectBot,
|
||||||
selectIsTrustedBot, selectMessageReplyInfo, selectSendAs, selectTabState, selectUser, selectUserFullInfo,
|
selectChat,
|
||||||
|
selectChatLastMessageId,
|
||||||
|
selectChatMessage,
|
||||||
|
selectCurrentChat,
|
||||||
|
selectCurrentMessageList,
|
||||||
|
selectDraft,
|
||||||
|
selectIsTrustedBot,
|
||||||
|
selectMessageReplyInfo,
|
||||||
|
selectSendAs,
|
||||||
|
selectTabState,
|
||||||
|
selectUser,
|
||||||
|
selectUserFullInfo,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
import { fetchChatByUsername } from './chats';
|
import { fetchChatByUsername } from './chats';
|
||||||
|
|
||||||
@ -193,8 +204,10 @@ addActionHandler('sendBotCommand', (global, actions, payload): ActionReturnType
|
|||||||
actions.resetDraftReplyInfo({ tabId });
|
actions.resetDraftReplyInfo({ tabId });
|
||||||
actions.clearWebPagePreview({ tabId });
|
actions.clearWebPagePreview({ tabId });
|
||||||
|
|
||||||
|
const lastMessageId = selectChatLastMessageId(global, chat.id);
|
||||||
|
|
||||||
void sendBotCommand(
|
void sendBotCommand(
|
||||||
chat, command, selectDraft(global, chat.id, threadId)?.replyInfo, selectSendAs(global, chat.id),
|
chat, command, selectDraft(global, chat.id, threadId)?.replyInfo, selectSendAs(global, chat.id), lastMessageId,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -207,6 +220,8 @@ addActionHandler('restartBot', async (global, actions, payload): Promise<void> =
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lastMessageId = selectChatLastMessageId(global, chat.id);
|
||||||
|
|
||||||
const result = await callApi('unblockUser', { user: bot });
|
const result = await callApi('unblockUser', { user: bot });
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return;
|
return;
|
||||||
@ -215,7 +230,7 @@ addActionHandler('restartBot', async (global, actions, payload): Promise<void> =
|
|||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = removeBlockedUser(global, bot.id);
|
global = removeBlockedUser(global, bot.id);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
void sendBotCommand(chat, '/start', undefined, selectSendAs(global, chatId));
|
void sendBotCommand(chat, '/start', undefined, selectSendAs(global, chatId), lastMessageId);
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('loadTopInlineBots', async (global): Promise<void> => {
|
addActionHandler('loadTopInlineBots', async (global): Promise<void> => {
|
||||||
@ -442,6 +457,7 @@ addActionHandler('sharePhoneWithBot', async (global, actions, payload): Promise<
|
|||||||
const currentUser = selectUser(global, global.currentUserId!)!;
|
const currentUser = selectUser(global, global.currentUserId!)!;
|
||||||
|
|
||||||
if (!chat) return;
|
if (!chat) return;
|
||||||
|
const lastMessageId = selectChatLastMessageId(global, chat.id);
|
||||||
|
|
||||||
await callApi('sendMessage', {
|
await callApi('sendMessage', {
|
||||||
chat,
|
chat,
|
||||||
@ -451,6 +467,7 @@ addActionHandler('sharePhoneWithBot', async (global, actions, payload): Promise<
|
|||||||
phoneNumber: currentUser.phoneNumber || '',
|
phoneNumber: currentUser.phoneNumber || '',
|
||||||
userId: currentUser.id,
|
userId: currentUser.id,
|
||||||
},
|
},
|
||||||
|
lastMessageId,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1069,13 +1086,14 @@ async function searchInlineBot<T extends GlobalState>(global: T, {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function sendBotCommand(
|
async function sendBotCommand(
|
||||||
chat: ApiChat, command: string, replyInfo?: ApiInputMessageReplyInfo, sendAs?: ApiPeer,
|
chat: ApiChat, command: string, replyInfo?: ApiInputMessageReplyInfo, sendAs?: ApiPeer, lastMessageId?: number,
|
||||||
) {
|
) {
|
||||||
await callApi('sendMessage', {
|
await callApi('sendMessage', {
|
||||||
chat,
|
chat,
|
||||||
replyInfo,
|
replyInfo,
|
||||||
text: command,
|
text: command,
|
||||||
sendAs,
|
sendAs,
|
||||||
|
lastMessageId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import type {
|
|||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import type { RequiredGlobalActions } from '../../index';
|
import type { RequiredGlobalActions } from '../../index';
|
||||||
import type {
|
import type {
|
||||||
ActionReturnType, GlobalState, TabArgs,
|
ActionReturnType, ChatListType, GlobalState, TabArgs,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||||
import {
|
import {
|
||||||
@ -12,6 +12,7 @@ import {
|
|||||||
ManagementProgress,
|
ManagementProgress,
|
||||||
NewChatMembersProgress,
|
NewChatMembersProgress,
|
||||||
SettingsScreens,
|
SettingsScreens,
|
||||||
|
type ThreadId,
|
||||||
} from '../../../types';
|
} from '../../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -20,6 +21,7 @@ import {
|
|||||||
CHAT_LIST_LOAD_SLICE,
|
CHAT_LIST_LOAD_SLICE,
|
||||||
DEBUG,
|
DEBUG,
|
||||||
RE_TG_LINK,
|
RE_TG_LINK,
|
||||||
|
SAVED_FOLDER_ID,
|
||||||
SERVICE_NOTIFICATIONS_USER_ID,
|
SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
TME_WEB_DOMAINS,
|
TME_WEB_DOMAINS,
|
||||||
TMP_CHAT_ID,
|
TMP_CHAT_ID,
|
||||||
@ -37,10 +39,10 @@ import { debounce, pause, throttle } from '../../../util/schedulers';
|
|||||||
import { extractCurrentThemeParams } from '../../../util/themeStyle';
|
import { extractCurrentThemeParams } from '../../../util/themeStyle';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
import {
|
import {
|
||||||
|
getIsSavedDialog,
|
||||||
isChatArchived,
|
isChatArchived,
|
||||||
isChatBasicGroup,
|
isChatBasicGroup,
|
||||||
isChatChannel,
|
isChatChannel,
|
||||||
isChatSummaryOnly,
|
|
||||||
isChatSuperGroup,
|
isChatSuperGroup,
|
||||||
isUserBot,
|
isUserBot,
|
||||||
toChannelId,
|
toChannelId,
|
||||||
@ -58,6 +60,7 @@ import {
|
|||||||
addUsersToRestrictedInviteList,
|
addUsersToRestrictedInviteList,
|
||||||
deleteTopic,
|
deleteTopic,
|
||||||
leaveChat,
|
leaveChat,
|
||||||
|
removeChatFromChatLists,
|
||||||
replaceChatFullInfo,
|
replaceChatFullInfo,
|
||||||
replaceChatListIds,
|
replaceChatListIds,
|
||||||
replaceChats,
|
replaceChats,
|
||||||
@ -66,9 +69,11 @@ import {
|
|||||||
replaceUserStatuses,
|
replaceUserStatuses,
|
||||||
updateChat,
|
updateChat,
|
||||||
updateChatFullInfo,
|
updateChatFullInfo,
|
||||||
|
updateChatLastMessageId,
|
||||||
updateChatListIds,
|
updateChatListIds,
|
||||||
updateChatListSecondaryInfo,
|
updateChatListSecondaryInfo,
|
||||||
updateChats,
|
updateChats,
|
||||||
|
updateChatsLastMessageId,
|
||||||
updateListedTopicIds,
|
updateListedTopicIds,
|
||||||
updateManagementProgress,
|
updateManagementProgress,
|
||||||
updatePeerFullInfo,
|
updatePeerFullInfo,
|
||||||
@ -85,11 +90,14 @@ import {
|
|||||||
selectChatByUsername,
|
selectChatByUsername,
|
||||||
selectChatFolder,
|
selectChatFolder,
|
||||||
selectChatFullInfo,
|
selectChatFullInfo,
|
||||||
|
selectChatLastMessage,
|
||||||
|
selectChatLastMessageId,
|
||||||
selectChatListType,
|
selectChatListType,
|
||||||
selectCurrentChat,
|
selectCurrentChat,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectDraft,
|
selectDraft,
|
||||||
selectIsChatPinned,
|
selectIsChatPinned,
|
||||||
|
selectIsChatWithSelf,
|
||||||
selectLastServiceNotification,
|
selectLastServiceNotification,
|
||||||
selectStickerSet,
|
selectStickerSet,
|
||||||
selectSupportChat,
|
selectSupportChat,
|
||||||
@ -146,12 +154,12 @@ addActionHandler('preloadTopChatMessages', async (global, actions): Promise<void
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function abortChatRequests(chatId: string, threadId?: number) {
|
function abortChatRequests(chatId: string, threadId?: ThreadId) {
|
||||||
callApi('abortChatRequests', { chatId, threadId });
|
callApi('abortChatRequests', { chatId, threadId });
|
||||||
}
|
}
|
||||||
|
|
||||||
function abortChatRequestsForCurrentChat<T extends GlobalState>(
|
function abortChatRequestsForCurrentChat<T extends GlobalState>(
|
||||||
global: T, newChatId?: string, newThreadId?: number,
|
global: T, newChatId?: string, newThreadId?: ThreadId,
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
) {
|
) {
|
||||||
const currentMessageList = selectCurrentMessageList(global, tabId);
|
const currentMessageList = selectCurrentMessageList(global, tabId);
|
||||||
@ -202,15 +210,16 @@ addActionHandler('openChat', (global, actions, payload): ActionReturnType => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { currentUserId } = global;
|
|
||||||
const chat = selectChat(global, id);
|
const chat = selectChat(global, id);
|
||||||
|
|
||||||
if (chat?.hasUnreadMark) {
|
if (chat?.hasUnreadMark) {
|
||||||
actions.toggleChatUnread({ id });
|
actions.toggleChatUnread({ id });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isChatOnlySummary = !selectChatLastMessageId(global, id);
|
||||||
|
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
if (id === currentUserId) {
|
if (selectIsChatWithSelf(global, id)) {
|
||||||
void callApi('fetchChat', { type: 'self' });
|
void callApi('fetchChat', { type: 'self' });
|
||||||
} else {
|
} else {
|
||||||
const user = selectUser(global, id);
|
const user = selectUser(global, id);
|
||||||
@ -218,12 +227,23 @@ addActionHandler('openChat', (global, actions, payload): ActionReturnType => {
|
|||||||
void callApi('fetchChat', { type: 'user', user });
|
void callApi('fetchChat', { type: 'user', user });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (isChatSummaryOnly(chat) && !chat.isMin) {
|
} else if (isChatOnlySummary && !chat.isMin) {
|
||||||
actions.requestChatUpdate({ chatId: id });
|
actions.requestChatUpdate({ chatId: id });
|
||||||
}
|
}
|
||||||
actions.closeStoryViewer({ tabId });
|
actions.closeStoryViewer({ tabId });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('openSavedDialog', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { chatId, tabId = getCurrentTabId(), ...otherParams } = payload;
|
||||||
|
|
||||||
|
actions.openThread({
|
||||||
|
chatId: global.currentUserId!,
|
||||||
|
threadId: chatId,
|
||||||
|
tabId,
|
||||||
|
...otherParams,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('openThread', async (global, actions, payload): Promise<void> => {
|
addActionHandler('openThread', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
type, isComments, noForumTopicPanel, shouldReplaceHistory, shouldReplaceLast,
|
type, isComments, noForumTopicPanel, shouldReplaceHistory, shouldReplaceLast,
|
||||||
@ -231,9 +251,9 @@ addActionHandler('openThread', async (global, actions, payload): Promise<void> =
|
|||||||
tabId = getCurrentTabId(),
|
tabId = getCurrentTabId(),
|
||||||
} = payload;
|
} = payload;
|
||||||
let { chatId } = payload;
|
let { chatId } = payload;
|
||||||
let threadId: number | undefined;
|
let threadId: ThreadId | undefined;
|
||||||
let loadingChatId: string;
|
let loadingChatId: string;
|
||||||
let loadingThreadId: number;
|
let loadingThreadId: ThreadId;
|
||||||
|
|
||||||
if (!isComments) {
|
if (!isComments) {
|
||||||
loadingChatId = payload.chatId;
|
loadingChatId = payload.chatId;
|
||||||
@ -251,7 +271,7 @@ addActionHandler('openThread', async (global, actions, payload): Promise<void> =
|
|||||||
tabId,
|
tabId,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
} else if (originalChat?.isForum) {
|
} else if (originalChat?.isForum || (chatId && getIsSavedDialog(chatId, threadId, global.currentUserId))) {
|
||||||
actions.processOpenChatOrThread({
|
actions.processOpenChatOrThread({
|
||||||
chatId,
|
chatId,
|
||||||
type,
|
type,
|
||||||
@ -280,7 +300,7 @@ addActionHandler('openThread', async (global, actions, payload): Promise<void> =
|
|||||||
|
|
||||||
if (chatId
|
if (chatId
|
||||||
&& threadInfo?.threadId
|
&& threadInfo?.threadId
|
||||||
&& (isComments || (thread?.listedIds?.length && thread.listedIds.includes(threadInfo.threadId)))) {
|
&& (isComments || (thread?.listedIds?.length && thread.listedIds.includes(Number(threadInfo.threadId))))) {
|
||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
loadingThread: undefined,
|
loadingThread: undefined,
|
||||||
}, tabId);
|
}, tabId);
|
||||||
@ -306,7 +326,7 @@ addActionHandler('openThread', async (global, actions, payload): Promise<void> =
|
|||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
loadingThread: {
|
loadingThread: {
|
||||||
loadingChatId,
|
loadingChatId,
|
||||||
loadingMessageId: loadingThreadId,
|
loadingMessageId: Number(loadingThreadId),
|
||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
@ -337,7 +357,7 @@ addActionHandler('openThread', async (global, actions, payload): Promise<void> =
|
|||||||
|
|
||||||
const result = await callApi('fetchDiscussionMessage', {
|
const result = await callApi('fetchDiscussionMessage', {
|
||||||
chat: selectChat(global, loadingChatId)!,
|
chat: selectChat(global, loadingChatId)!,
|
||||||
messageId: loadingThreadId,
|
messageId: Number(loadingThreadId),
|
||||||
});
|
});
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
@ -476,13 +496,13 @@ addActionHandler('openSupportChat', async (global, actions, payload): Promise<vo
|
|||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('loadAllChats', async (global, actions, payload): Promise<void> => {
|
addActionHandler('loadAllChats', async (global, actions, payload): Promise<void> => {
|
||||||
const listType = payload.listType as 'active' | 'archived';
|
const listType = payload.listType;
|
||||||
const { onReplace } = payload;
|
const { onReplace } = payload;
|
||||||
let { shouldReplace } = payload;
|
let { shouldReplace } = payload;
|
||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
const getOrderDate = (chat: ApiChat) => {
|
const getOrderDate = (chat: ApiChat) => {
|
||||||
return chat.lastMessage?.date || chat.creationDate;
|
return selectChatLastMessage(global, chat.id)?.date || chat.creationDate;
|
||||||
};
|
};
|
||||||
|
|
||||||
while (shouldReplace || !global.chats.isFullyLoaded[listType]) {
|
while (shouldReplace || !global.chats.isFullyLoaded[listType]) {
|
||||||
@ -559,11 +579,12 @@ addActionHandler('loadTopChats', (): ActionReturnType => {
|
|||||||
runThrottledForLoadTopChats(() => {
|
runThrottledForLoadTopChats(() => {
|
||||||
loadChats('active');
|
loadChats('active');
|
||||||
loadChats('archived');
|
loadChats('archived');
|
||||||
|
loadChats('saved');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('requestChatUpdate', (global, actions, payload): ActionReturnType => {
|
addActionHandler('requestChatUpdate', (global, actions, payload): ActionReturnType => {
|
||||||
const { chatId } = payload!;
|
const { chatId } = payload;
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
return;
|
return;
|
||||||
@ -577,6 +598,49 @@ addActionHandler('requestChatUpdate', (global, actions, payload): ActionReturnTy
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('requestSavedDialogUpdate', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { chatId } = payload;
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
|
if (!chat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await callApi('fetchMessages', {
|
||||||
|
chat,
|
||||||
|
isSavedDialog: true,
|
||||||
|
limit: 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!result) return;
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
|
||||||
|
global = addMessages(global, result.messages);
|
||||||
|
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
||||||
|
global = addChats(global, buildCollectionByKey(result.chats, 'id'));
|
||||||
|
|
||||||
|
if (result.messages.length) {
|
||||||
|
global = updateChatLastMessageId(global, chatId, result.messages[0].id, 'saved');
|
||||||
|
global = updateChatListIds(global, 'saved', [chatId]);
|
||||||
|
|
||||||
|
setGlobal(global);
|
||||||
|
} else {
|
||||||
|
global = removeChatFromChatLists(global, chatId, 'saved');
|
||||||
|
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
||||||
|
const currentMessageList = selectCurrentMessageList(global, tabId);
|
||||||
|
if (!currentMessageList) return;
|
||||||
|
const { chatId: tabChatId, threadId } = currentMessageList;
|
||||||
|
|
||||||
|
if (selectIsChatWithSelf(global, tabChatId) && threadId === chatId) {
|
||||||
|
actions.openChat({ id: undefined, tabId });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('updateChatMutedState', (global, actions, payload): ActionReturnType => {
|
addActionHandler('updateChatMutedState', (global, actions, payload): ActionReturnType => {
|
||||||
const { chatId, muteUntil = 0 } = payload;
|
const { chatId, muteUntil = 0 } = payload;
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
@ -906,6 +970,28 @@ addActionHandler('toggleChatArchived', (global, actions, payload): ActionReturnT
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('toggleSavedDialogPinned', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { id, tabId = getCurrentTabId() } = payload!;
|
||||||
|
const chat = selectChat(global, id);
|
||||||
|
if (!chat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const limit = selectCurrentLimit(global, 'savedDialogsPinned');
|
||||||
|
|
||||||
|
const isPinned = selectIsChatPinned(global, id, SAVED_FOLDER_ID);
|
||||||
|
|
||||||
|
const ids = global.chats.orderedPinnedIds.saved;
|
||||||
|
if ((ids?.length || 0) >= limit && !isPinned) {
|
||||||
|
actions.openLimitReachedModal({
|
||||||
|
limit: 'savedDialogsPinned',
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
void callApi('toggleSavedDialogPinned', { chat, shouldBePinned: !isPinned });
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('loadChatFolders', async (global): Promise<void> => {
|
addActionHandler('loadChatFolders', async (global): Promise<void> => {
|
||||||
const chatFolders = await callApi('fetchChatFolders');
|
const chatFolders = await callApi('fetchChatFolders');
|
||||||
|
|
||||||
@ -1889,7 +1975,7 @@ addActionHandler('fetchChat', (global, actions, payload): ActionReturnType => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chatId === global.currentUserId) {
|
if (selectIsChatWithSelf(global, chatId)) {
|
||||||
void callApi('fetchChat', { type: 'self' });
|
void callApi('fetchChat', { type: 'self' });
|
||||||
} else {
|
} else {
|
||||||
const user = selectUser(global, chatId);
|
const user = selectUser(global, chatId);
|
||||||
@ -2545,7 +2631,7 @@ addActionHandler('fetchChannelRecommendations', async (global, actions, payload)
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function loadChats(
|
async function loadChats(
|
||||||
listType: 'active' | 'archived',
|
listType: ChatListType,
|
||||||
offsetId?: string,
|
offsetId?: string,
|
||||||
offsetDate?: number,
|
offsetDate?: number,
|
||||||
shouldReplace = false,
|
shouldReplace = false,
|
||||||
@ -2553,13 +2639,17 @@ async function loadChats(
|
|||||||
) {
|
) {
|
||||||
// eslint-disable-next-line eslint-multitab-tt/no-immediate-global
|
// eslint-disable-next-line eslint-multitab-tt/no-immediate-global
|
||||||
let global = getGlobal();
|
let global = getGlobal();
|
||||||
let lastLocalServiceMessage = selectLastServiceNotification(global)?.message;
|
let lastLocalServiceMessageId = selectLastServiceNotification(global)?.id;
|
||||||
const result = await callApi('fetchChats', {
|
const result = listType === 'saved' ? await callApi('fetchSavedChats', {
|
||||||
|
limit: CHAT_LIST_LOAD_SLICE,
|
||||||
|
offsetDate,
|
||||||
|
withPinned: shouldReplace,
|
||||||
|
}) : await callApi('fetchChats', {
|
||||||
limit: CHAT_LIST_LOAD_SLICE,
|
limit: CHAT_LIST_LOAD_SLICE,
|
||||||
offsetDate,
|
offsetDate,
|
||||||
archived: listType === 'archived',
|
archived: listType === 'archived',
|
||||||
withPinned: shouldReplace,
|
withPinned: shouldReplace,
|
||||||
lastLocalServiceMessage,
|
lastLocalServiceMessageId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
@ -2573,63 +2663,56 @@ async function loadChats(
|
|||||||
}
|
}
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
|
lastLocalServiceMessageId = selectLastServiceNotification(global)?.id;
|
||||||
|
|
||||||
lastLocalServiceMessage = selectLastServiceNotification(global)?.message;
|
if (shouldReplace) {
|
||||||
|
if (listType === 'active') {
|
||||||
|
// Always include service notifications chat
|
||||||
|
if (!chatIds.includes(SERVICE_NOTIFICATIONS_USER_ID)) {
|
||||||
|
const result2 = await callApi('fetchChat', {
|
||||||
|
type: 'user',
|
||||||
|
user: SERVICE_NOTIFICATIONS_USER_MOCK,
|
||||||
|
});
|
||||||
|
|
||||||
if (shouldReplace && listType === 'active') {
|
global = getGlobal();
|
||||||
// Always include service notifications chat
|
|
||||||
if (!chatIds.includes(SERVICE_NOTIFICATIONS_USER_ID)) {
|
|
||||||
const result2 = await callApi('fetchChat', {
|
|
||||||
type: 'user',
|
|
||||||
user: SERVICE_NOTIFICATIONS_USER_MOCK,
|
|
||||||
});
|
|
||||||
|
|
||||||
global = getGlobal();
|
const notificationsChat = result2 && selectChat(global, result2.chatId);
|
||||||
|
if (notificationsChat) {
|
||||||
const notificationsChat = result2 && selectChat(global, result2.chatId);
|
chatIds.unshift(notificationsChat.id);
|
||||||
if (notificationsChat) {
|
result.chats.unshift(notificationsChat);
|
||||||
chatIds.unshift(notificationsChat.id);
|
if (lastLocalServiceMessageId) {
|
||||||
result.chats.unshift(notificationsChat);
|
result.lastMessageByChatId[notificationsChat.id] = lastLocalServiceMessageId;
|
||||||
if (lastLocalServiceMessage) {
|
}
|
||||||
notificationsChat.lastMessage = lastLocalServiceMessage;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tabStates = Object.values(global.byTabId);
|
||||||
|
const visibleChats = tabStates.flatMap(({ id: tabId }) => {
|
||||||
|
const currentChat = selectCurrentChat(global, tabId);
|
||||||
|
return currentChat ? [currentChat] : [];
|
||||||
|
});
|
||||||
|
|
||||||
|
const visibleUsers = tabStates.flatMap(({ id: tabId }) => {
|
||||||
|
return selectVisibleUsers(global, tabId) || [];
|
||||||
|
});
|
||||||
|
|
||||||
|
if (global.currentUserId && global.users.byId[global.currentUserId]) {
|
||||||
|
visibleUsers.push(global.users.byId[global.currentUserId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
global = replaceUsers(global, buildCollectionByKey(visibleUsers.concat(result.users), 'id'));
|
||||||
|
global = replaceUserStatuses(global, result.userStatusesById);
|
||||||
|
global = replaceChats(global, buildCollectionByKey(visibleChats.concat(result.chats), 'id'));
|
||||||
|
global = replaceChatListIds(global, listType, chatIds);
|
||||||
|
} else {
|
||||||
|
// Archived and Saved
|
||||||
|
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
||||||
|
global = addUserStatuses(global, result.userStatusesById);
|
||||||
|
global = updateChats(global, buildCollectionByKey(result.chats, 'id'));
|
||||||
|
global = replaceChatListIds(global, listType, chatIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabStates = Object.values(global.byTabId);
|
|
||||||
const visibleChats = tabStates.flatMap(({ id: tabId }) => {
|
|
||||||
const currentChat = selectCurrentChat(global, tabId);
|
|
||||||
return currentChat ? [currentChat] : [];
|
|
||||||
});
|
|
||||||
|
|
||||||
const visibleUsers = tabStates.flatMap(({ id: tabId }) => {
|
|
||||||
return selectVisibleUsers(global, tabId) || [];
|
|
||||||
});
|
|
||||||
|
|
||||||
if (global.currentUserId && global.users.byId[global.currentUserId]) {
|
|
||||||
visibleUsers.push(global.users.byId[global.currentUserId]);
|
|
||||||
}
|
|
||||||
|
|
||||||
global = replaceUsers(global, buildCollectionByKey(visibleUsers.concat(result.users), 'id'));
|
|
||||||
global = replaceUserStatuses(global, result.userStatusesById);
|
|
||||||
global = replaceChats(global, buildCollectionByKey(visibleChats.concat(result.chats), 'id'));
|
|
||||||
global = replaceChatListIds(global, listType, chatIds);
|
|
||||||
} else if (shouldReplace && listType === 'archived') {
|
|
||||||
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
|
||||||
global = addUserStatuses(global, result.userStatusesById);
|
|
||||||
global = updateChats(global, buildCollectionByKey(result.chats, 'id'));
|
|
||||||
global = replaceChatListIds(global, listType, chatIds);
|
|
||||||
} else {
|
} else {
|
||||||
const newChats = buildCollectionByKey(result.chats, 'id');
|
const newChats = buildCollectionByKey(result.chats, 'id');
|
||||||
if (chatIds.includes(SERVICE_NOTIFICATIONS_USER_ID)) {
|
|
||||||
const notificationsChat = newChats[SERVICE_NOTIFICATIONS_USER_ID];
|
|
||||||
if (notificationsChat && lastLocalServiceMessage) {
|
|
||||||
newChats[SERVICE_NOTIFICATIONS_USER_ID] = {
|
|
||||||
...notificationsChat,
|
|
||||||
lastMessage: lastLocalServiceMessage,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
global = addUsers(global, buildCollectionByKey(result.users, 'id'));
|
||||||
global = addUserStatuses(global, result.userStatusesById);
|
global = addUserStatuses(global, result.userStatusesById);
|
||||||
@ -2638,6 +2721,8 @@ async function loadChats(
|
|||||||
}
|
}
|
||||||
|
|
||||||
global = updateChatListSecondaryInfo(global, listType, result);
|
global = updateChatListSecondaryInfo(global, listType, result);
|
||||||
|
global = addMessages(global, result.messages);
|
||||||
|
global = updateChatsLastMessageId(global, result.lastMessageByChatId, listType);
|
||||||
|
|
||||||
const idsToUpdateDraft = isFullDraftSync ? result.chatIds : Object.keys(result.draftsById);
|
const idsToUpdateDraft = isFullDraftSync ? result.chatIds : Object.keys(result.draftsById);
|
||||||
idsToUpdateDraft.forEach((chatId) => {
|
idsToUpdateDraft.forEach((chatId) => {
|
||||||
@ -2653,7 +2738,7 @@ async function loadChats(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (chatIds.length === 0 && !global.chats.isFullyLoaded[listType]) {
|
if ((chatIds.length === 0 || chatIds.length === result.totalChatCount) && !global.chats.isFullyLoaded[listType]) {
|
||||||
global = {
|
global = {
|
||||||
...global,
|
...global,
|
||||||
chats: {
|
chats: {
|
||||||
@ -2831,7 +2916,7 @@ async function openChatByUsername<T extends GlobalState>(
|
|||||||
global: T,
|
global: T,
|
||||||
actions: RequiredGlobalActions,
|
actions: RequiredGlobalActions,
|
||||||
username: string,
|
username: string,
|
||||||
threadId?: number,
|
threadId?: ThreadId,
|
||||||
channelPostId?: number,
|
channelPostId?: number,
|
||||||
startParam?: string,
|
startParam?: string,
|
||||||
startAttach?: string,
|
startAttach?: string,
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import type { ApiChat } from '../../../api/types';
|
import type { ApiChat } from '../../../api/types';
|
||||||
import type { SharedMediaType } from '../../../types';
|
import type { SharedMediaType, ThreadId } from '../../../types';
|
||||||
import type { ActionReturnType, GlobalState, TabArgs } from '../../types';
|
import type { ActionReturnType, GlobalState, TabArgs } from '../../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';
|
||||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
|
import { getIsSavedDialog } from '../../helpers';
|
||||||
import {
|
import {
|
||||||
addActionHandler, getGlobal, setGlobal,
|
addActionHandler, getGlobal, setGlobal,
|
||||||
} from '../../index';
|
} from '../../index';
|
||||||
@ -26,7 +27,14 @@ import {
|
|||||||
addActionHandler('searchTextMessagesLocal', async (global, actions, payload): Promise<void> => {
|
addActionHandler('searchTextMessagesLocal', async (global, actions, payload): Promise<void> => {
|
||||||
const { tabId = getCurrentTabId() } = payload || {};
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
const { chatId, threadId } = selectCurrentMessageList(global, tabId) || {};
|
const { chatId, threadId } = selectCurrentMessageList(global, tabId) || {};
|
||||||
const chat = chatId ? selectChat(global, chatId) : undefined;
|
|
||||||
|
if (!chatId) return;
|
||||||
|
|
||||||
|
const currentUserId = global.currentUserId!;
|
||||||
|
const isSavedDialog = getIsSavedDialog(chatId, threadId, currentUserId);
|
||||||
|
const realChatId = isSavedDialog ? String(threadId) : chatId;
|
||||||
|
|
||||||
|
const chat = realChatId ? selectChat(global, realChatId) : undefined;
|
||||||
let currentSearch = selectCurrentTextSearch(global, tabId);
|
let currentSearch = selectCurrentTextSearch(global, tabId);
|
||||||
if (!chat || !currentSearch || !threadId) {
|
if (!chat || !currentSearch || !threadId) {
|
||||||
return;
|
return;
|
||||||
@ -46,6 +54,7 @@ addActionHandler('searchTextMessagesLocal', async (global, actions, payload): Pr
|
|||||||
threadId,
|
threadId,
|
||||||
limit: MESSAGE_SEARCH_SLICE,
|
limit: MESSAGE_SEARCH_SLICE,
|
||||||
offsetId,
|
offsetId,
|
||||||
|
isSavedDialog,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
@ -66,10 +75,12 @@ addActionHandler('searchTextMessagesLocal', async (global, actions, payload): Pr
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const resultChatId = isSavedDialog ? currentUserId : chat.id;
|
||||||
|
|
||||||
global = addChats(global, buildCollectionByKey(chats, 'id'));
|
global = addChats(global, buildCollectionByKey(chats, 'id'));
|
||||||
global = addUsers(global, buildCollectionByKey(users, 'id'));
|
global = addUsers(global, buildCollectionByKey(users, 'id'));
|
||||||
global = addChatMessagesById(global, chat.id, byId);
|
global = addChatMessagesById(global, resultChatId, byId);
|
||||||
global = updateLocalTextSearchResults(global, chat.id, threadId, newFoundIds, totalCount, nextOffsetId, tabId);
|
global = updateLocalTextSearchResults(global, resultChatId, threadId, newFoundIds, totalCount, nextOffsetId, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -80,7 +91,10 @@ addActionHandler('searchMediaMessagesLocal', (global, actions, payload): ActionR
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const chat = selectChat(global, chatId);
|
const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId);
|
||||||
|
const realChatId = isSavedDialog ? String(threadId) : chatId;
|
||||||
|
|
||||||
|
const chat = selectChat(global, realChatId);
|
||||||
const currentSearch = selectCurrentMediaSearch(global, tabId);
|
const currentSearch = selectCurrentMediaSearch(global, tabId);
|
||||||
|
|
||||||
if (!chat || !currentSearch) {
|
if (!chat || !currentSearch) {
|
||||||
@ -95,7 +109,7 @@ addActionHandler('searchMediaMessagesLocal', (global, actions, payload): ActionR
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
void searchSharedMedia(global, chat, threadId, type, offsetId, undefined, tabId);
|
void searchSharedMedia(global, chat, threadId, type, offsetId, undefined, isSavedDialog, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('searchMessagesByDate', async (global, actions, payload): Promise<void> => {
|
addActionHandler('searchMessagesByDate', async (global, actions, payload): Promise<void> => {
|
||||||
@ -130,18 +144,22 @@ addActionHandler('searchMessagesByDate', async (global, actions, payload): Promi
|
|||||||
async function searchSharedMedia<T extends GlobalState>(
|
async function searchSharedMedia<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
chat: ApiChat,
|
chat: ApiChat,
|
||||||
threadId: number,
|
threadId: ThreadId,
|
||||||
type: SharedMediaType,
|
type: SharedMediaType,
|
||||||
offsetId?: number,
|
offsetId?: number,
|
||||||
isBudgetPreload = false,
|
isBudgetPreload = false,
|
||||||
|
isSavedDialog?: boolean,
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
) {
|
) {
|
||||||
|
const resultChatId = isSavedDialog ? global.currentUserId! : chat.id;
|
||||||
|
|
||||||
const result = await callApi('searchMessagesLocal', {
|
const result = await callApi('searchMessagesLocal', {
|
||||||
chat,
|
chat,
|
||||||
type,
|
type,
|
||||||
limit: SHARED_MEDIA_SLICE * 2,
|
limit: SHARED_MEDIA_SLICE * 2,
|
||||||
threadId,
|
threadId,
|
||||||
offsetId,
|
offsetId,
|
||||||
|
isSavedDialog,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
@ -164,11 +182,13 @@ async function searchSharedMedia<T extends GlobalState>(
|
|||||||
|
|
||||||
global = addChats(global, buildCollectionByKey(chats, 'id'));
|
global = addChats(global, buildCollectionByKey(chats, 'id'));
|
||||||
global = addUsers(global, buildCollectionByKey(users, 'id'));
|
global = addUsers(global, buildCollectionByKey(users, 'id'));
|
||||||
global = addChatMessagesById(global, chat.id, byId);
|
global = addChatMessagesById(global, resultChatId, byId);
|
||||||
global = updateLocalMediaSearchResults(global, chat.id, threadId, type, newFoundIds, totalCount, nextOffsetId, tabId);
|
global = updateLocalMediaSearchResults(
|
||||||
|
global, resultChatId, threadId, type, newFoundIds, totalCount, nextOffsetId, tabId,
|
||||||
|
);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
if (!isBudgetPreload) {
|
if (!isBudgetPreload) {
|
||||||
void searchSharedMedia(global, chat, threadId, type, nextOffsetId, true, tabId);
|
void searchSharedMedia(global, chat, threadId, type, nextOffsetId, true, isSavedDialog, tabId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import type {
|
|||||||
ActionReturnType, ApiDraft, GlobalState, TabArgs,
|
ActionReturnType, ApiDraft, GlobalState, TabArgs,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import { MAIN_THREAD_ID, MESSAGE_DELETED } from '../../../api/types';
|
import { MAIN_THREAD_ID, MESSAGE_DELETED } from '../../../api/types';
|
||||||
import { LoadMoreDirection } from '../../../types';
|
import { LoadMoreDirection, type ThreadId } from '../../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GIF_MIME_TYPE,
|
GIF_MIME_TYPE,
|
||||||
@ -44,6 +44,7 @@ import {
|
|||||||
import { IS_IOS } from '../../../util/windowEnvironment';
|
import { IS_IOS } from '../../../util/windowEnvironment';
|
||||||
import { callApi, cancelApiProgress } from '../../../api/gramjs';
|
import { callApi, cancelApiProgress } from '../../../api/gramjs';
|
||||||
import {
|
import {
|
||||||
|
getIsSavedDialog,
|
||||||
getMessageOriginalId,
|
getMessageOriginalId,
|
||||||
getUserFullName,
|
getUserFullName,
|
||||||
isChatChannel,
|
isChatChannel,
|
||||||
@ -83,6 +84,7 @@ import { updateTabState } from '../../reducers/tabs';
|
|||||||
import {
|
import {
|
||||||
selectChat,
|
selectChat,
|
||||||
selectChatFullInfo,
|
selectChatFullInfo,
|
||||||
|
selectChatLastMessageId,
|
||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
selectCurrentChat,
|
selectCurrentChat,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
@ -96,6 +98,7 @@ import {
|
|||||||
selectFocusedMessageId,
|
selectFocusedMessageId,
|
||||||
selectForwardsCanBeSentToChat,
|
selectForwardsCanBeSentToChat,
|
||||||
selectForwardsContainVoiceMessages,
|
selectForwardsContainVoiceMessages,
|
||||||
|
selectIsChatWithSelf,
|
||||||
selectIsCurrentUserPremium,
|
selectIsCurrentUserPremium,
|
||||||
selectLanguageCode,
|
selectLanguageCode,
|
||||||
selectListedIds,
|
selectListedIds,
|
||||||
@ -227,7 +230,7 @@ async function loadWithBudget<T extends GlobalState>(
|
|||||||
global: T,
|
global: T,
|
||||||
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: ThreadId, direction: LoadMoreDirection, offsetId?: number,
|
||||||
onLoaded?: NoneToVoidFunction,
|
onLoaded?: NoneToVoidFunction,
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
) {
|
) {
|
||||||
@ -308,6 +311,7 @@ addActionHandler('sendMessage', (global, actions, payload): ActionReturnType =>
|
|||||||
const messageReplyInfo = selectMessageReplyInfo(global, chatId!, threadId!, draftReplyInfo);
|
const messageReplyInfo = selectMessageReplyInfo(global, chatId!, threadId!, draftReplyInfo);
|
||||||
|
|
||||||
const replyInfo = storyReplyInfo || messageReplyInfo;
|
const replyInfo = storyReplyInfo || messageReplyInfo;
|
||||||
|
const lastMessageId = selectChatLastMessageId(global, chatId!);
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
...payload,
|
...payload,
|
||||||
@ -315,6 +319,7 @@ addActionHandler('sendMessage', (global, actions, payload): ActionReturnType =>
|
|||||||
replyInfo,
|
replyInfo,
|
||||||
noWebPage: selectNoWebPage(global, chatId!, threadId!),
|
noWebPage: selectNoWebPage(global, chatId!, threadId!),
|
||||||
sendAs: selectSendAs(global, chatId!),
|
sendAs: selectSendAs(global, chatId!),
|
||||||
|
lastMessageId,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!isStoryReply) {
|
if (!isStoryReply) {
|
||||||
@ -545,7 +550,7 @@ addActionHandler('resetDraftReplyInfo', (global, actions, payload): ActionReturn
|
|||||||
async function saveDraft<T extends GlobalState>({
|
async function saveDraft<T extends GlobalState>({
|
||||||
global, chatId, threadId, draft, isLocalOnly, noLocalTimeUpdate,
|
global, chatId, threadId, draft, isLocalOnly, noLocalTimeUpdate,
|
||||||
} : {
|
} : {
|
||||||
global: T; chatId: string; threadId: number; draft?: ApiDraft; isLocalOnly?: boolean; noLocalTimeUpdate?: boolean;
|
global: T; chatId: string; threadId: ThreadId; draft?: ApiDraft; isLocalOnly?: boolean; noLocalTimeUpdate?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
const user = selectUser(global, chatId);
|
const user = selectUser(global, chatId);
|
||||||
@ -732,7 +737,7 @@ addActionHandler('reportMessages', async (global, actions, payload): Promise<voi
|
|||||||
addActionHandler('sendMessageAction', async (global, actions, payload): Promise<void> => {
|
addActionHandler('sendMessageAction', async (global, actions, payload): Promise<void> => {
|
||||||
const { action, chatId, threadId } = payload!;
|
const { action, chatId, threadId } = payload!;
|
||||||
if (global.connectionState !== 'connectionStateReady') return;
|
if (global.connectionState !== 'connectionStateReady') return;
|
||||||
if (chatId === global.currentUserId) return; // Message actions are disabled in Saved Messages
|
if (selectIsChatWithSelf(global, chatId)) return;
|
||||||
|
|
||||||
const chat = selectChat(global, chatId)!;
|
const chat = selectChat(global, chatId)!;
|
||||||
if (!chat) return;
|
if (!chat) return;
|
||||||
@ -754,7 +759,7 @@ addActionHandler('markMessageListRead', (global, actions, payload): ActionReturn
|
|||||||
|
|
||||||
const { chatId, threadId } = currentMessageList;
|
const { chatId, threadId } = currentMessageList;
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) {
|
if (!chat || getIsSavedDialog(chatId, threadId, global.currentUserId)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -803,7 +808,7 @@ addActionHandler('markMessageListRead', (global, actions, payload): ActionReturn
|
|||||||
unreadCount: Math.max(0, chat.unreadCount - 1),
|
unreadCount: Math.max(0, chat.unreadCount - 1),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return updateTopic(global, chatId, threadId, {
|
return updateTopic(global, chatId, Number(threadId), {
|
||||||
unreadCount: newTopicUnreadCount,
|
unreadCount: newTopicUnreadCount,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -951,6 +956,7 @@ addActionHandler('forwardMessages', (global, actions, payload): ActionReturnType
|
|||||||
|
|
||||||
const sendAs = selectSendAs(global, toChatId!);
|
const sendAs = selectSendAs(global, toChatId!);
|
||||||
const draft = selectDraft(global, toChatId!, toThreadId || MAIN_THREAD_ID);
|
const draft = selectDraft(global, toChatId!, toThreadId || MAIN_THREAD_ID);
|
||||||
|
const lastMessageId = selectChatLastMessageId(global, toChat.id);
|
||||||
|
|
||||||
const [realMessages, serviceMessages] = partition(messages, (m) => !isServiceNotificationMessage(m));
|
const [realMessages, serviceMessages] = partition(messages, (m) => !isServiceNotificationMessage(m));
|
||||||
if (realMessages.length) {
|
if (realMessages.length) {
|
||||||
@ -969,6 +975,7 @@ addActionHandler('forwardMessages', (global, actions, payload): ActionReturnType
|
|||||||
noCaptions,
|
noCaptions,
|
||||||
isCurrentUserPremium,
|
isCurrentUserPremium,
|
||||||
wasDrafted: Boolean(draft),
|
wasDrafted: Boolean(draft),
|
||||||
|
lastMessageId,
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
@ -990,6 +997,7 @@ addActionHandler('forwardMessages', (global, actions, payload): ActionReturnType
|
|||||||
isSilent,
|
isSilent,
|
||||||
scheduledAt,
|
scheduledAt,
|
||||||
sendAs,
|
sendAs,
|
||||||
|
lastMessageId,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1021,7 +1029,7 @@ addActionHandler('loadScheduledHistory', async (global, actions, payload): Promi
|
|||||||
global = replaceScheduledMessages(global, chat.id, byId);
|
global = replaceScheduledMessages(global, chat.id, byId);
|
||||||
global = replaceThreadParam(global, chat.id, MAIN_THREAD_ID, 'scheduledIds', ids);
|
global = replaceThreadParam(global, chat.id, MAIN_THREAD_ID, 'scheduledIds', ids);
|
||||||
if (chat?.isForum) {
|
if (chat?.isForum) {
|
||||||
const scheduledPerThread: Record<number, number[]> = {};
|
const scheduledPerThread: Record<ThreadId, number[]> = {};
|
||||||
messages.forEach((message) => {
|
messages.forEach((message) => {
|
||||||
const threadId = selectThreadIdFromMessage(global, message);
|
const threadId = selectThreadIdFromMessage(global, message);
|
||||||
const scheduledInThread = scheduledPerThread[threadId] || [];
|
const scheduledInThread = scheduledPerThread[threadId] || [];
|
||||||
@ -1121,7 +1129,7 @@ addActionHandler('loadCustomEmojis', async (global, actions, payload): Promise<v
|
|||||||
async function loadViewportMessages<T extends GlobalState>(
|
async function loadViewportMessages<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
chat: ApiChat,
|
chat: ApiChat,
|
||||||
threadId: number,
|
threadId: ThreadId,
|
||||||
offsetId: number | undefined,
|
offsetId: number | undefined,
|
||||||
direction: LoadMoreDirection,
|
direction: LoadMoreDirection,
|
||||||
isOutlying = false,
|
isOutlying = false,
|
||||||
@ -1154,12 +1162,18 @@ async function loadViewportMessages<T extends GlobalState>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
|
|
||||||
|
const currentUserId = global.currentUserId!;
|
||||||
|
const isSavedDialog = getIsSavedDialog(chatId, threadId, currentUserId);
|
||||||
|
const realChatId = isSavedDialog ? String(threadId) : chatId;
|
||||||
|
|
||||||
const result = await callApi('fetchMessages', {
|
const result = await callApi('fetchMessages', {
|
||||||
chat: selectChat(global, chatId)!,
|
chat: selectChat(global, realChatId)!,
|
||||||
offsetId,
|
offsetId,
|
||||||
addOffset,
|
addOffset,
|
||||||
limit: sliceSize,
|
limit: sliceSize,
|
||||||
threadId,
|
threadId,
|
||||||
|
isSavedDialog,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
@ -1179,10 +1193,10 @@ async function loadViewportMessages<T extends GlobalState>(
|
|||||||
const byId = buildCollectionByKey(allMessages, 'id');
|
const byId = buildCollectionByKey(allMessages, 'id');
|
||||||
const ids = Object.keys(byId).map(Number);
|
const ids = Object.keys(byId).map(Number);
|
||||||
|
|
||||||
if (threadId !== MAIN_THREAD_ID) {
|
if (threadId !== MAIN_THREAD_ID && !getIsSavedDialog(chatId, threadId, global.currentUserId)) {
|
||||||
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(Number(threadId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1314,6 +1328,7 @@ async function sendMessage<T extends GlobalState>(global: T, params: {
|
|||||||
sendAs?: ApiPeer;
|
sendAs?: ApiPeer;
|
||||||
groupedId?: string;
|
groupedId?: string;
|
||||||
wasDrafted?: boolean;
|
wasDrafted?: boolean;
|
||||||
|
lastMessageId?: number;
|
||||||
}) {
|
}) {
|
||||||
let localId: number | undefined;
|
let localId: number | undefined;
|
||||||
const progressCallback = params.attachment ? (progress: number, messageLocalId: number) => {
|
const progressCallback = params.attachment ? (progress: number, messageLocalId: number) => {
|
||||||
@ -1351,7 +1366,7 @@ async function sendMessage<T extends GlobalState>(global: T, params: {
|
|||||||
addActionHandler('loadPinnedMessages', async (global, actions, payload): Promise<void> => {
|
addActionHandler('loadPinnedMessages', async (global, actions, payload): Promise<void> => {
|
||||||
const { chatId, threadId } = payload;
|
const { chatId, threadId } = payload;
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) {
|
if (!chat || getIsSavedDialog(chatId, threadId, global.currentUserId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1651,12 +1666,15 @@ addActionHandler('forwardStory', (global, actions, payload): ActionReturnType =>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const lastMessageId = selectChatLastMessageId(global, toChatId);
|
||||||
|
|
||||||
const { text, entities } = (story as ApiStory).content.text || {};
|
const { text, entities } = (story as ApiStory).content.text || {};
|
||||||
void sendMessage(global, {
|
void sendMessage(global, {
|
||||||
chat: toChat,
|
chat: toChat,
|
||||||
text,
|
text,
|
||||||
entities,
|
entities,
|
||||||
story,
|
story,
|
||||||
|
lastMessageId,
|
||||||
});
|
});
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
|
|||||||
@ -25,6 +25,7 @@ import {
|
|||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
selectCurrentChat,
|
selectCurrentChat,
|
||||||
selectDefaultReaction,
|
selectDefaultReaction,
|
||||||
|
selectIsChatWithSelf,
|
||||||
selectMaxUserReactions,
|
selectMaxUserReactions,
|
||||||
selectMessageIdsByGroupId,
|
selectMessageIdsByGroupId,
|
||||||
selectPerformanceSettingsValue,
|
selectPerformanceSettingsValue,
|
||||||
@ -98,7 +99,7 @@ addActionHandler('sendEmojiInteraction', (global, actions, payload): ActionRetur
|
|||||||
|
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
|
|
||||||
if (!chat || !emoji || chatId === global.currentUserId) {
|
if (!chat || !emoji || selectIsChatWithSelf(global, chatId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -314,7 +315,7 @@ addActionHandler('sendWatchingEmojiInteraction', (global, actions, payload): Act
|
|||||||
|
|
||||||
const tabState = selectTabState(global, tabId);
|
const tabState = selectTabState(global, tabId);
|
||||||
if (!chat || !tabState.activeEmojiInteractions?.some((interaction) => interaction.id === id)
|
if (!chat || !tabState.activeEmojiInteractions?.some((interaction) => interaction.id === id)
|
||||||
|| chatId === global.currentUserId) {
|
|| selectIsChatWithSelf(global, chatId)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { addCallback } from '../../../lib/teact/teactn';
|
import { addCallback } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
import type { ApiChat } from '../../../api/types';
|
import type { ThreadId } from '../../../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';
|
||||||
@ -11,11 +11,13 @@ import {
|
|||||||
buildCollectionByKey, omitUndefined, pick, unique,
|
buildCollectionByKey, omitUndefined, pick, unique,
|
||||||
} from '../../../util/iteratees';
|
} from '../../../util/iteratees';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
|
import { getIsSavedDialog } from '../../helpers';
|
||||||
import {
|
import {
|
||||||
addActionHandler, getActions, getGlobal, setGlobal,
|
addActionHandler, getActions, getGlobal, setGlobal,
|
||||||
} from '../../index';
|
} from '../../index';
|
||||||
import {
|
import {
|
||||||
addChatMessagesById,
|
addChatMessagesById,
|
||||||
|
addMessages,
|
||||||
safeReplaceViewportIds,
|
safeReplaceViewportIds,
|
||||||
updateChats,
|
updateChats,
|
||||||
updateListedIds,
|
updateListedIds,
|
||||||
@ -25,6 +27,7 @@ import {
|
|||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import {
|
import {
|
||||||
|
selectChat,
|
||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
selectChatMessages,
|
selectChatMessages,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
@ -87,6 +90,7 @@ addActionHandler('sync', (global, actions): ActionReturnType => {
|
|||||||
|
|
||||||
initFolderManager();
|
initFolderManager();
|
||||||
loadAllChats({ listType: 'archived', shouldReplace: true });
|
loadAllChats({ listType: 'archived', shouldReplace: true });
|
||||||
|
loadAllChats({ listType: 'saved', shouldReplace: true });
|
||||||
void callApi('fetchCurrentUser');
|
void callApi('fetchCurrentUser');
|
||||||
preloadTopChatMessages();
|
preloadTopChatMessages();
|
||||||
loadAllStories();
|
loadAllStories();
|
||||||
@ -121,6 +125,14 @@ async function loadAndReplaceMessages<T extends GlobalState>(global: T, actions:
|
|||||||
}, {});
|
}, {});
|
||||||
/* eslint-enable @typescript-eslint/indent */
|
/* eslint-enable @typescript-eslint/indent */
|
||||||
|
|
||||||
|
// Memoize last messages
|
||||||
|
const lastMessages = Object.entries(global.chats.lastMessageIds.all || {}).map(([chatId, messageId]) => (
|
||||||
|
selectChatMessage(global, chatId, Number(messageId))
|
||||||
|
)).filter(Boolean);
|
||||||
|
const savedLastMessages = Object.values(global.chats.lastMessageIds.saved || {}).map((messageId) => (
|
||||||
|
selectChatMessage(global, global.currentUserId!, Number(messageId))
|
||||||
|
)).filter(Boolean);
|
||||||
|
|
||||||
for (const { id: tabId } of Object.values(global.byTabId)) {
|
for (const { id: tabId } of Object.values(global.byTabId)) {
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
const { chatId: currentChatId, threadId: currentThreadId } = selectCurrentMessageList(global, tabId) || {};
|
const { chatId: currentChatId, threadId: currentThreadId } = selectCurrentMessageList(global, tabId) || {};
|
||||||
@ -131,14 +143,15 @@ async function loadAndReplaceMessages<T extends GlobalState>(global: T, actions:
|
|||||||
if (currentChatId && currentChat) {
|
if (currentChatId && currentChat) {
|
||||||
const [result, resultDiscussion] = await Promise.all([
|
const [result, resultDiscussion] = await Promise.all([
|
||||||
loadTopMessages(
|
loadTopMessages(
|
||||||
currentChat,
|
global,
|
||||||
|
currentChatId,
|
||||||
activeThreadId,
|
activeThreadId,
|
||||||
activeThreadId !== MAIN_THREAD_ID ? activeThreadId : undefined,
|
|
||||||
),
|
),
|
||||||
activeThreadId !== MAIN_THREAD_ID ? callApi('fetchDiscussionMessage', {
|
activeThreadId !== MAIN_THREAD_ID && !getIsSavedDialog(currentChat.id, activeThreadId, global.currentUserId)
|
||||||
chat: currentChat,
|
? callApi('fetchDiscussionMessage', {
|
||||||
messageId: activeThreadId,
|
chat: currentChat,
|
||||||
}) : undefined,
|
messageId: Number(activeThreadId),
|
||||||
|
}) : undefined,
|
||||||
]);
|
]);
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
const { chatId: newCurrentChatId } = selectCurrentMessageList(global, tabId) || {};
|
const { chatId: newCurrentChatId } = selectCurrentMessageList(global, tabId) || {};
|
||||||
@ -212,7 +225,7 @@ async function loadAndReplaceMessages<T extends GlobalState>(global: T, actions:
|
|||||||
actions.loadTopics({ chatId: currentChatId!, force: true });
|
actions.loadTopics({ chatId: currentChatId!, force: true });
|
||||||
if (currentThreadId && currentThreadId !== MAIN_THREAD_ID) {
|
if (currentThreadId && currentThreadId !== MAIN_THREAD_ID) {
|
||||||
actions.loadTopicById({
|
actions.loadTopicById({
|
||||||
chatId: currentChatId!, topicId: currentThreadId, shouldCloseChatOnError: true,
|
chatId: currentChatId!, topicId: Number(currentThreadId), shouldCloseChatOnError: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -245,6 +258,10 @@ async function loadAndReplaceMessages<T extends GlobalState>(global: T, actions:
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Restore last messages
|
||||||
|
global = addMessages(global, lastMessages);
|
||||||
|
global = addMessages(global, savedLastMessages);
|
||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
||||||
@ -255,13 +272,20 @@ async function loadAndReplaceMessages<T extends GlobalState>(global: T, actions:
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadTopMessages(chat: ApiChat, threadId: number, offsetId?: number) {
|
function loadTopMessages<T extends GlobalState>(global: T, chatId: string, threadId: ThreadId) {
|
||||||
|
const currentUserId = global.currentUserId!;
|
||||||
|
const isSavedDialog = getIsSavedDialog(chatId, threadId, currentUserId);
|
||||||
|
const realChatId = isSavedDialog ? String(threadId) : chatId;
|
||||||
|
|
||||||
|
const chat = selectChat(global, realChatId)!;
|
||||||
|
|
||||||
return callApi('fetchMessages', {
|
return callApi('fetchMessages', {
|
||||||
chat,
|
chat,
|
||||||
threadId,
|
threadId,
|
||||||
offsetId: offsetId || chat.lastReadInboxMessageId,
|
offsetId: !isSavedDialog ? chat.lastReadInboxMessageId : undefined,
|
||||||
addOffset: -(Math.round(MESSAGE_LIST_SLICE / 2) + 1),
|
addOffset: -(Math.round(MESSAGE_LIST_SLICE / 2) + 1),
|
||||||
limit: MESSAGE_LIST_SLICE,
|
limit: MESSAGE_LIST_SLICE,
|
||||||
|
isSavedDialog,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -219,6 +219,21 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'updatePinnedSavedDialogIds': {
|
||||||
|
const { ids } = update;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...global,
|
||||||
|
chats: {
|
||||||
|
...global.chats,
|
||||||
|
orderedPinnedIds: {
|
||||||
|
...global.chats.orderedPinnedIds,
|
||||||
|
saved: ids.length ? ids : undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case 'updateChatPinned': {
|
case 'updateChatPinned': {
|
||||||
const { id, isPinned } = update;
|
const { id, isPinned } = update;
|
||||||
const listType = selectChatListType(global, id);
|
const listType = selectChatListType(global, id);
|
||||||
@ -256,6 +271,30 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'updateSavedDialogPinned': {
|
||||||
|
const { id, isPinned } = update;
|
||||||
|
|
||||||
|
const { saved: orderedPinnedIds } = global.chats.orderedPinnedIds;
|
||||||
|
|
||||||
|
let newOrderedPinnedIds = orderedPinnedIds || [];
|
||||||
|
if (!isPinned) {
|
||||||
|
newOrderedPinnedIds = newOrderedPinnedIds.filter((pinnedId) => pinnedId !== id);
|
||||||
|
} else if (!newOrderedPinnedIds.includes(id)) {
|
||||||
|
newOrderedPinnedIds = [id, ...newOrderedPinnedIds];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...global,
|
||||||
|
chats: {
|
||||||
|
...global.chats,
|
||||||
|
orderedPinnedIds: {
|
||||||
|
...global.chats.orderedPinnedIds,
|
||||||
|
saved: newOrderedPinnedIds.length ? newOrderedPinnedIds : undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
case 'updateChatListType': {
|
case 'updateChatListType': {
|
||||||
const { id, folderId } = update;
|
const { id, folderId } = update;
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import type {
|
import type {
|
||||||
ApiChat, ApiMessage, ApiPollResult, ApiReactions,
|
ApiChat, ApiMessage, ApiPollResult, ApiReactions,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
|
import type { ThreadId } from '../../../types';
|
||||||
import type { RequiredGlobalActions } from '../../index';
|
import type { RequiredGlobalActions } from '../../index';
|
||||||
import type {
|
import type {
|
||||||
ActionReturnType, ActiveEmojiInteraction, GlobalState, RequiredGlobalState,
|
ActionReturnType, ActiveEmojiInteraction, GlobalState, RequiredGlobalState,
|
||||||
@ -14,12 +15,13 @@ import { omit, pickTruthy, unique } from '../../../util/iteratees';
|
|||||||
import { notifyAboutMessage } from '../../../util/notifications';
|
import { notifyAboutMessage } from '../../../util/notifications';
|
||||||
import { onTickEnd } from '../../../util/schedulers';
|
import { onTickEnd } from '../../../util/schedulers';
|
||||||
import {
|
import {
|
||||||
checkIfHasUnreadReactions, getMessageContent, getMessageText, isActionMessage,
|
checkIfHasUnreadReactions, getIsSavedDialog, getMessageContent, getMessageText, isActionMessage,
|
||||||
isMessageLocal, isUserId,
|
isMessageLocal, isUserId,
|
||||||
} from '../../helpers';
|
} from '../../helpers';
|
||||||
import { getMessageReplyInfo, getStoryReplyInfo } from '../../helpers/replies';
|
import { getMessageReplyInfo, getStoryReplyInfo } from '../../helpers/replies';
|
||||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||||
import {
|
import {
|
||||||
|
addMessages,
|
||||||
addViewportId,
|
addViewportId,
|
||||||
clearMessageTranslation,
|
clearMessageTranslation,
|
||||||
deleteChatMessages,
|
deleteChatMessages,
|
||||||
@ -28,6 +30,7 @@ import {
|
|||||||
removeChatFromChatLists,
|
removeChatFromChatLists,
|
||||||
replaceThreadParam,
|
replaceThreadParam,
|
||||||
updateChat,
|
updateChat,
|
||||||
|
updateChatLastMessageId,
|
||||||
updateChatMessage,
|
updateChatMessage,
|
||||||
updateListedIds,
|
updateListedIds,
|
||||||
updateMessageTranslations,
|
updateMessageTranslations,
|
||||||
@ -41,6 +44,7 @@ import { updateUnreadReactions } from '../../reducers/reactions';
|
|||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import {
|
import {
|
||||||
selectChat,
|
selectChat,
|
||||||
|
selectChatLastMessageId,
|
||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
selectChatMessageByPollId,
|
selectChatMessageByPollId,
|
||||||
selectChatMessages,
|
selectChatMessages,
|
||||||
@ -49,11 +53,13 @@ import {
|
|||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectFirstUnreadId,
|
selectFirstUnreadId,
|
||||||
selectIsChatListed,
|
selectIsChatListed,
|
||||||
|
selectIsChatWithSelf,
|
||||||
selectIsMessageInCurrentMessageList,
|
selectIsMessageInCurrentMessageList,
|
||||||
selectIsServiceChatReady,
|
selectIsServiceChatReady,
|
||||||
selectIsViewportNewest,
|
selectIsViewportNewest,
|
||||||
selectListedIds,
|
selectListedIds,
|
||||||
selectPinnedIds,
|
selectPinnedIds,
|
||||||
|
selectSavedDialogIdFromMessage,
|
||||||
selectScheduledIds,
|
selectScheduledIds,
|
||||||
selectScheduledMessage,
|
selectScheduledMessage,
|
||||||
selectSendAs,
|
selectSendAs,
|
||||||
@ -87,8 +93,9 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
actions.loadTopicById({ chatId, topicId: replyInfo.replyToMsgId });
|
actions.loadTopicById({ chatId, topicId: replyInfo.replyToMsgId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isLocal = isMessageLocal(message as ApiMessage);
|
||||||
|
|
||||||
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
||||||
const isLocal = isMessageLocal(message as ApiMessage);
|
|
||||||
// Force update for last message on drafted messages to prevent flickering
|
// Force update for last message on drafted messages to prevent flickering
|
||||||
if (isLocal && wasDrafted) {
|
if (isLocal && wasDrafted) {
|
||||||
global = updateChatLastMessage(global, chatId, newMessage);
|
global = updateChatLastMessage(global, chatId, newMessage);
|
||||||
@ -138,6 +145,22 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
actions.loadTopChats();
|
actions.loadTopChats();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selectIsChatWithSelf(global, chatId) && !isLocal) {
|
||||||
|
const savedDialogId = selectSavedDialogIdFromMessage(global, newMessage);
|
||||||
|
if (savedDialogId && !selectIsChatListed(global, savedDialogId, 'saved')) {
|
||||||
|
actions.requestSavedDialogUpdate({ chatId: savedDialogId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'updateChatLastMessage': {
|
||||||
|
const { id, lastMessage } = update;
|
||||||
|
|
||||||
|
global = updateChatLastMessage(global, id, lastMessage);
|
||||||
|
global = addMessages(global, [lastMessage]);
|
||||||
|
setGlobal(global);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -197,10 +220,6 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
|
|
||||||
const newMessage = selectChatMessage(global, chatId, id)!;
|
const newMessage = selectChatMessage(global, chatId, id)!;
|
||||||
|
|
||||||
if (currentMessage) {
|
|
||||||
global = updateChatLastMessage(global, chatId, newMessage);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.reactions && chat) {
|
if (message.reactions && chat) {
|
||||||
global = updateReactions(global, chatId, id, message.reactions, chat, newMessage.isOutgoing, currentMessage);
|
global = updateReactions(global, chatId, id, message.reactions, chat, newMessage.isOutgoing, currentMessage);
|
||||||
}
|
}
|
||||||
@ -289,6 +308,13 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
lastReadInboxMessageId: message.id,
|
lastReadInboxMessageId: message.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (selectIsChatWithSelf(global, chatId)) {
|
||||||
|
const savedDialogId = selectSavedDialogIdFromMessage(global, newMessage);
|
||||||
|
if (savedDialogId && !selectIsChatListed(global, savedDialogId, 'saved')) {
|
||||||
|
actions.requestSavedDialogUpdate({ chatId: savedDialogId });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@ -322,7 +348,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
const { chatId, isPinned, messageIds } = update;
|
const { chatId, isPinned, messageIds } = update;
|
||||||
|
|
||||||
const messages = pickTruthy(selectChatMessages(global, chatId), messageIds);
|
const messages = pickTruthy(selectChatMessages(global, chatId), messageIds);
|
||||||
const updatePerThread: Record<number, number[]> = {
|
const updatePerThread: Record<ThreadId, number[]> = {
|
||||||
[MAIN_THREAD_ID]: messageIds,
|
[MAIN_THREAD_ID]: messageIds,
|
||||||
};
|
};
|
||||||
Object.values(messages).forEach((message) => {
|
Object.values(messages).forEach((message) => {
|
||||||
@ -361,7 +387,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
const currentThreadInfo = selectThreadInfo(global, chatId, threadId);
|
const currentThreadInfo = selectThreadInfo(global, chatId, threadId);
|
||||||
if (chat?.isForum && threadInfo.lastReadInboxMessageId !== currentThreadInfo?.lastReadInboxMessageId) {
|
if (chat?.isForum && threadInfo.lastReadInboxMessageId !== currentThreadInfo?.lastReadInboxMessageId) {
|
||||||
actions.loadTopicById({ chatId, topicId: threadId });
|
actions.loadTopicById({ chatId, topicId: Number(threadId) });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update reply thread last read message id if already read in main thread
|
// Update reply thread last read message id if already read in main thread
|
||||||
@ -445,10 +471,6 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
const chatId = selectCommonBoxChatId(global, id);
|
const chatId = selectCommonBoxChatId(global, id);
|
||||||
if (chatId) {
|
if (chatId) {
|
||||||
global = updateChatMessage(global, chatId, id, messageUpdate);
|
global = updateChatMessage(global, chatId, id, messageUpdate);
|
||||||
const message = selectChatMessage(global, chatId, id);
|
|
||||||
if (message) {
|
|
||||||
global = updateChatLastMessage(global, chatId, message);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -785,15 +807,17 @@ function updateListedAndViewportIds<T extends GlobalState>(
|
|||||||
) {
|
) {
|
||||||
const { id, chatId } = message;
|
const { id, chatId } = message;
|
||||||
|
|
||||||
|
const savedDialogId = selectSavedDialogIdFromMessage(global, message);
|
||||||
|
|
||||||
const { threadInfo } = 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 ?? {};
|
const { threadId } = threadInfo ?? { threadId: savedDialogId };
|
||||||
|
|
||||||
if (threadInfo && threadId) {
|
if (threadId) {
|
||||||
global = updateListedIds(global, chatId, threadId, [id]);
|
global = updateListedIds(global, chatId, threadId, [id]);
|
||||||
|
|
||||||
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
||||||
@ -809,15 +833,17 @@ function updateListedAndViewportIds<T extends GlobalState>(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
global = replaceThreadParam(global, chatId, threadId, 'threadInfo', {
|
if (threadInfo) {
|
||||||
...threadInfo,
|
global = replaceThreadParam(global, chatId, threadId, 'threadInfo', {
|
||||||
lastMessageId: message.id,
|
...threadInfo,
|
||||||
});
|
lastMessageId: message.id,
|
||||||
|
|
||||||
if (!isMessageLocal(message) && !isActionMessage(message)) {
|
|
||||||
global = updateThreadInfo(global, chatId, threadId, {
|
|
||||||
messagesCount: (threadInfo.messagesCount || 0) + 1,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!isMessageLocal(message) && !isActionMessage(message)) {
|
||||||
|
global = updateThreadInfo(global, chatId, threadId, {
|
||||||
|
messagesCount: (threadInfo.messagesCount || 0) + 1,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -851,7 +877,7 @@ function updateChatLastMessage<T extends GlobalState>(
|
|||||||
) {
|
) {
|
||||||
const { chats } = global;
|
const { chats } = global;
|
||||||
const chat = chats.byId[chatId];
|
const chat = chats.byId[chatId];
|
||||||
const currentLastMessage = chat?.lastMessage;
|
const currentLastMessageId = selectChatLastMessageId(global, chatId);
|
||||||
|
|
||||||
const topic = chat?.isForum ? selectTopicFromMessage(global, message) : undefined;
|
const topic = chat?.isForum ? selectTopicFromMessage(global, message) : undefined;
|
||||||
if (topic) {
|
if (topic) {
|
||||||
@ -860,22 +886,27 @@ function updateChatLastMessage<T extends GlobalState>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentLastMessage && !force) {
|
const savedDialogId = selectSavedDialogIdFromMessage(global, message);
|
||||||
|
if (savedDialogId) {
|
||||||
|
global = updateChatLastMessageId(global, savedDialogId, message.id, 'saved');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentLastMessageId && !force) {
|
||||||
const isSameOrNewer = (
|
const isSameOrNewer = (
|
||||||
currentLastMessage.id === message.id || currentLastMessage.id === message.previousLocalId
|
currentLastMessageId === message.id || currentLastMessageId === message.previousLocalId
|
||||||
) || message.id > currentLastMessage.id;
|
) || message.id > currentLastMessageId;
|
||||||
|
|
||||||
if (!isSameOrNewer) {
|
if (!isSameOrNewer) {
|
||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
global = updateChat(global, chatId, { lastMessage: message });
|
global = updateChatLastMessageId(global, chatId, message.id);
|
||||||
|
|
||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
|
|
||||||
function findLastMessage<T extends GlobalState>(global: T, chatId: string, threadId = MAIN_THREAD_ID) {
|
function findLastMessage<T extends GlobalState>(global: T, chatId: string, threadId: ThreadId = MAIN_THREAD_ID) {
|
||||||
const byId = selectChatMessages(global, chatId);
|
const byId = selectChatMessages(global, chatId);
|
||||||
const listedIds = selectListedIds(global, chatId, threadId);
|
const listedIds = selectListedIds(global, chatId, threadId);
|
||||||
|
|
||||||
@ -903,7 +934,7 @@ 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>();
|
const threadIdsToUpdate = new Set<ThreadId>();
|
||||||
threadIdsToUpdate.add(MAIN_THREAD_ID);
|
threadIdsToUpdate.add(MAIN_THREAD_ID);
|
||||||
|
|
||||||
ids.forEach((id) => {
|
ids.forEach((id) => {
|
||||||
@ -979,6 +1010,18 @@ export function deleteMessages<T extends GlobalState>(
|
|||||||
global = updateChatLastMessage(global, commonBoxChatId, newLastMessage, true);
|
global = updateChatLastMessage(global, commonBoxChatId, newLastMessage, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const message = selectChatMessage(global, commonBoxChatId, id);
|
||||||
|
if (selectIsChatWithSelf(global, commonBoxChatId) && message) {
|
||||||
|
const threadId = selectThreadIdFromMessage(global, message);
|
||||||
|
if (getIsSavedDialog(commonBoxChatId, threadId, global.currentUserId)) {
|
||||||
|
const newLastSavedDialogMessage = findLastMessage(global, commonBoxChatId, threadId);
|
||||||
|
actions.requestSavedDialogUpdate({ chatId: String(threadId) });
|
||||||
|
if (newLastSavedDialogMessage) {
|
||||||
|
global = updateChatLastMessageId(global, commonBoxChatId, newLastSavedDialogMessage.id, 'saved');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = deleteChatMessages(global, commonBoxChatId, [id]);
|
global = deleteChatMessages(global, commonBoxChatId, [id]);
|
||||||
|
|||||||
@ -6,7 +6,12 @@ import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
|||||||
import {
|
import {
|
||||||
deleteContact, replaceUserStatuses, updatePeerStoriesHidden, updateUser, updateUserFullInfo,
|
deleteContact, replaceUserStatuses, updatePeerStoriesHidden, updateUser, updateUserFullInfo,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
import { selectIsCurrentUserPremium, selectUser, selectUserFullInfo } from '../../selectors';
|
import {
|
||||||
|
selectIsChatWithSelf,
|
||||||
|
selectIsCurrentUserPremium,
|
||||||
|
selectUser,
|
||||||
|
selectUserFullInfo,
|
||||||
|
} from '../../selectors';
|
||||||
|
|
||||||
const STATUS_UPDATE_THROTTLE = 3000;
|
const STATUS_UPDATE_THROTTLE = 3000;
|
||||||
|
|
||||||
@ -40,7 +45,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
|
|
||||||
case 'updateUser': {
|
case 'updateUser': {
|
||||||
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
||||||
if (update.id === global.currentUserId && update.user.isPremium !== selectIsCurrentUserPremium(global)) {
|
if (selectIsChatWithSelf(global, update.id) && update.user.isPremium !== selectIsCurrentUserPremium(global)) {
|
||||||
if (update.user.isPremium && global.byTabId[tabId].premiumModal) {
|
if (update.user.isPremium && global.byTabId[tabId].premiumModal) {
|
||||||
actions.openPremiumModal({ isSuccess: true, tabId });
|
actions.openPremiumModal({ isSuccess: true, tabId });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,7 +20,9 @@ import parseHtmlAsFormattedText from '../../../util/parseHtmlAsFormattedText';
|
|||||||
import { getServerTime } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
import { IS_TOUCH_ENV } from '../../../util/windowEnvironment';
|
||||||
import versionNotification from '../../../versionNotification.txt';
|
import versionNotification from '../../../versionNotification.txt';
|
||||||
import { getMessageSummaryText, getSenderTitle, isChatChannel } from '../../helpers';
|
import {
|
||||||
|
getIsSavedDialog, getMessageSummaryText, getSenderTitle, isChatChannel,
|
||||||
|
} from '../../helpers';
|
||||||
import { renderMessageSummaryHtml } from '../../helpers/renderMessageSummaryHtml';
|
import { renderMessageSummaryHtml } from '../../helpers/renderMessageSummaryHtml';
|
||||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||||
import {
|
import {
|
||||||
@ -38,6 +40,7 @@ import { updateTabState } from '../../reducers/tabs';
|
|||||||
import {
|
import {
|
||||||
selectAllowedMessageActions,
|
selectAllowedMessageActions,
|
||||||
selectChat,
|
selectChat,
|
||||||
|
selectChatLastMessageId,
|
||||||
selectChatMessages,
|
selectChatMessages,
|
||||||
selectChatScheduledMessages,
|
selectChatScheduledMessages,
|
||||||
selectCurrentChat,
|
selectCurrentChat,
|
||||||
@ -144,9 +147,7 @@ addActionHandler('replyToNextMessage', (global, actions, payload): ActionReturnT
|
|||||||
|
|
||||||
if (!isLatest || !replyInfo?.replyToMsgId) {
|
if (!isLatest || !replyInfo?.replyToMsgId) {
|
||||||
if (threadId === MAIN_THREAD_ID) {
|
if (threadId === MAIN_THREAD_ID) {
|
||||||
const chat = selectChat(global, chatId);
|
messageId = selectChatLastMessageId(global, chatId);
|
||||||
|
|
||||||
messageId = chat?.lastMessage?.id;
|
|
||||||
} else {
|
} else {
|
||||||
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
||||||
|
|
||||||
@ -316,6 +317,8 @@ addActionHandler('focusLastMessage', (global, actions, payload): ActionReturnTyp
|
|||||||
|
|
||||||
const { chatId, threadId, type } = currentMessageList;
|
const { chatId, threadId, type } = currentMessageList;
|
||||||
|
|
||||||
|
const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId);
|
||||||
|
|
||||||
let lastMessageId: number | undefined;
|
let lastMessageId: number | undefined;
|
||||||
if (threadId === MAIN_THREAD_ID) {
|
if (threadId === MAIN_THREAD_ID) {
|
||||||
if (type === 'pinned') {
|
if (type === 'pinned') {
|
||||||
@ -326,10 +329,10 @@ addActionHandler('focusLastMessage', (global, actions, payload): ActionReturnTyp
|
|||||||
|
|
||||||
lastMessageId = pinnedMessageIds[pinnedMessageIds.length - 1];
|
lastMessageId = pinnedMessageIds[pinnedMessageIds.length - 1];
|
||||||
} else {
|
} else {
|
||||||
const chat = selectChat(global, chatId);
|
lastMessageId = selectChatLastMessageId(global, chatId);
|
||||||
|
|
||||||
lastMessageId = chat?.lastMessage?.id;
|
|
||||||
}
|
}
|
||||||
|
} else if (isSavedDialog) {
|
||||||
|
lastMessageId = selectChatLastMessageId(global, String(threadId), 'saved');
|
||||||
} else {
|
} else {
|
||||||
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
||||||
|
|
||||||
@ -703,10 +706,9 @@ addActionHandler('checkVersionNotification', (global, actions): ActionReturnType
|
|||||||
addActionHandler('createServiceNotification', (global, actions, payload): ActionReturnType => {
|
addActionHandler('createServiceNotification', (global, actions, payload): ActionReturnType => {
|
||||||
const { message, version } = payload;
|
const { message, version } = payload;
|
||||||
const { serviceNotifications } = global;
|
const { serviceNotifications } = global;
|
||||||
const serviceChat = selectChat(global, SERVICE_NOTIFICATIONS_USER_ID)!;
|
|
||||||
|
|
||||||
const maxId = Math.max(
|
const maxId = Math.max(
|
||||||
serviceChat.lastMessage?.id || 0,
|
selectChatLastMessageId(global, SERVICE_NOTIFICATIONS_USER_ID) || 0,
|
||||||
...serviceNotifications.map(({ id }) => id),
|
...serviceNotifications.map(({ id }) => id),
|
||||||
);
|
);
|
||||||
const fractionalPart = (serviceNotifications.length + 1) / SERVICE_NOTIFICATIONS_MAX_AMOUNT;
|
const fractionalPart = (serviceNotifications.length + 1) / SERVICE_NOTIFICATIONS_MAX_AMOUNT;
|
||||||
|
|||||||
@ -12,11 +12,11 @@ import {
|
|||||||
DEBUG,
|
DEBUG,
|
||||||
DEFAULT_LIMITS,
|
DEFAULT_LIMITS,
|
||||||
GLOBAL_STATE_CACHE_CHAT_LIST_LIMIT,
|
GLOBAL_STATE_CACHE_CHAT_LIST_LIMIT,
|
||||||
GLOBAL_STATE_CACHE_CHATS_WITH_MESSAGES_LIMIT,
|
|
||||||
GLOBAL_STATE_CACHE_CUSTOM_EMOJI_LIMIT,
|
GLOBAL_STATE_CACHE_CUSTOM_EMOJI_LIMIT,
|
||||||
GLOBAL_STATE_CACHE_DISABLED,
|
GLOBAL_STATE_CACHE_DISABLED,
|
||||||
GLOBAL_STATE_CACHE_KEY,
|
GLOBAL_STATE_CACHE_KEY,
|
||||||
GLOBAL_STATE_CACHE_USER_LIST_LIMIT,
|
GLOBAL_STATE_CACHE_USER_LIST_LIMIT,
|
||||||
|
SAVED_FOLDER_ID,
|
||||||
} from '../config';
|
} from '../config';
|
||||||
import { getOrderedIds } from '../util/folderManager';
|
import { getOrderedIds } from '../util/folderManager';
|
||||||
import {
|
import {
|
||||||
@ -31,6 +31,7 @@ import { INITIAL_GLOBAL_STATE, INITIAL_PERFORMANCE_STATE_MID, INITIAL_PERFORMANC
|
|||||||
import { clearGlobalForLockScreen } from './reducers';
|
import { clearGlobalForLockScreen } from './reducers';
|
||||||
import {
|
import {
|
||||||
selectChat,
|
selectChat,
|
||||||
|
selectChatLastMessageId,
|
||||||
selectChatMessages,
|
selectChatMessages,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectViewportIds,
|
selectViewportIds,
|
||||||
@ -209,6 +210,10 @@ function unsafeMigrateCache(cached: GlobalState, initialState: GlobalState) {
|
|||||||
cached.chats.similarChannelsById = initialState.chats.similarChannelsById;
|
cached.chats.similarChannelsById = initialState.chats.similarChannelsById;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!cached.chats.lastMessageIds) {
|
||||||
|
cached.chats.lastMessageIds = initialState.chats.lastMessageIds;
|
||||||
|
}
|
||||||
|
|
||||||
// Clear old color storage to optimize cache size
|
// Clear old color storage to optimize cache size
|
||||||
if (untypedCached?.appConfig?.peerColors) {
|
if (untypedCached?.appConfig?.peerColors) {
|
||||||
untypedCached.appConfig.peerColors = undefined;
|
untypedCached.appConfig.peerColors = undefined;
|
||||||
@ -370,6 +375,7 @@ function reduceChats<T extends GlobalState>(global: T): GlobalState['chats'] {
|
|||||||
...currentUserId ? [currentUserId] : [],
|
...currentUserId ? [currentUserId] : [],
|
||||||
...currentChatIds,
|
...currentChatIds,
|
||||||
...messagesChatIds,
|
...messagesChatIds,
|
||||||
|
...getOrderedIds(SAVED_FOLDER_ID) || [],
|
||||||
...getOrderedIds(ALL_FOLDER_ID) || [],
|
...getOrderedIds(ALL_FOLDER_ID) || [],
|
||||||
...getOrderedIds(ARCHIVED_FOLDER_ID) || [],
|
...getOrderedIds(ARCHIVED_FOLDER_ID) || [],
|
||||||
...global.recentlyFoundChatIds || [],
|
...global.recentlyFoundChatIds || [],
|
||||||
@ -382,6 +388,10 @@ function reduceChats<T extends GlobalState>(global: T): GlobalState['chats'] {
|
|||||||
isFullyLoaded: {},
|
isFullyLoaded: {},
|
||||||
byId: pick(global.chats.byId, idsToSave),
|
byId: pick(global.chats.byId, idsToSave),
|
||||||
fullInfoById: pick(global.chats.fullInfoById, idsToSave),
|
fullInfoById: pick(global.chats.fullInfoById, idsToSave),
|
||||||
|
lastMessageIds: {
|
||||||
|
all: pick(global.chats.lastMessageIds.all || {}, idsToSave),
|
||||||
|
saved: global.chats.lastMessageIds.saved,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,7 +410,7 @@ function reduceMessages<T extends GlobalState>(global: T): GlobalState['messages
|
|||||||
...currentChatIds,
|
...currentChatIds,
|
||||||
...currentUserId ? [currentUserId] : [],
|
...currentUserId ? [currentUserId] : [],
|
||||||
...forumPanelChatIds,
|
...forumPanelChatIds,
|
||||||
...getOrderedIds(ALL_FOLDER_ID)?.slice(0, GLOBAL_STATE_CACHE_CHATS_WITH_MESSAGES_LIMIT) || [],
|
...getOrderedIds(ALL_FOLDER_ID) || [],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
chatIdsToSave.forEach((chatId) => {
|
chatIdsToSave.forEach((chatId) => {
|
||||||
@ -410,6 +420,7 @@ function reduceMessages<T extends GlobalState>(global: T): GlobalState['messages
|
|||||||
}
|
}
|
||||||
|
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
|
const chatLastMessageId = selectChatLastMessageId(global, chatId);
|
||||||
|
|
||||||
const threadIds = unique(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) || {};
|
||||||
@ -423,15 +434,18 @@ function reduceMessages<T extends GlobalState>(global: T): GlobalState['messages
|
|||||||
.map(({ threadInfo }) => (threadInfo?.isCommentsInfo ? threadInfo?.originMessageId : undefined)),
|
.map(({ threadInfo }) => (threadInfo?.isCommentsInfo ? threadInfo?.originMessageId : undefined)),
|
||||||
)));
|
)));
|
||||||
|
|
||||||
const threadIdsToSave = threadIds.length ? [MAIN_THREAD_ID, ...threadIds] : [MAIN_THREAD_ID];
|
const threadsToSave = pickTruthy(current.threadsById, [MAIN_THREAD_ID, ...threadIds]);
|
||||||
const threadsToSave = pickTruthy(current.threadsById, threadIdsToSave);
|
|
||||||
if (!Object.keys(threadsToSave).length) {
|
if (!Object.keys(threadsToSave).length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const viewportIdsToSave = unique(Object.values(threadsToSave).flatMap((thread) => thread.lastViewportIds || []));
|
const viewportIdsToSave = unique(Object.values(threadsToSave).flatMap((thread) => thread.lastViewportIds || []));
|
||||||
const lastMessageIdsToSave = chat?.topics
|
const topicLastMessageIds = chat?.topics ? Object.values(chat.topics).map(({ lastMessageId }) => lastMessageId)
|
||||||
? Object.values(chat.topics).map(({ lastMessageId }) => lastMessageId) : [];
|
: [];
|
||||||
|
const savedLastMessageIds = chatId === currentUserId && global.chats.lastMessageIds.saved
|
||||||
|
? Object.values(global.chats.lastMessageIds.saved) : [];
|
||||||
|
const lastMessageIdsToSave = [chatLastMessageId].concat(topicLastMessageIds).concat(savedLastMessageIds)
|
||||||
|
.filter(Boolean);
|
||||||
const byId = pick(current.byId, viewportIdsToSave.concat(lastMessageIdsToSave));
|
const byId = pick(current.byId, viewportIdsToSave.concat(lastMessageIdsToSave));
|
||||||
const threadsById = Object.keys(threadsToSave).reduce((acc, key) => {
|
const threadsById = Object.keys(threadsToSave).reduce((acc, key) => {
|
||||||
const thread = threadsToSave[Number(key)];
|
const thread = threadsToSave[Number(key)];
|
||||||
|
|||||||
@ -8,23 +8,20 @@ import type {
|
|||||||
ApiUser,
|
ApiUser,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { LangFn } from '../../hooks/useLang';
|
import type { LangFn } from '../../hooks/useLang';
|
||||||
import type { NotifyException, NotifySettings } from '../../types';
|
import type { NotifyException, NotifySettings, ThreadId } from '../../types';
|
||||||
import { MAIN_THREAD_ID } from '../../api/types';
|
import { MAIN_THREAD_ID } from '../../api/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
ANONYMOUS_USER_ID,
|
||||||
ARCHIVED_FOLDER_ID, CHANNEL_ID_LENGTH, GENERAL_TOPIC_ID, REPLIES_USER_ID, TME_LINK_PREFIX,
|
ARCHIVED_FOLDER_ID, CHANNEL_ID_LENGTH, GENERAL_TOPIC_ID, REPLIES_USER_ID, TME_LINK_PREFIX,
|
||||||
} from '../../config';
|
} from '../../config';
|
||||||
import { formatDateToString, formatTime } from '../../util/dateFormat';
|
import { formatDateToString, formatTime } from '../../util/dateFormat';
|
||||||
import { orderBy } from '../../util/iteratees';
|
|
||||||
import { prepareSearchWordsForNeedle } from '../../util/searchWords';
|
import { prepareSearchWordsForNeedle } from '../../util/searchWords';
|
||||||
import { getGlobal } from '..';
|
import { getGlobal } from '..';
|
||||||
import { getMainUsername, getUserFirstOrLastName } from './users';
|
import { getMainUsername, getUserFirstOrLastName } from './users';
|
||||||
|
|
||||||
const FOREVER_BANNED_DATE = Date.now() / 1000 + 31622400; // 366 days
|
const FOREVER_BANNED_DATE = Date.now() / 1000 + 31622400; // 366 days
|
||||||
|
|
||||||
const VERIFIED_PRIORITY_BASE = 3e9;
|
|
||||||
const PINNED_PRIORITY_BASE = 3e8;
|
|
||||||
|
|
||||||
export function isUserId(entityId: string) {
|
export function isUserId(entityId: string) {
|
||||||
return !entityId.startsWith('-');
|
return !entityId.startsWith('-');
|
||||||
}
|
}
|
||||||
@ -65,6 +62,10 @@ export function isChatWithRepliesBot(chatId: string) {
|
|||||||
return chatId === REPLIES_USER_ID;
|
return chatId === REPLIES_USER_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isAnonymousForwardsChat(chatId: string) {
|
||||||
|
return chatId === ANONYMOUS_USER_ID;
|
||||||
|
}
|
||||||
|
|
||||||
export function getChatTypeString(chat: ApiChat) {
|
export function getChatTypeString(chat: ApiChat) {
|
||||||
switch (chat.type) {
|
switch (chat.type) {
|
||||||
case 'chatTypePrivate':
|
case 'chatTypePrivate':
|
||||||
@ -116,10 +117,6 @@ export function getChatAvatarHash(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isChatSummaryOnly(chat: ApiChat) {
|
|
||||||
return !chat.lastMessage;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isChatAdmin(chat: ApiChat) {
|
export function isChatAdmin(chat: ApiChat) {
|
||||||
return Boolean(chat.adminRights);
|
return Boolean(chat.adminRights);
|
||||||
}
|
}
|
||||||
@ -140,7 +137,7 @@ export function isUserRightBanned(chat: ApiChat, key: keyof ApiChatBannedRights)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCanPostInChat(chat: ApiChat, threadId: number, isMessageThread?: boolean) {
|
export function getCanPostInChat(chat: ApiChat, threadId: ThreadId, isMessageThread?: boolean) {
|
||||||
if (threadId !== MAIN_THREAD_ID) {
|
if (threadId !== MAIN_THREAD_ID) {
|
||||||
if (chat.isForum) {
|
if (chat.isForum) {
|
||||||
if (chat.isNotJoined) {
|
if (chat.isNotJoined) {
|
||||||
@ -155,7 +152,7 @@ export function getCanPostInChat(chat: ApiChat, threadId: number, isMessageThrea
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (chat.isRestricted || chat.isForbidden || chat.migratedTo
|
if (chat.isRestricted || chat.isForbidden || chat.migratedTo
|
||||||
|| (!isMessageThread && chat.isNotJoined) || isChatWithRepliesBot(chat.id)) {
|
|| (!isMessageThread && chat.isNotJoined) || isChatWithRepliesBot(chat.id) || isAnonymousForwardsChat(chat.id)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -257,7 +254,7 @@ export function getMessageSendingRestrictionReason(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getForumComposerPlaceholder(
|
export function getForumComposerPlaceholder(
|
||||||
lang: LangFn, chat?: ApiChat, threadId = MAIN_THREAD_ID, isReplying?: boolean,
|
lang: LangFn, chat?: ApiChat, threadId: ThreadId = MAIN_THREAD_ID, isReplying?: boolean,
|
||||||
) {
|
) {
|
||||||
if (!chat?.isForum) {
|
if (!chat?.isForum) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -377,36 +374,6 @@ export function getMessageSenderName(lang: LangFn, chatId: string, sender?: ApiP
|
|||||||
return getUserFirstOrLastName(sender);
|
return getUserFirstOrLastName(sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sortChatIds(
|
|
||||||
chatIds: string[],
|
|
||||||
chatsById: Record<string, ApiChat>,
|
|
||||||
shouldPrioritizeVerified = false,
|
|
||||||
priorityIds?: string[],
|
|
||||||
) {
|
|
||||||
return orderBy(chatIds, (id) => {
|
|
||||||
const chat = chatsById[id];
|
|
||||||
if (!chat) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
let priority = 0;
|
|
||||||
|
|
||||||
if (chat.lastMessage) {
|
|
||||||
priority += chat.lastMessage.date;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (shouldPrioritizeVerified && chat.isVerified) {
|
|
||||||
priority += VERIFIED_PRIORITY_BASE; // ~100 years in seconds
|
|
||||||
}
|
|
||||||
|
|
||||||
if (priorityIds && priorityIds.includes(id)) {
|
|
||||||
priority = Date.now() + PINNED_PRIORITY_BASE + (priorityIds.length - priorityIds.indexOf(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
return priority;
|
|
||||||
}, 'desc');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function filterChatsByName(
|
export function filterChatsByName(
|
||||||
lang: LangFn,
|
lang: LangFn,
|
||||||
chatIds: string[],
|
chatIds: string[],
|
||||||
@ -480,3 +447,7 @@ export function getPeerColorCount(peer: ApiPeer) {
|
|||||||
// eslint-disable-next-line eslint-multitab-tt/no-immediate-global
|
// eslint-disable-next-line eslint-multitab-tt/no-immediate-global
|
||||||
return getGlobal().peerColors?.general[key].colors?.length || 1;
|
return getGlobal().peerColors?.general[key].colors?.length || 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getIsSavedDialog(chatId: string, threadId: ThreadId | undefined, currentUserId: string | undefined) {
|
||||||
|
return chatId === currentUserId && threadId !== MAIN_THREAD_ID;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
export function buildChatThreadKey(chatId: string, threadId: number) {
|
import type { ThreadId } from '../../types';
|
||||||
|
|
||||||
|
export function buildChatThreadKey(chatId: string, threadId: ThreadId) {
|
||||||
return `${chatId}_${threadId}`;
|
return `${chatId}_${threadId}`;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type { ApiPeer, ApiUser, ApiUserStatus } from '../../api/types';
|
import type { ApiPeer, ApiUser, ApiUserStatus } from '../../api/types';
|
||||||
import type { LangFn } from '../../hooks/useLang';
|
import type { LangFn } from '../../hooks/useLang';
|
||||||
|
|
||||||
import { SERVICE_NOTIFICATIONS_USER_ID } from '../../config';
|
import { ANONYMOUS_USER_ID, SERVICE_NOTIFICATIONS_USER_ID } from '../../config';
|
||||||
import { formatFullDate, formatTime } from '../../util/dateFormat';
|
import { formatFullDate, formatTime } from '../../util/dateFormat';
|
||||||
import { orderBy } from '../../util/iteratees';
|
import { orderBy } from '../../util/iteratees';
|
||||||
import { formatPhoneNumber } from '../../util/phoneNumber';
|
import { formatPhoneNumber } from '../../util/phoneNumber';
|
||||||
@ -191,7 +191,7 @@ export function isUserBot(user: ApiUser) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function getCanAddContact(user: ApiUser) {
|
export function getCanAddContact(user: ApiUser) {
|
||||||
return !user.isSelf && !user.isContact && !isUserBot(user);
|
return !user.isSelf && !user.isContact && !isUserBot(user) && user.id !== ANONYMOUS_USER_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sortUserIds(
|
export function sortUserIds(
|
||||||
|
|||||||
@ -117,7 +117,7 @@ addActionHandler('init', (global, actions, payload): ActionReturnType => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const parsedMessageList = parseLocationHash();
|
const parsedMessageList = parseLocationHash(global.currentUserId);
|
||||||
|
|
||||||
if (global.authState !== 'authorizationStateReady'
|
if (global.authState !== 'authorizationStateReady'
|
||||||
&& !global.passcode.hasPasscode && !global.passcode.isScreenLocked) {
|
&& !global.passcode.hasPasscode && !global.passcode.isScreenLocked) {
|
||||||
|
|||||||
@ -103,6 +103,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
|
|||||||
isFullyLoaded: {},
|
isFullyLoaded: {},
|
||||||
orderedPinnedIds: {},
|
orderedPinnedIds: {},
|
||||||
totalCount: {},
|
totalCount: {},
|
||||||
|
lastMessageIds: {},
|
||||||
byId: {},
|
byId: {},
|
||||||
fullInfoById: {},
|
fullInfoById: {},
|
||||||
similarChannelsById: {},
|
similarChannelsById: {},
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import type {
|
import type {
|
||||||
ApiChat, ApiChatFullInfo, ApiChatMember, ApiPhoto, ApiTopic,
|
ApiChat, ApiChatFullInfo, ApiChatMember, ApiPhoto, ApiTopic,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { GlobalState } from '../types';
|
import type { ChatListType, GlobalState } from '../types';
|
||||||
|
|
||||||
import { ARCHIVED_FOLDER_ID } from '../../config';
|
import { ARCHIVED_FOLDER_ID } from '../../config';
|
||||||
import { areDeepEqual } from '../../util/areDeepEqual';
|
import { areDeepEqual } from '../../util/areDeepEqual';
|
||||||
@ -11,9 +11,11 @@ import {
|
|||||||
import { selectChat, selectChatFullInfo } from '../selectors';
|
import { selectChat, selectChatFullInfo } from '../selectors';
|
||||||
import { updateThread, updateThreadInfo } from './messages';
|
import { updateThread, updateThreadInfo } from './messages';
|
||||||
|
|
||||||
|
const DEFAULT_CHAT_LISTS: ChatListType[] = ['active', 'archived'];
|
||||||
|
|
||||||
export function replaceChatListIds<T extends GlobalState>(
|
export function replaceChatListIds<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
type: 'active' | 'archived',
|
type: ChatListType,
|
||||||
newIds: string[] | undefined,
|
newIds: string[] | undefined,
|
||||||
): T {
|
): T {
|
||||||
return {
|
return {
|
||||||
@ -28,8 +30,46 @@ export function replaceChatListIds<T extends GlobalState>(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function updateChatLastMessageId<T extends GlobalState>(
|
||||||
|
global: T, chatId: string, lastMessageId: number, listType?: ChatListType,
|
||||||
|
): T {
|
||||||
|
const key = listType === 'saved' ? 'saved' : 'all';
|
||||||
|
return {
|
||||||
|
...global,
|
||||||
|
chats: {
|
||||||
|
...global.chats,
|
||||||
|
lastMessageIds: {
|
||||||
|
...global.chats.lastMessageIds,
|
||||||
|
[key]: {
|
||||||
|
...global.chats.lastMessageIds[key],
|
||||||
|
[chatId]: lastMessageId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function updateChatsLastMessageId<T extends GlobalState>(
|
||||||
|
global: T, messageIds: Record<string, number>, listType?: ChatListType,
|
||||||
|
): T {
|
||||||
|
const key = listType === 'saved' ? 'saved' : 'all';
|
||||||
|
return {
|
||||||
|
...global,
|
||||||
|
chats: {
|
||||||
|
...global.chats,
|
||||||
|
lastMessageIds: {
|
||||||
|
...global.chats.lastMessageIds,
|
||||||
|
[key]: {
|
||||||
|
...global.chats.lastMessageIds[key],
|
||||||
|
...messageIds,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function updateChatListIds<T extends GlobalState>(
|
export function updateChatListIds<T extends GlobalState>(
|
||||||
global: T, type: 'active' | 'archived', idsUpdate: string[],
|
global: T, type: ChatListType, idsUpdate: string[],
|
||||||
): T {
|
): T {
|
||||||
const { [type]: listIds } = global.chats.listIds;
|
const { [type]: listIds } = global.chats.listIds;
|
||||||
const newIds = listIds?.length
|
const newIds = listIds?.length
|
||||||
@ -243,13 +283,13 @@ export function updateChatListType<T extends GlobalState>(
|
|||||||
|
|
||||||
export function updateChatListSecondaryInfo<T extends GlobalState>(
|
export function updateChatListSecondaryInfo<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
type: 'active' | 'archived',
|
type: ChatListType,
|
||||||
info: {
|
info: {
|
||||||
orderedPinnedIds?: string[];
|
orderedPinnedIds?: string[];
|
||||||
totalChatCount: number;
|
totalChatCount: number;
|
||||||
},
|
},
|
||||||
): T {
|
): T {
|
||||||
const totalCountKey = type === 'active' ? 'all' : 'archived';
|
const totalCountKey = type === 'active' ? 'all' : type;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...global,
|
...global,
|
||||||
@ -281,10 +321,12 @@ export function leaveChat<T extends GlobalState>(global: T, leftChatId: string):
|
|||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function removeChatFromChatLists<T extends GlobalState>(global: T, chatId: string): T {
|
export function removeChatFromChatLists<T extends GlobalState>(
|
||||||
const lists = global.chats.listIds;
|
global: T, chatId: string, type: 'all' | 'saved' = 'all',
|
||||||
Object.entries(lists).forEach(([listType, listIds]) => {
|
): T {
|
||||||
global = replaceChatListIds(global, listType as keyof typeof lists, listIds.filter((id) => id !== chatId));
|
const chatLists = type === 'all' ? DEFAULT_CHAT_LISTS : [type];
|
||||||
|
chatLists.forEach((listType) => {
|
||||||
|
global = replaceChatListIds(global, listType, global.chats.listIds[listType]?.filter((id) => id !== chatId));
|
||||||
});
|
});
|
||||||
|
|
||||||
return global;
|
return global;
|
||||||
@ -393,8 +435,7 @@ export function deleteTopic<T extends GlobalState>(
|
|||||||
global: T, chatId: string, topicId: number,
|
global: T, chatId: string, topicId: number,
|
||||||
) {
|
) {
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
const topics = chat?.topics || [];
|
const topics = chat?.topics || {};
|
||||||
|
|
||||||
global = updateChat(global, chatId, {
|
global = updateChat(global, chatId, {
|
||||||
topics: omit(topics, [topicId]),
|
topics: omit(topics, [topicId]),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import type { ApiMessageSearchType } from '../../api/types';
|
import type { ApiMessageSearchType } from '../../api/types';
|
||||||
import type { SharedMediaType } from '../../types';
|
import type { SharedMediaType, ThreadId } from '../../types';
|
||||||
import type { GlobalState, TabArgs } from '../types';
|
import type { GlobalState, TabArgs } from '../types';
|
||||||
|
|
||||||
import { getCurrentTabId } from '../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../util/establishMultitabRole';
|
||||||
@ -46,7 +46,7 @@ function replaceLocalTextSearch<T extends GlobalState>(
|
|||||||
export function updateLocalTextSearch<T extends GlobalState>(
|
export function updateLocalTextSearch<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
chatId: string,
|
chatId: string,
|
||||||
threadId: number,
|
threadId: ThreadId,
|
||||||
isActive: boolean,
|
isActive: boolean,
|
||||||
query?: string,
|
query?: string,
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
@ -63,7 +63,7 @@ export function updateLocalTextSearch<T extends GlobalState>(
|
|||||||
export function replaceLocalTextSearchResults<T extends GlobalState>(
|
export function replaceLocalTextSearchResults<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
chatId: string,
|
chatId: string,
|
||||||
threadId: number,
|
threadId: ThreadId,
|
||||||
foundIds?: number[],
|
foundIds?: number[],
|
||||||
totalCount?: number,
|
totalCount?: number,
|
||||||
nextOffsetId?: number,
|
nextOffsetId?: number,
|
||||||
@ -84,7 +84,7 @@ export function replaceLocalTextSearchResults<T extends GlobalState>(
|
|||||||
export function updateLocalTextSearchResults<T extends GlobalState>(
|
export function updateLocalTextSearchResults<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
chatId: string,
|
chatId: string,
|
||||||
threadId: number,
|
threadId: ThreadId,
|
||||||
newFoundIds: number[],
|
newFoundIds: number[],
|
||||||
totalCount?: number,
|
totalCount?: number,
|
||||||
nextOffsetId?: number,
|
nextOffsetId?: number,
|
||||||
@ -102,7 +102,7 @@ export function updateLocalTextSearchResults<T extends GlobalState>(
|
|||||||
function replaceLocalMediaSearch<T extends GlobalState>(
|
function replaceLocalMediaSearch<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
chatId: string,
|
chatId: string,
|
||||||
threadId: number,
|
threadId: ThreadId,
|
||||||
searchParams: MediaSearchParams,
|
searchParams: MediaSearchParams,
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
): T {
|
): T {
|
||||||
@ -121,7 +121,7 @@ function replaceLocalMediaSearch<T extends GlobalState>(
|
|||||||
export function updateLocalMediaSearchType<T extends GlobalState>(
|
export function updateLocalMediaSearchType<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
chatId: string,
|
chatId: string,
|
||||||
threadId: number,
|
threadId: ThreadId,
|
||||||
currentType: SharedMediaType | undefined,
|
currentType: SharedMediaType | undefined,
|
||||||
...[tabId = getCurrentTabId()]: TabArgs<T>
|
...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||||
): T {
|
): T {
|
||||||
@ -136,7 +136,7 @@ export function updateLocalMediaSearchType<T extends GlobalState>(
|
|||||||
export function replaceLocalMediaSearchResults<T extends GlobalState>(
|
export function replaceLocalMediaSearchResults<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
chatId: string,
|
chatId: string,
|
||||||
threadId: number,
|
threadId: ThreadId,
|
||||||
type: ApiMessageSearchType,
|
type: ApiMessageSearchType,
|
||||||
foundIds?: number[],
|
foundIds?: number[],
|
||||||
totalCount?: number,
|
totalCount?: number,
|
||||||
@ -161,7 +161,7 @@ export function replaceLocalMediaSearchResults<T extends GlobalState>(
|
|||||||
export function updateLocalMediaSearchResults<T extends GlobalState>(
|
export function updateLocalMediaSearchResults<T extends GlobalState>(
|
||||||
global: T,
|
global: T,
|
||||||
chatId: string,
|
chatId: string,
|
||||||
threadId: number,
|
threadId: ThreadId,
|
||||||
type: SharedMediaType,
|
type: SharedMediaType,
|
||||||
newFoundIds: number[],
|
newFoundIds: number[],
|
||||||
totalCount?: number,
|
totalCount?: number,
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user