Saved Dialogs: Allow deletion (#4243)
This commit is contained in:
parent
8548e96e01
commit
af8aa64d8d
@ -158,7 +158,8 @@ export type UniversalMessage = (
|
|||||||
& Pick<Partial<GramJs.Message & GramJs.MessageService>, (
|
& Pick<Partial<GramJs.Message & GramJs.MessageService>, (
|
||||||
'out' | 'message' | 'entities' | 'fromId' | 'peerId' | 'fwdFrom' | 'replyTo' | 'replyMarkup' | 'post' |
|
'out' | 'message' | 'entities' | 'fromId' | 'peerId' | 'fwdFrom' | 'replyTo' | 'replyMarkup' | 'post' |
|
||||||
'media' | 'action' | 'views' | 'editDate' | 'editHide' | 'mediaUnread' | 'groupedId' | 'mentioned' | 'viaBotId' |
|
'media' | 'action' | 'views' | 'editDate' | 'editHide' | 'mediaUnread' | 'groupedId' | 'mentioned' | 'viaBotId' |
|
||||||
'replies' | 'fromScheduled' | 'postAuthor' | 'noforwards' | 'reactions' | 'forwards' | 'silent' | 'pinned'
|
'replies' | 'fromScheduled' | 'postAuthor' | 'noforwards' | 'reactions' | 'forwards' | 'silent' | 'pinned' |
|
||||||
|
'savedPeerId'
|
||||||
)>
|
)>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -197,6 +198,8 @@ export function buildApiMessageWithChatId(
|
|||||||
const emojiOnlyCount = getEmojiOnlyCountForMessage(content, groupedId);
|
const emojiOnlyCount = getEmojiOnlyCountForMessage(content, groupedId);
|
||||||
const hasComments = mtpMessage.replies?.comments;
|
const hasComments = mtpMessage.replies?.comments;
|
||||||
|
|
||||||
|
const savedPeerId = mtpMessage.savedPeerId && getApiChatIdFromMtpPeer(mtpMessage.savedPeerId);
|
||||||
|
|
||||||
return omitUndefined({
|
return omitUndefined({
|
||||||
id: mtpMessage.id,
|
id: mtpMessage.id,
|
||||||
chatId,
|
chatId,
|
||||||
@ -233,6 +236,7 @@ export function buildApiMessageWithChatId(
|
|||||||
isProtected,
|
isProtected,
|
||||||
isForwardingAllowed,
|
isForwardingAllowed,
|
||||||
hasComments,
|
hasComments,
|
||||||
|
savedPeerId,
|
||||||
} satisfies ApiMessage);
|
} satisfies ApiMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -72,8 +72,10 @@ import {
|
|||||||
} from '../helpers';
|
} from '../helpers';
|
||||||
import localDb from '../localDb';
|
import localDb from '../localDb';
|
||||||
import { scheduleMutedChatUpdate } from '../scheduleUnmute';
|
import { scheduleMutedChatUpdate } from '../scheduleUnmute';
|
||||||
import { applyState, processUpdate, updateChannelState } from '../updateManager';
|
import {
|
||||||
import { dispatchThreadInfoUpdates } from '../updater';
|
applyState, processAffectedHistory, processUpdate, updateChannelState,
|
||||||
|
} from '../updates/updateManager';
|
||||||
|
import { dispatchThreadInfoUpdates } from '../updates/updater';
|
||||||
import { invokeRequest, uploadFile } from './client';
|
import { invokeRequest, uploadFile } from './client';
|
||||||
|
|
||||||
type FullChatData = {
|
type FullChatData = {
|
||||||
@ -1787,7 +1789,7 @@ export async function fetchTopicById({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteTopic({
|
export async function deleteTopic({
|
||||||
chat, topicId,
|
chat, topicId,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
@ -1795,12 +1797,18 @@ export function deleteTopic({
|
|||||||
}) {
|
}) {
|
||||||
const { id, accessHash } = chat;
|
const { id, accessHash } = chat;
|
||||||
|
|
||||||
return invokeRequest(new GramJs.channels.DeleteTopicHistory({
|
const result = await invokeRequest(new GramJs.channels.DeleteTopicHistory({
|
||||||
channel: buildInputPeer(id, accessHash),
|
channel: buildInputPeer(id, accessHash),
|
||||||
topMsgId: topicId,
|
topMsgId: topicId,
|
||||||
}), {
|
}));
|
||||||
shouldReturnTrue: true,
|
|
||||||
});
|
if (!result) return;
|
||||||
|
|
||||||
|
processAffectedHistory(chat, result);
|
||||||
|
|
||||||
|
if (result.offset) {
|
||||||
|
await deleteTopic({ chat, topicId });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function togglePinnedTopic({
|
export function togglePinnedTopic({
|
||||||
|
|||||||
@ -36,7 +36,7 @@ import {
|
|||||||
reset as resetUpdatesManager,
|
reset as resetUpdatesManager,
|
||||||
scheduleGetChannelDifference,
|
scheduleGetChannelDifference,
|
||||||
updateChannelState,
|
updateChannelState,
|
||||||
} from '../updateManager';
|
} from '../updates/updateManager';
|
||||||
import {
|
import {
|
||||||
onAuthError, onAuthReady, onCurrentUserUpdate, onRequestCode, onRequestPassword, onRequestPhoneNumber,
|
onAuthError, onAuthReady, onCurrentUserUpdate, onRequestCode, onRequestPassword, onRequestPhoneNumber,
|
||||||
onRequestQrCode, onRequestRegistration, onWebAuthTokenFailed,
|
onRequestQrCode, onRequestRegistration, onWebAuthTokenFailed,
|
||||||
|
|||||||
@ -35,6 +35,7 @@ export {
|
|||||||
reportMessages, sendMessageAction, fetchSeenBy, fetchSponsoredMessages, viewSponsoredMessage, fetchSendAs,
|
reportMessages, sendMessageAction, fetchSeenBy, fetchSponsoredMessages, viewSponsoredMessage, fetchSendAs,
|
||||||
saveDefaultSendAs, fetchUnreadReactions, readAllReactions, fetchUnreadMentions, readAllMentions, transcribeAudio,
|
saveDefaultSendAs, fetchUnreadReactions, readAllReactions, fetchUnreadMentions, readAllMentions, transcribeAudio,
|
||||||
closePoll, fetchExtendedMedia, translateText, fetchMessageViews, fetchDiscussionMessage, clickSponsoredMessage,
|
closePoll, fetchExtendedMedia, translateText, fetchMessageViews, fetchDiscussionMessage, clickSponsoredMessage,
|
||||||
|
deleteSavedHistory,
|
||||||
} from './messages';
|
} from './messages';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import type { MethodArgs, MethodResponse, Methods } from './types';
|
|||||||
import { API_THROTTLE_RESET_UPDATES, API_UPDATE_THROTTLE } from '../../../config';
|
import { API_THROTTLE_RESET_UPDATES, API_UPDATE_THROTTLE } from '../../../config';
|
||||||
import { throttle, throttleWithTickEnd } from '../../../util/schedulers';
|
import { throttle, throttleWithTickEnd } from '../../../util/schedulers';
|
||||||
import { updateFullLocalDb } from '../localDb';
|
import { updateFullLocalDb } from '../localDb';
|
||||||
import { init as initUpdater } from '../updater';
|
import { init as initUpdater } from '../updates/updater';
|
||||||
import { init as initAuth } from './auth';
|
import { init as initAuth } from './auth';
|
||||||
import { init as initBots } from './bots';
|
import { init as initBots } from './bots';
|
||||||
import { init as initCalls } from './calls';
|
import { init as initCalls } from './calls';
|
||||||
|
|||||||
@ -80,8 +80,8 @@ import {
|
|||||||
addMessageToLocalDb,
|
addMessageToLocalDb,
|
||||||
deserializeBytes,
|
deserializeBytes,
|
||||||
} from '../helpers';
|
} from '../helpers';
|
||||||
import { updateChannelState } from '../updateManager';
|
import { processAffectedHistory, updateChannelState } from '../updates/updateManager';
|
||||||
import { dispatchThreadInfoUpdates } from '../updater';
|
import { dispatchThreadInfoUpdates } from '../updates/updater';
|
||||||
import { requestChatUpdate } from './chats';
|
import { requestChatUpdate } from './chats';
|
||||||
import { handleGramJsUpdate, invokeRequest, uploadFile } from './client';
|
import { handleGramJsUpdate, invokeRequest, uploadFile } from './client';
|
||||||
|
|
||||||
@ -715,10 +715,18 @@ export async function pinMessage({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function unpinAllMessages({ chat, threadId }: { chat: ApiChat; threadId?: ThreadId }) {
|
export async function unpinAllMessages({ chat, threadId }: { chat: ApiChat; threadId?: ThreadId }) {
|
||||||
await invokeRequest(new GramJs.messages.UnpinAllMessages({
|
const result = await invokeRequest(new GramJs.messages.UnpinAllMessages({
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
...(threadId && { topMsgId: Number(threadId) }),
|
...(threadId && { topMsgId: Number(threadId) }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
if (!result) return;
|
||||||
|
|
||||||
|
processAffectedHistory(chat, result);
|
||||||
|
|
||||||
|
if (result.offset) {
|
||||||
|
await unpinAllMessages({ chat, threadId });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteMessages({
|
export async function deleteMessages({
|
||||||
@ -744,6 +752,8 @@ export async function deleteMessages({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
processAffectedHistory(chat, result);
|
||||||
|
|
||||||
onUpdate({
|
onUpdate({
|
||||||
'@type': 'deleteMessages',
|
'@type': 'deleteMessages',
|
||||||
ids: messageIds,
|
ids: messageIds,
|
||||||
@ -784,9 +794,13 @@ export async function deleteHistory({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ('offset' in result && result.offset) {
|
if ('offset' in result) {
|
||||||
await deleteHistory({ chat, shouldDeleteForAll });
|
processAffectedHistory(chat, result);
|
||||||
return;
|
|
||||||
|
if (result.offset) {
|
||||||
|
await deleteHistory({ chat, shouldDeleteForAll });
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onUpdate({
|
onUpdate({
|
||||||
@ -795,6 +809,32 @@ export async function deleteHistory({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function deleteSavedHistory({
|
||||||
|
chat,
|
||||||
|
}: {
|
||||||
|
chat: ApiChat;
|
||||||
|
}) {
|
||||||
|
const result = await invokeRequest(new GramJs.messages.DeleteSavedHistory({
|
||||||
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
processAffectedHistory(chat, result);
|
||||||
|
|
||||||
|
if (result.offset) {
|
||||||
|
await deleteSavedHistory({ chat });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
onUpdate({
|
||||||
|
'@type': 'deleteSavedHistory',
|
||||||
|
chatId: chat.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function reportMessages({
|
export async function reportMessages({
|
||||||
peer, messageIds, reason, description,
|
peer, messageIds, reason, description,
|
||||||
}: {
|
}: {
|
||||||
@ -862,10 +902,14 @@ export async function markMessageListRead({
|
|||||||
readMaxId: fixedMaxId,
|
readMaxId: fixedMaxId,
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
await invokeRequest(new GramJs.messages.ReadHistory({
|
const result = await invokeRequest(new GramJs.messages.ReadHistory({
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
maxId: fixedMaxId,
|
maxId: fixedMaxId,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
processAffectedHistory(chat, result);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (threadId === MAIN_THREAD_ID) {
|
if (threadId === MAIN_THREAD_ID) {
|
||||||
@ -880,7 +924,7 @@ export async function markMessagesRead({
|
|||||||
}) {
|
}) {
|
||||||
const isChannel = getEntityTypeById(chat.id) === 'channel';
|
const isChannel = getEntityTypeById(chat.id) === 'channel';
|
||||||
|
|
||||||
await invokeRequest(
|
const result = await invokeRequest(
|
||||||
isChannel
|
isChannel
|
||||||
? new GramJs.channels.ReadMessageContents({
|
? new GramJs.channels.ReadMessageContents({
|
||||||
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
|
channel: buildInputEntity(chat.id, chat.accessHash) as GramJs.InputChannel,
|
||||||
@ -891,6 +935,14 @@ export async function markMessagesRead({
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result !== true) {
|
||||||
|
processAffectedHistory(chat, result);
|
||||||
|
}
|
||||||
|
|
||||||
onUpdate({
|
onUpdate({
|
||||||
...(isChannel ? {
|
...(isChannel ? {
|
||||||
'@type': 'updateChannelMessages',
|
'@type': 'updateChannelMessages',
|
||||||
@ -1575,28 +1627,40 @@ export function clickSponsoredMessage({ chat, random }: { chat: ApiChat; random:
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readAllMentions({
|
export async function readAllMentions({
|
||||||
chat,
|
chat,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
}) {
|
}) {
|
||||||
return invokeRequest(new GramJs.messages.ReadMentions({
|
const result = await invokeRequest(new GramJs.messages.ReadMentions({
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
}), {
|
}));
|
||||||
shouldReturnTrue: true,
|
|
||||||
});
|
if (!result) return;
|
||||||
|
|
||||||
|
processAffectedHistory(chat, result);
|
||||||
|
|
||||||
|
if (result.offset) {
|
||||||
|
await readAllMentions({ chat });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readAllReactions({
|
export async function readAllReactions({
|
||||||
chat,
|
chat,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
}) {
|
}) {
|
||||||
return invokeRequest(new GramJs.messages.ReadReactions({
|
const result = await invokeRequest(new GramJs.messages.ReadReactions({
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
}), {
|
}));
|
||||||
shouldReturnTrue: true,
|
|
||||||
});
|
if (!result) return;
|
||||||
|
|
||||||
|
processAffectedHistory(chat, result);
|
||||||
|
|
||||||
|
if (result.offset) {
|
||||||
|
await readAllReactions({ chat });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchUnreadMentions({
|
export async function fetchUnreadMentions({
|
||||||
|
|||||||
16
src/api/gramjs/updates/UpdatePts.ts
Normal file
16
src/api/gramjs/updates/UpdatePts.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/* eslint-disable max-classes-per-file */
|
||||||
|
import type { BigInteger } from 'big-integer';
|
||||||
|
|
||||||
|
export class LocalUpdatePts {
|
||||||
|
constructor(public pts: number, public ptsCount: number) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class LocalUpdateChannelPts {
|
||||||
|
constructor(public channelId: BigInteger, public pts: number, public ptsCount: number) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpdatePts = LocalUpdatePts | LocalUpdateChannelPts;
|
||||||
|
|
||||||
|
export function buildLocalUpdatePts(pts: number, ptsCount: number, channelId?: BigInteger) {
|
||||||
|
return channelId ? new LocalUpdateChannelPts(channelId, pts, ptsCount) : new LocalUpdatePts(pts, ptsCount);
|
||||||
|
}
|
||||||
@ -1,17 +1,20 @@
|
|||||||
import { Api as GramJs } from '../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
import { UpdateConnectionState, UpdateServerTimeOffset } from '../../lib/gramjs/network';
|
import { UpdateConnectionState, UpdateServerTimeOffset } from '../../../lib/gramjs/network';
|
||||||
|
|
||||||
import type { invokeRequest } from './methods/client';
|
import type { ApiChat } from '../../types';
|
||||||
|
import type { invokeRequest } from '../methods/client';
|
||||||
import type { Update } from './updater';
|
import type { Update } from './updater';
|
||||||
|
|
||||||
import { DEBUG } from '../../config';
|
import { DEBUG } from '../../../config';
|
||||||
import SortedQueue from '../../util/SortedQueue';
|
import SortedQueue from '../../../util/SortedQueue';
|
||||||
import { buildApiPeerId } from './apiBuilders/peers';
|
import { buildApiPeerId } from '../apiBuilders/peers';
|
||||||
import { buildInputEntity } from './gramjsBuilders';
|
import { buildInputEntity, buildMtpPeerId } from '../gramjsBuilders';
|
||||||
import { addEntitiesToLocalDb } from './helpers';
|
import { addEntitiesToLocalDb } from '../helpers';
|
||||||
import localDb from './localDb';
|
import localDb from '../localDb';
|
||||||
import { dispatchUserAndChatUpdates, sendUpdate, updater } from './updater';
|
import { dispatchUserAndChatUpdates, sendUpdate, updater } from './updater';
|
||||||
|
|
||||||
|
import { buildLocalUpdatePts, type UpdatePts } from './UpdatePts';
|
||||||
|
|
||||||
export type State = {
|
export type State = {
|
||||||
seq: number;
|
seq: number;
|
||||||
date: number;
|
date: number;
|
||||||
@ -19,7 +22,7 @@ export type State = {
|
|||||||
qts: number;
|
qts: number;
|
||||||
};
|
};
|
||||||
type SeqUpdate = (GramJs.Updates | GramJs.UpdatesCombined) & { _isFromDifference?: true };
|
type SeqUpdate = (GramJs.Updates | GramJs.UpdatesCombined) & { _isFromDifference?: true };
|
||||||
type PtsUpdate = GramJs.TypeUpdate & { pts: number } & { _isFromDifference?: true };
|
type PtsUpdate = ((GramJs.TypeUpdate & { pts: number }) | UpdatePts) & { _isFromDifference?: true };
|
||||||
|
|
||||||
const COMMON_BOX_QUEUE_ID = '0';
|
const COMMON_BOX_QUEUE_ID = '0';
|
||||||
const CHANNEL_DIFFERENCE_LIMIT = 1000;
|
const CHANNEL_DIFFERENCE_LIMIT = 1000;
|
||||||
@ -201,10 +204,12 @@ function popPtsQueue(channelId: string) {
|
|||||||
const pts = update.pts;
|
const pts = update.pts;
|
||||||
const ptsCount = getPtsCount(update);
|
const ptsCount = getPtsCount(update);
|
||||||
|
|
||||||
|
// Sometimes server sends updates for channels that are opened in other clients. We ignore them
|
||||||
if (localPts === undefined) {
|
if (localPts === undefined) {
|
||||||
if (DEBUG) {
|
if (DEBUG) {
|
||||||
|
// Uncomment to debug missing updates
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error('[UpdateManager] Got pts update without local state', channelId);
|
// console.error('[UpdateManager] Got pts update without local state', channelId);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -389,6 +394,16 @@ export function reset() {
|
|||||||
isInited = false;
|
isInited = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function processAffectedHistory(
|
||||||
|
chat: ApiChat, affected: GramJs.messages.AffectedMessages | GramJs.messages.AffectedHistory,
|
||||||
|
) {
|
||||||
|
const isChannel = chat.type === 'chatTypeChannel' || chat.type === 'chatTypeSuperGroup';
|
||||||
|
const channeId = isChannel ? buildMtpPeerId(chat.id, 'channel') : undefined;
|
||||||
|
const update = buildLocalUpdatePts(affected.pts, affected.ptsCount, channeId);
|
||||||
|
|
||||||
|
processUpdate(update);
|
||||||
|
}
|
||||||
|
|
||||||
async function loadRemoteState() {
|
async function loadRemoteState() {
|
||||||
const remoteState = await invoke(new GramJs.updates.GetState());
|
const remoteState = await invoke(new GramJs.updates.GetState());
|
||||||
if (!remoteState) return;
|
if (!remoteState) return;
|
||||||
@ -1,21 +1,21 @@
|
|||||||
import { Api as GramJs, connection } from '../../lib/gramjs';
|
import { Api as GramJs, connection } from '../../../lib/gramjs';
|
||||||
|
|
||||||
import type { GroupCallConnectionData } from '../../lib/secret-sauce';
|
import type { GroupCallConnectionData } from '../../../lib/secret-sauce';
|
||||||
import type {
|
import type {
|
||||||
ApiMessage, ApiMessageExtendedMediaPreview, ApiStory, ApiStorySkipped,
|
ApiMessage, ApiMessageExtendedMediaPreview, ApiStory, ApiStorySkipped,
|
||||||
ApiUpdate, ApiUpdateConnectionStateType, MediaContent, OnApiUpdate,
|
ApiUpdate, ApiUpdateConnectionStateType, MediaContent, OnApiUpdate,
|
||||||
} from '../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { DEBUG, GENERAL_TOPIC_ID } from '../../config';
|
import { DEBUG, GENERAL_TOPIC_ID } from '../../../config';
|
||||||
import { compact, omit, pick } from '../../util/iteratees';
|
import { compact, omit, pick } from '../../../util/iteratees';
|
||||||
import { getServerTimeOffset, setServerTimeOffset } from '../../util/serverTime';
|
import { getServerTimeOffset, setServerTimeOffset } from '../../../util/serverTime';
|
||||||
import { buildApiBotMenuButton } from './apiBuilders/bots';
|
import { buildApiBotMenuButton } from '../apiBuilders/bots';
|
||||||
import {
|
import {
|
||||||
buildApiGroupCall,
|
buildApiGroupCall,
|
||||||
buildApiGroupCallParticipant,
|
buildApiGroupCallParticipant,
|
||||||
buildPhoneCall,
|
buildPhoneCall,
|
||||||
getGroupCallId,
|
getGroupCallId,
|
||||||
} from './apiBuilders/calls';
|
} from '../apiBuilders/calls';
|
||||||
import {
|
import {
|
||||||
buildApiChatFolder,
|
buildApiChatFolder,
|
||||||
buildApiChatFromPreview,
|
buildApiChatFromPreview,
|
||||||
@ -24,15 +24,15 @@ import {
|
|||||||
buildChatMember,
|
buildChatMember,
|
||||||
buildChatMembers,
|
buildChatMembers,
|
||||||
buildChatTypingStatus,
|
buildChatTypingStatus,
|
||||||
} from './apiBuilders/chats';
|
} from '../apiBuilders/chats';
|
||||||
import { buildApiPhoto, buildApiUsernames, buildPrivacyRules } from './apiBuilders/common';
|
import { buildApiPhoto, buildApiUsernames, buildPrivacyRules } from '../apiBuilders/common';
|
||||||
import { omitVirtualClassFields } from './apiBuilders/helpers';
|
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
||||||
import {
|
import {
|
||||||
buildApiMessageExtendedMediaPreview,
|
buildApiMessageExtendedMediaPreview,
|
||||||
buildMessageMediaContent,
|
buildMessageMediaContent,
|
||||||
buildPoll,
|
buildPoll,
|
||||||
buildPollResults,
|
buildPollResults,
|
||||||
} from './apiBuilders/messageContent';
|
} from '../apiBuilders/messageContent';
|
||||||
import {
|
import {
|
||||||
buildApiMessage,
|
buildApiMessage,
|
||||||
buildApiMessageFromNotification,
|
buildApiMessageFromNotification,
|
||||||
@ -40,28 +40,28 @@ import {
|
|||||||
buildApiMessageFromShortChat,
|
buildApiMessageFromShortChat,
|
||||||
buildApiThreadInfoFromMessage,
|
buildApiThreadInfoFromMessage,
|
||||||
buildMessageDraft,
|
buildMessageDraft,
|
||||||
} from './apiBuilders/messages';
|
} from '../apiBuilders/messages';
|
||||||
import {
|
import {
|
||||||
buildApiNotifyException,
|
buildApiNotifyException,
|
||||||
buildApiNotifyExceptionTopic,
|
buildApiNotifyExceptionTopic,
|
||||||
buildPrivacyKey,
|
buildPrivacyKey,
|
||||||
} from './apiBuilders/misc';
|
} from '../apiBuilders/misc';
|
||||||
import { buildApiEmojiStatus, buildApiPeerId, getApiChatIdFromMtpPeer } from './apiBuilders/peers';
|
import { buildApiEmojiStatus, buildApiPeerId, getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
||||||
import {
|
import {
|
||||||
buildApiReaction,
|
buildApiReaction,
|
||||||
buildMessageReactions,
|
buildMessageReactions,
|
||||||
} from './apiBuilders/reactions';
|
} from '../apiBuilders/reactions';
|
||||||
import { buildApiStealthMode, buildApiStory } from './apiBuilders/stories';
|
import { buildApiStealthMode, buildApiStory } from '../apiBuilders/stories';
|
||||||
import { buildApiEmojiInteraction, buildStickerSet } from './apiBuilders/symbols';
|
import { buildApiEmojiInteraction, buildStickerSet } from '../apiBuilders/symbols';
|
||||||
import {
|
import {
|
||||||
buildApiUser,
|
buildApiUser,
|
||||||
buildApiUserStatus,
|
buildApiUserStatus,
|
||||||
} from './apiBuilders/users';
|
} from '../apiBuilders/users';
|
||||||
import {
|
import {
|
||||||
buildChatPhotoForLocalDb,
|
buildChatPhotoForLocalDb,
|
||||||
buildMessageFromUpdate,
|
buildMessageFromUpdate,
|
||||||
isMessageWithMedia,
|
isMessageWithMedia,
|
||||||
} from './gramjsBuilders';
|
} from '../gramjsBuilders';
|
||||||
import {
|
import {
|
||||||
addEntitiesToLocalDb,
|
addEntitiesToLocalDb,
|
||||||
addMessageToLocalDb,
|
addMessageToLocalDb,
|
||||||
@ -72,13 +72,15 @@ import {
|
|||||||
resolveMessageApiChatId,
|
resolveMessageApiChatId,
|
||||||
serializeBytes,
|
serializeBytes,
|
||||||
swapLocalInvoiceMedia,
|
swapLocalInvoiceMedia,
|
||||||
} from './helpers';
|
} from '../helpers';
|
||||||
import localDb from './localDb';
|
import localDb from '../localDb';
|
||||||
import { scheduleMutedChatUpdate, scheduleMutedTopicUpdate } from './scheduleUnmute';
|
import { scheduleMutedChatUpdate, scheduleMutedTopicUpdate } from '../scheduleUnmute';
|
||||||
|
|
||||||
|
import { LocalUpdateChannelPts, LocalUpdatePts, type UpdatePts } from './UpdatePts';
|
||||||
|
|
||||||
export type Update = (
|
export type Update = (
|
||||||
(GramJs.TypeUpdate | GramJs.TypeUpdates) & { _entities?: (GramJs.TypeUser | GramJs.TypeChat)[] }
|
(GramJs.TypeUpdate | GramJs.TypeUpdates) & { _entities?: (GramJs.TypeUser | GramJs.TypeChat)[] }
|
||||||
) | typeof connection.UpdateConnectionState;
|
) | typeof connection.UpdateConnectionState | UpdatePts;
|
||||||
|
|
||||||
const DELETE_MISSING_CHANNEL_MESSAGE_DELAY = 1000;
|
const DELETE_MISSING_CHANNEL_MESSAGE_DELAY = 1000;
|
||||||
|
|
||||||
@ -1156,6 +1158,8 @@ export function updater(update: Update) {
|
|||||||
chatId: buildApiPeerId(update.channelId, 'channel'),
|
chatId: buildApiPeerId(update.channelId, 'channel'),
|
||||||
isEnabled: update.enabled ? true : undefined,
|
isEnabled: update.enabled ? true : undefined,
|
||||||
});
|
});
|
||||||
|
} else if (update instanceof LocalUpdatePts || update instanceof LocalUpdateChannelPts) {
|
||||||
|
// Do nothing, handled on the manager side
|
||||||
} else if (DEBUG) {
|
} else if (DEBUG) {
|
||||||
const params = typeof update === 'object' && 'className' in update ? update.className : update;
|
const params = typeof update === 'object' && 'className' in update ? update.className : update;
|
||||||
log('UNEXPECTED UPDATE', params);
|
log('UNEXPECTED UPDATE', params);
|
||||||
@ -536,6 +536,7 @@ export interface ApiMessage {
|
|||||||
};
|
};
|
||||||
reactions?: ApiReactions;
|
reactions?: ApiReactions;
|
||||||
hasComments?: boolean;
|
hasComments?: boolean;
|
||||||
|
savedPeerId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiReactions {
|
export interface ApiReactions {
|
||||||
|
|||||||
@ -315,6 +315,11 @@ export type ApiUpdateDeleteHistory = {
|
|||||||
chatId: string;
|
chatId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiUpdateDeleteSavedHistory = {
|
||||||
|
'@type': 'deleteSavedHistory';
|
||||||
|
chatId: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type ApiUpdateDeleteProfilePhotos = {
|
export type ApiUpdateDeleteProfilePhotos = {
|
||||||
'@type': 'deleteProfilePhotos';
|
'@type': 'deleteProfilePhotos';
|
||||||
ids: string[];
|
ids: string[];
|
||||||
@ -730,7 +735,8 @@ 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 | ApiUpdateSavedDialogPinned | ApiUpdatePinnedSavedDialogIds | ApiUpdateChatLastMessage
|
ApiUpdateViewForumAsMessages | ApiUpdateSavedDialogPinned | ApiUpdatePinnedSavedDialogIds | ApiUpdateChatLastMessage |
|
||||||
|
ApiUpdateDeleteSavedHistory
|
||||||
);
|
);
|
||||||
|
|
||||||
export type OnApiUpdate = (update: ApiUpdate) => void;
|
export type OnApiUpdate = (update: ApiUpdate) => void;
|
||||||
|
|||||||
@ -28,6 +28,7 @@ import './DeleteChatModal.scss';
|
|||||||
export type OwnProps = {
|
export type OwnProps = {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
|
isSavedDialog?: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onCloseAnimationEnd?: () => void;
|
onCloseAnimationEnd?: () => void;
|
||||||
};
|
};
|
||||||
@ -47,6 +48,7 @@ type StateProps = {
|
|||||||
const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
||||||
isOpen,
|
isOpen,
|
||||||
chat,
|
chat,
|
||||||
|
isSavedDialog,
|
||||||
isChannel,
|
isChannel,
|
||||||
isPrivateChat,
|
isPrivateChat,
|
||||||
isChatWithSelf,
|
isChatWithSelf,
|
||||||
@ -62,6 +64,7 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
|||||||
const {
|
const {
|
||||||
leaveChannel,
|
leaveChannel,
|
||||||
deleteHistory,
|
deleteHistory,
|
||||||
|
deleteSavedHistory,
|
||||||
deleteChannel,
|
deleteChannel,
|
||||||
deleteChatUser,
|
deleteChatUser,
|
||||||
blockUser,
|
blockUser,
|
||||||
@ -74,7 +77,7 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
|||||||
deleteHistory({ chatId: chat.id, shouldDeleteForAll: true });
|
deleteHistory({ chatId: chat.id, shouldDeleteForAll: true });
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
}, [deleteHistory, chat.id, onClose]);
|
}, [chat.id, onClose]);
|
||||||
|
|
||||||
const handleDeleteAndStop = useCallback(() => {
|
const handleDeleteAndStop = useCallback(() => {
|
||||||
deleteHistory({ chatId: chat.id, shouldDeleteForAll: true });
|
deleteHistory({ chatId: chat.id, shouldDeleteForAll: true });
|
||||||
@ -84,7 +87,9 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
|||||||
}, [chat.id, onClose]);
|
}, [chat.id, onClose]);
|
||||||
|
|
||||||
const handleDeleteChat = useCallback(() => {
|
const handleDeleteChat = useCallback(() => {
|
||||||
if (isPrivateChat) {
|
if (isSavedDialog) {
|
||||||
|
deleteSavedHistory({ chatId: chat.id });
|
||||||
|
} else if (isPrivateChat) {
|
||||||
deleteHistory({ chatId: chat.id, shouldDeleteForAll: false });
|
deleteHistory({ chatId: chat.id, shouldDeleteForAll: false });
|
||||||
} else if (isBasicGroup) {
|
} else if (isBasicGroup) {
|
||||||
deleteChatUser({ chatId: chat.id, userId: currentUserId! });
|
deleteChatUser({ chatId: chat.id, userId: currentUserId! });
|
||||||
@ -103,11 +108,8 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
|||||||
currentUserId,
|
currentUserId,
|
||||||
chat.isCreator,
|
chat.isCreator,
|
||||||
chat.id,
|
chat.id,
|
||||||
|
isSavedDialog,
|
||||||
onClose,
|
onClose,
|
||||||
deleteHistory,
|
|
||||||
deleteChatUser,
|
|
||||||
leaveChannel,
|
|
||||||
deleteChannel,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleLeaveChat = useCallback(() => {
|
const handleLeaveChat = useCallback(() => {
|
||||||
@ -133,6 +135,10 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderTitle() {
|
function renderTitle() {
|
||||||
|
if (isSavedDialog) {
|
||||||
|
return isChatWithSelf ? 'ClearHistoryMyNotesTitle' : 'ClearHistoryTitleSingle2';
|
||||||
|
}
|
||||||
|
|
||||||
if (isChannel && !chat.isCreator) {
|
if (isChannel && !chat.isCreator) {
|
||||||
return 'LeaveChannel';
|
return 'LeaveChannel';
|
||||||
}
|
}
|
||||||
@ -149,6 +155,16 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderContent() {
|
function renderContent() {
|
||||||
|
if (isSavedDialog) {
|
||||||
|
return (
|
||||||
|
<p>
|
||||||
|
{renderText(
|
||||||
|
isChatWithSelf ? lang('ClearHistoryMyNotesMessage') : lang('ClearHistoryMessageSingle', chatTitle),
|
||||||
|
['simple_markdown', 'emoji'],
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
}
|
||||||
if (isChannel && chat.isCreator) {
|
if (isChannel && chat.isCreator) {
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
@ -165,6 +181,10 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderActionText() {
|
function renderActionText() {
|
||||||
|
if (isSavedDialog) {
|
||||||
|
return 'Delete';
|
||||||
|
}
|
||||||
|
|
||||||
if (isChannel && !chat.isCreator) {
|
if (isChannel && !chat.isCreator) {
|
||||||
return 'LeaveChannel';
|
return 'LeaveChannel';
|
||||||
}
|
}
|
||||||
@ -189,7 +209,7 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
|||||||
>
|
>
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
<div className="dialog-buttons-column">
|
<div className="dialog-buttons-column">
|
||||||
{isBot && (
|
{isBot && !isSavedDialog && (
|
||||||
<Button color="danger" className="confirm-dialog-button" isText onClick={handleDeleteAndStop}>
|
<Button color="danger" className="confirm-dialog-button" isText onClick={handleDeleteAndStop}>
|
||||||
{lang('DeleteAndStop')}
|
{lang('DeleteAndStop')}
|
||||||
</Button>
|
</Button>
|
||||||
@ -199,7 +219,7 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
|||||||
{contactName ? renderText(lang('ChatList.DeleteForEveryone', contactName)) : lang('DeleteForAll')}
|
{contactName ? renderText(lang('ChatList.DeleteForEveryone', contactName)) : lang('DeleteForAll')}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
{!isPrivateChat && chat.isCreator && (
|
{!isPrivateChat && chat.isCreator && !isSavedDialog && (
|
||||||
<Button color="danger" className="confirm-dialog-button" isText onClick={handleDeleteChat}>
|
<Button color="danger" className="confirm-dialog-button" isText onClick={handleDeleteChat}>
|
||||||
{lang('DeleteForAll')}
|
{lang('DeleteForAll')}
|
||||||
</Button>
|
</Button>
|
||||||
@ -208,7 +228,7 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
|||||||
color="danger"
|
color="danger"
|
||||||
className="confirm-dialog-button"
|
className="confirm-dialog-button"
|
||||||
isText
|
isText
|
||||||
onClick={isPrivateChat ? handleDeleteChat : handleLeaveChat}
|
onClick={(isPrivateChat || isSavedDialog) ? handleDeleteChat : handleLeaveChat}
|
||||||
>
|
>
|
||||||
{lang(renderActionText())}
|
{lang(renderActionText())}
|
||||||
</Button>
|
</Button>
|
||||||
@ -219,12 +239,12 @@ const DeleteChatModal: FC<OwnProps & StateProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { chat }): StateProps => {
|
(global, { chat, isSavedDialog }): StateProps => {
|
||||||
const isPrivateChat = isUserId(chat.id);
|
const isPrivateChat = isUserId(chat.id);
|
||||||
const isChatWithSelf = selectIsChatWithSelf(global, chat.id);
|
const isChatWithSelf = selectIsChatWithSelf(global, chat.id);
|
||||||
const user = isPrivateChat && selectUser(global, getPrivateChatUserId(chat)!);
|
const user = isPrivateChat && selectUser(global, getPrivateChatUserId(chat)!);
|
||||||
const isBot = user && isUserBot(user) && !chat.isSupport;
|
const isBot = user && isUserBot(user) && !chat.isSupport;
|
||||||
const canDeleteForAll = (isPrivateChat && !isChatWithSelf && !isBot);
|
const canDeleteForAll = (isPrivateChat && !isChatWithSelf && !isBot && !isSavedDialog);
|
||||||
const contactName = isPrivateChat
|
const contactName = isPrivateChat
|
||||||
? getUserFirstOrLastName(selectUser(global, getPrivateChatUserId(chat)!))
|
? getUserFirstOrLastName(selectUser(global, getPrivateChatUserId(chat)!))
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|||||||
@ -354,6 +354,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
|||||||
onClose={closeDeleteModal}
|
onClose={closeDeleteModal}
|
||||||
onCloseAnimationEnd={unmarkRenderDeleteModal}
|
onCloseAnimationEnd={unmarkRenderDeleteModal}
|
||||||
chat={chat}
|
chat={chat}
|
||||||
|
isSavedDialog={isSavedDialog}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{shouldRenderMuteModal && (
|
{shouldRenderMuteModal && (
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import { ManagementScreens } from '../../types';
|
|||||||
import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterdom';
|
import { requestMeasure, requestNextMutation } from '../../lib/fasterdom/fasterdom';
|
||||||
import {
|
import {
|
||||||
getHasAdminRight,
|
getHasAdminRight,
|
||||||
|
getIsSavedDialog,
|
||||||
isAnonymousForwardsChat,
|
isAnonymousForwardsChat,
|
||||||
isChatBasicGroup, isChatChannel, isChatSuperGroup, isUserId,
|
isChatBasicGroup, isChatChannel, isChatSuperGroup, isUserId,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
@ -478,6 +479,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const isDiscussionThread = messageListType === 'thread' && threadId !== MAIN_THREAD_ID;
|
const isDiscussionThread = messageListType === 'thread' && threadId !== MAIN_THREAD_ID;
|
||||||
const isRightColumnShown = selectIsRightColumnShown(global, isMobile);
|
const isRightColumnShown = selectIsRightColumnShown(global, isMobile);
|
||||||
|
|
||||||
|
const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId);
|
||||||
|
|
||||||
const isUserBlocked = isPrivate ? selectIsUserBlocked(global, chatId) : false;
|
const isUserBlocked = isPrivate ? selectIsUserBlocked(global, chatId) : false;
|
||||||
const canRestartBot = Boolean(bot && isUserBlocked);
|
const canRestartBot = Boolean(bot && isUserBlocked);
|
||||||
const canStartBot = !canRestartBot && Boolean(selectIsChatBotNotStarted(global, chatId));
|
const canStartBot = !canRestartBot && Boolean(selectIsChatBotNotStarted(global, chatId));
|
||||||
@ -489,7 +492,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const canCall = ARE_CALLS_SUPPORTED && isUserId(chat.id) && !isChatWithSelf && !bot && !chat.isSupport
|
const canCall = ARE_CALLS_SUPPORTED && isUserId(chat.id) && !isChatWithSelf && !bot && !chat.isSupport
|
||||||
&& !isAnonymousForwardsChat(chat.id);
|
&& !isAnonymousForwardsChat(chat.id);
|
||||||
const canMute = isMainThread && !isChatWithSelf && !canSubscribe;
|
const canMute = isMainThread && !isChatWithSelf && !canSubscribe;
|
||||||
const canLeave = isMainThread && !canSubscribe;
|
const canLeave = isSavedDialog || (isMainThread && !canSubscribe);
|
||||||
const canEnterVoiceChat = ARE_CALLS_SUPPORTED && isMainThread && chat.isCallActive;
|
const canEnterVoiceChat = ARE_CALLS_SUPPORTED && isMainThread && chat.isCallActive;
|
||||||
const canCreateVoiceChat = ARE_CALLS_SUPPORTED && isMainThread && !chat.isCallActive
|
const canCreateVoiceChat = ARE_CALLS_SUPPORTED && isMainThread && !chat.isCallActive
|
||||||
&& (chat.adminRights?.manageCall || (chat.isCreator && isChatBasicGroup(chat)));
|
&& (chat.adminRights?.manageCall || (chat.isCreator && isChatBasicGroup(chat)));
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import {
|
|||||||
getCanDeleteChat,
|
getCanDeleteChat,
|
||||||
getCanManageTopic,
|
getCanManageTopic,
|
||||||
getHasAdminRight,
|
getHasAdminRight,
|
||||||
|
getIsSavedDialog,
|
||||||
isChatChannel,
|
isChatChannel,
|
||||||
isChatGroup,
|
isChatGroup,
|
||||||
isUserId,
|
isUserId,
|
||||||
@ -119,6 +120,7 @@ type StateProps = {
|
|||||||
isBlocked?: boolean;
|
isBlocked?: boolean;
|
||||||
isBot?: boolean;
|
isBot?: boolean;
|
||||||
isChatWithSelf?: boolean;
|
isChatWithSelf?: boolean;
|
||||||
|
savedDialog?: ApiChat;
|
||||||
};
|
};
|
||||||
|
|
||||||
const CLOSE_MENU_ANIMATION_DURATION = 200;
|
const CLOSE_MENU_ANIMATION_DURATION = 200;
|
||||||
@ -163,6 +165,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
isBlocked,
|
isBlocked,
|
||||||
isBot,
|
isBot,
|
||||||
isChatWithSelf,
|
isChatWithSelf,
|
||||||
|
savedDialog,
|
||||||
onJoinRequestsClick,
|
onJoinRequestsClick,
|
||||||
onSubscribeChannel,
|
onSubscribeChannel,
|
||||||
onSearchClick,
|
onSearchClick,
|
||||||
@ -403,6 +406,24 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
}, [botCommands, closeMenu, lang, sendBotCommand]);
|
}, [botCommands, closeMenu, lang, sendBotCommand]);
|
||||||
|
|
||||||
|
const deleteTitle = useMemo(() => {
|
||||||
|
if (!chat) return undefined;
|
||||||
|
|
||||||
|
if (isPrivate || savedDialog) {
|
||||||
|
return lang('DeleteChatUser');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (canDeleteChat) {
|
||||||
|
return lang('GroupInfo.DeleteAndExit');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isChannel) {
|
||||||
|
return lang('LeaveChannel');
|
||||||
|
}
|
||||||
|
|
||||||
|
return lang('Group.LeaveGroup');
|
||||||
|
}, [canDeleteChat, chat, isChannel, isPrivate, savedDialog, lang]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Portal>
|
<Portal>
|
||||||
<div className="HeaderMenuContainer">
|
<div className="HeaderMenuContainer">
|
||||||
@ -627,9 +648,7 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
icon="delete"
|
icon="delete"
|
||||||
onClick={handleDelete}
|
onClick={handleDelete}
|
||||||
>
|
>
|
||||||
{lang(isPrivate
|
{deleteTitle}
|
||||||
? 'DeleteChatUser'
|
|
||||||
: (canDeleteChat ? 'GroupInfo.DeleteAndExit' : (isChannel ? 'LeaveChannel' : 'Group.LeaveGroup')))}
|
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@ -638,7 +657,8 @@ const HeaderMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
<DeleteChatModal
|
<DeleteChatModal
|
||||||
isOpen={isDeleteModalOpen}
|
isOpen={isDeleteModalOpen}
|
||||||
onClose={closeDeleteModal}
|
onClose={closeDeleteModal}
|
||||||
chat={chat}
|
chat={savedDialog || chat}
|
||||||
|
isSavedDialog={Boolean(savedDialog)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{canMute && shouldRenderMuteModal && chat?.id && (
|
{canMute && shouldRenderMuteModal && chat?.id && (
|
||||||
@ -694,6 +714,9 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
// Context menu item should only be displayed if user hid translation panel
|
// Context menu item should only be displayed if user hid translation panel
|
||||||
const canTranslate = selectCanTranslateChat(global, chatId) && fullInfo?.isTranslationDisabled;
|
const canTranslate = selectCanTranslateChat(global, chatId) && fullInfo?.isTranslationDisabled;
|
||||||
|
|
||||||
|
const isSavedDialog = getIsSavedDialog(chatId, threadId, global.currentUserId);
|
||||||
|
const savedDialog = isSavedDialog ? selectChat(global, String(threadId)) : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chat,
|
chat,
|
||||||
isMuted: selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)),
|
isMuted: selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)),
|
||||||
@ -717,6 +740,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isBlocked: userFullInfo?.isBlocked,
|
isBlocked: userFullInfo?.isBlocked,
|
||||||
isBot: Boolean(chatBot),
|
isBot: Boolean(chatBot),
|
||||||
isChatWithSelf,
|
isChatWithSelf,
|
||||||
|
savedDialog,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(HeaderMenuContainer));
|
)(HeaderMenuContainer));
|
||||||
|
|||||||
@ -583,7 +583,6 @@ addActionHandler('loadTopChats', (): ActionReturnType => {
|
|||||||
runThrottledForLoadTopChats(() => {
|
runThrottledForLoadTopChats(() => {
|
||||||
loadChats('active');
|
loadChats('active');
|
||||||
loadChats('archived');
|
loadChats('archived');
|
||||||
loadChats('saved');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -2244,9 +2243,7 @@ addActionHandler('deleteTopic', async (global, actions, payload): Promise<void>
|
|||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) return;
|
if (!chat) return;
|
||||||
|
|
||||||
const result = await callApi('deleteTopic', { chat, topicId });
|
await callApi('deleteTopic', { chat, topicId });
|
||||||
|
|
||||||
if (!result) return;
|
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = deleteTopic(global, chatId, topicId);
|
global = deleteTopic(global, chatId, topicId);
|
||||||
|
|||||||
@ -710,6 +710,22 @@ addActionHandler('deleteHistory', async (global, actions, payload): Promise<void
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addActionHandler('deleteSavedHistory', async (global, actions, payload): Promise<void> => {
|
||||||
|
const { chatId, tabId = getCurrentTabId() } = payload!;
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
|
if (!chat) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await callApi('deleteSavedHistory', { chat });
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
const activeChat = selectCurrentMessageList(global, tabId);
|
||||||
|
if (activeChat && activeChat.threadId === chatId) {
|
||||||
|
actions.openChat({ id: undefined, tabId });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
addActionHandler('reportMessages', async (global, actions, payload): Promise<void> => {
|
addActionHandler('reportMessages', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
messageIds, reason, description, tabId = getCurrentTabId(),
|
messageIds, reason, description, tabId = getCurrentTabId(),
|
||||||
|
|||||||
@ -464,6 +464,18 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 'deleteSavedHistory': {
|
||||||
|
const { chatId } = update;
|
||||||
|
const currentUserId = global.currentUserId!;
|
||||||
|
global = removeChatFromChatLists(global, chatId, 'saved');
|
||||||
|
setGlobal(global);
|
||||||
|
|
||||||
|
global = getGlobal();
|
||||||
|
deleteThread(global, currentUserId, chatId, actions);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case 'updateCommonBoxMessages': {
|
case 'updateCommonBoxMessages': {
|
||||||
const { ids, messageUpdate } = update;
|
const { ids, messageUpdate } = update;
|
||||||
|
|
||||||
@ -925,6 +937,29 @@ function findLastMessage<T extends GlobalState>(global: T, chatId: string, threa
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function deleteThread<T extends GlobalState>(
|
||||||
|
global: T,
|
||||||
|
chatId: string,
|
||||||
|
threadId: ThreadId,
|
||||||
|
actions: RequiredGlobalActions,
|
||||||
|
) {
|
||||||
|
const byId = selectChatMessages(global, chatId);
|
||||||
|
if (!byId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messageIds = Object.values(byId).filter((message) => {
|
||||||
|
const messageThreadId = selectThreadIdFromMessage(global, message);
|
||||||
|
return messageThreadId === threadId;
|
||||||
|
}).map((message) => message.id);
|
||||||
|
|
||||||
|
if (!messageIds.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteMessages(global, chatId, messageIds, actions);
|
||||||
|
}
|
||||||
|
|
||||||
export function deleteMessages<T extends GlobalState>(
|
export function deleteMessages<T extends GlobalState>(
|
||||||
global: T, chatId: string | undefined, ids: number[], actions: RequiredGlobalActions,
|
global: T, chatId: string | undefined, ids: number[], actions: RequiredGlobalActions,
|
||||||
) {
|
) {
|
||||||
@ -942,8 +977,6 @@ export function deleteMessages<T extends GlobalState>(
|
|||||||
isDeleting: true,
|
isDeleting: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
global = clearMessageTranslation(global, chatId, id);
|
|
||||||
|
|
||||||
if (chat.topics?.[id]) {
|
if (chat.topics?.[id]) {
|
||||||
global = deleteTopic(global, chatId, id);
|
global = deleteTopic(global, chatId, id);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,6 +36,7 @@ import {
|
|||||||
selectViewportIds,
|
selectViewportIds,
|
||||||
} from '../selectors';
|
} from '../selectors';
|
||||||
import { updateTabState } from './tabs';
|
import { updateTabState } from './tabs';
|
||||||
|
import { clearMessageTranslation } from './translations';
|
||||||
|
|
||||||
type MessageStoreSections = {
|
type MessageStoreSections = {
|
||||||
byId: Record<number, ApiMessage>;
|
byId: Record<number, ApiMessage>;
|
||||||
@ -261,10 +262,12 @@ export function deleteChatMessages<T extends GlobalState>(
|
|||||||
const message = byId[messageId];
|
const message = byId[messageId];
|
||||||
if (!message) return;
|
if (!message) return;
|
||||||
const threadId = selectThreadIdFromMessage(global, message);
|
const threadId = selectThreadIdFromMessage(global, message);
|
||||||
if (!threadId || threadId === MAIN_THREAD_ID) return;
|
if (!threadId) return;
|
||||||
const threadMessages = updatedThreads.get(threadId) || [];
|
const threadMessages = updatedThreads.get(threadId) || [];
|
||||||
threadMessages.push(messageId);
|
threadMessages.push(messageId);
|
||||||
updatedThreads.set(threadId, threadMessages);
|
updatedThreads.set(threadId, threadMessages);
|
||||||
|
|
||||||
|
global = clearMessageTranslation(global, chatId, messageId);
|
||||||
});
|
});
|
||||||
|
|
||||||
const deletedForwardedPosts = Object.values(pickTruthy(byId, messageIds)).filter(
|
const deletedForwardedPosts = Object.values(pickTruthy(byId, messageIds)).filter(
|
||||||
@ -297,15 +300,11 @@ export function deleteChatMessages<T extends GlobalState>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
||||||
let viewportIds = selectViewportIds(global, chatId, threadId, tabId);
|
const viewportIds = selectViewportIds(global, chatId, threadId, tabId);
|
||||||
|
if (!viewportIds) return;
|
||||||
|
|
||||||
messageIds.forEach((messageId) => {
|
const newViewportIds = excludeSortedArray(viewportIds, messageIds);
|
||||||
if (viewportIds?.includes(messageId)) {
|
global = replaceTabThreadParam(global, chatId, threadId, 'viewportIds', newViewportIds, tabId);
|
||||||
viewportIds = viewportIds.filter((id) => id !== messageId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
global = replaceTabThreadParam(global, chatId, threadId, 'viewportIds', viewportIds, tabId);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
global = replaceThreadParam(global, chatId, threadId, 'listedIds', listedIds);
|
global = replaceThreadParam(global, chatId, threadId, 'listedIds', listedIds);
|
||||||
|
|||||||
@ -508,7 +508,12 @@ export function selectCanDeleteTopic<T extends GlobalState>(global: T, chatId: s
|
|||||||
export function selectSavedDialogIdFromMessage<T extends GlobalState>(
|
export function selectSavedDialogIdFromMessage<T extends GlobalState>(
|
||||||
global: T, message: ApiMessage,
|
global: T, message: ApiMessage,
|
||||||
): string | undefined {
|
): string | undefined {
|
||||||
const { chatId, senderId, forwardInfo } = message;
|
const {
|
||||||
|
chatId, senderId, forwardInfo, savedPeerId,
|
||||||
|
} = message;
|
||||||
|
|
||||||
|
if (savedPeerId) return savedPeerId;
|
||||||
|
|
||||||
if (chatId !== global.currentUserId) {
|
if (chatId !== global.currentUserId) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1385,6 +1385,9 @@ export interface ActionPayloads {
|
|||||||
chatId: string;
|
chatId: string;
|
||||||
shouldDeleteForAll?: boolean;
|
shouldDeleteForAll?: boolean;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
deleteSavedHistory: {
|
||||||
|
chatId: string;
|
||||||
|
} & WithTabId;
|
||||||
loadSponsoredMessages: {
|
loadSponsoredMessages: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -45,6 +45,24 @@ const useChatContextActions = ({
|
|||||||
const { isSelf } = user || {};
|
const { isSelf } = user || {};
|
||||||
const isServiceNotifications = user?.id === SERVICE_NOTIFICATIONS_USER_ID;
|
const isServiceNotifications = user?.id === SERVICE_NOTIFICATIONS_USER_ID;
|
||||||
|
|
||||||
|
const deleteTitle = useMemo(() => {
|
||||||
|
if (!chat) return undefined;
|
||||||
|
|
||||||
|
if (isUserId(chat.id) || isSavedDialog) {
|
||||||
|
return lang('DeleteChatUser');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (getCanDeleteChat(chat)) {
|
||||||
|
return lang('DeleteChat');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isChatChannel(chat)) {
|
||||||
|
return lang('LeaveChannel');
|
||||||
|
}
|
||||||
|
|
||||||
|
return lang('Group.LeaveGroup');
|
||||||
|
}, [chat, isSavedDialog, lang]);
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -91,8 +109,15 @@ const useChatContextActions = ({
|
|||||||
handler: togglePinned,
|
handler: togglePinned,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const actionDelete = {
|
||||||
|
title: deleteTitle,
|
||||||
|
icon: 'delete',
|
||||||
|
destructive: true,
|
||||||
|
handler: handleDelete,
|
||||||
|
};
|
||||||
|
|
||||||
if (isSavedDialog) {
|
if (isSavedDialog) {
|
||||||
return compact([actionOpenInNewTab, actionPin]) as MenuItemContextAction[];
|
return compact([actionOpenInNewTab, actionPin, actionDelete]) as MenuItemContextAction[];
|
||||||
}
|
}
|
||||||
|
|
||||||
const actionAddToFolder = canChangeFolder ? {
|
const actionAddToFolder = canChangeFolder ? {
|
||||||
@ -133,17 +158,6 @@ const useChatContextActions = ({
|
|||||||
? { title: lang('ReportPeer.Report'), icon: 'flag', handler: handleReport }
|
? { title: lang('ReportPeer.Report'), icon: 'flag', handler: handleReport }
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const actionDelete = {
|
|
||||||
title: isUserId(chat.id)
|
|
||||||
? lang('Delete')
|
|
||||||
: lang(getCanDeleteChat(chat)
|
|
||||||
? 'DeleteChat'
|
|
||||||
: (isChatChannel(chat) ? 'LeaveChannel' : 'Group.LeaveGroup')),
|
|
||||||
icon: 'delete',
|
|
||||||
destructive: true,
|
|
||||||
handler: handleDelete,
|
|
||||||
};
|
|
||||||
|
|
||||||
const isInFolder = folderId !== undefined;
|
const isInFolder = folderId !== undefined;
|
||||||
|
|
||||||
return compact([
|
return compact([
|
||||||
@ -159,7 +173,7 @@ const useChatContextActions = ({
|
|||||||
]) as MenuItemContextAction[];
|
]) as MenuItemContextAction[];
|
||||||
}, [
|
}, [
|
||||||
chat, user, canChangeFolder, lang, handleChatFolderChange, isPinned, isInSearch, isMuted, currentUserId,
|
chat, user, canChangeFolder, lang, handleChatFolderChange, isPinned, isInSearch, isMuted, currentUserId,
|
||||||
handleDelete, handleMute, handleReport, folderId, isSelf, isServiceNotifications, isSavedDialog,
|
handleDelete, handleMute, handleReport, folderId, isSelf, isServiceNotifications, isSavedDialog, deleteTitle,
|
||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1404,6 +1404,7 @@ messages.getBotApp#34fdc5c3 app:InputBotApp hash:long = messages.BotApp;
|
|||||||
messages.requestAppWebView#8c5a3b3c flags:# write_allowed:flags.0?true peer:InputPeer app:InputBotApp start_param:flags.1?string theme_params:flags.2?DataJSON platform:string = AppWebViewResult;
|
messages.requestAppWebView#8c5a3b3c flags:# write_allowed:flags.0?true peer:InputPeer app:InputBotApp start_param:flags.1?string theme_params:flags.2?DataJSON platform:string = AppWebViewResult;
|
||||||
messages.getSavedDialogs#5381d21a flags:# exclude_pinned:flags.0?true offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.SavedDialogs;
|
messages.getSavedDialogs#5381d21a flags:# exclude_pinned:flags.0?true offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.SavedDialogs;
|
||||||
messages.getSavedHistory#3d9a414d peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
|
messages.getSavedHistory#3d9a414d peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
|
||||||
|
messages.deleteSavedHistory#6e98102b flags:# peer:InputPeer max_id:int min_date:flags.2?int max_date:flags.3?int = messages.AffectedHistory;
|
||||||
messages.getPinnedSavedDialogs#d63d94e0 = messages.SavedDialogs;
|
messages.getPinnedSavedDialogs#d63d94e0 = messages.SavedDialogs;
|
||||||
messages.toggleSavedDialogPin#ac81bbde flags:# pinned:flags.0?true peer:InputDialogPeer = Bool;
|
messages.toggleSavedDialogPin#ac81bbde flags:# pinned:flags.0?true peer:InputDialogPeer = Bool;
|
||||||
updates.getState#edd4882a = updates.State;
|
updates.getState#edd4882a = updates.State;
|
||||||
|
|||||||
@ -284,6 +284,7 @@
|
|||||||
"messages.getUnreadMentions",
|
"messages.getUnreadMentions",
|
||||||
"messages.getSavedDialogs",
|
"messages.getSavedDialogs",
|
||||||
"messages.getSavedHistory",
|
"messages.getSavedHistory",
|
||||||
|
"messages.deleteSavedHistory",
|
||||||
"messages.getPinnedSavedDialogs",
|
"messages.getPinnedSavedDialogs",
|
||||||
"messages.toggleSavedDialogPin",
|
"messages.toggleSavedDialogPin",
|
||||||
"help.getPremiumPromo",
|
"help.getPremiumPromo",
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user