Revert "Support Bot Forums (#6407)"
This reverts commit c3b850e6767b0210ebc9f49177cfbb6086930c75.
This commit is contained in:
parent
a4c655637f
commit
fe69e6b50d
1
src/@types/global.d.ts
vendored
1
src/@types/global.d.ts
vendored
@ -175,6 +175,7 @@ interface BooleanConstructor {
|
|||||||
|
|
||||||
interface Array<T> {
|
interface Array<T> {
|
||||||
filter<S extends T>(predicate: BooleanConstructor, thisArg?: unknown): Exclude<S, Falsy>[];
|
filter<S extends T>(predicate: BooleanConstructor, thisArg?: unknown): Exclude<S, Falsy>[];
|
||||||
|
at(index: number): T; // Make it behave like arr[arr.length - 1]
|
||||||
}
|
}
|
||||||
interface ReadonlyArray<T> {
|
interface ReadonlyArray<T> {
|
||||||
filter<S extends T>(predicate: BooleanConstructor, thisArg?: unknown): Exclude<S, Falsy>[];
|
filter<S extends T>(predicate: BooleanConstructor, thisArg?: unknown): Exclude<S, Falsy>[];
|
||||||
|
|||||||
@ -118,7 +118,6 @@ export interface GramJsAppConfig extends LimitsConfig {
|
|||||||
verify_age_bot_username?: string;
|
verify_age_bot_username?: string;
|
||||||
verify_age_country?: string;
|
verify_age_country?: string;
|
||||||
verify_age_min?: number;
|
verify_age_min?: number;
|
||||||
message_typing_draft_ttl?: number;
|
|
||||||
contact_note_length_limit?: number;
|
contact_note_length_limit?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,7 +241,6 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
|||||||
verifyAgeBotUsername: appConfig.verify_age_bot_username,
|
verifyAgeBotUsername: appConfig.verify_age_bot_username,
|
||||||
verifyAgeCountry: appConfig.verify_age_country,
|
verifyAgeCountry: appConfig.verify_age_country,
|
||||||
verifyAgeMin: appConfig.verify_age_min,
|
verifyAgeMin: appConfig.verify_age_min,
|
||||||
typingDraftTtl: appConfig.message_typing_draft_ttl,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -89,7 +89,6 @@ function buildApiChatFieldsFromPeerEntity(
|
|||||||
const emojiStatus = userOrChannel?.emojiStatus ? buildApiEmojiStatus(userOrChannel.emojiStatus) : undefined;
|
const emojiStatus = userOrChannel?.emojiStatus ? buildApiEmojiStatus(userOrChannel.emojiStatus) : undefined;
|
||||||
const paidMessagesStars = userOrChannel?.sendPaidMessagesStars;
|
const paidMessagesStars = userOrChannel?.sendPaidMessagesStars;
|
||||||
const isVerified = userOrChannel?.verified;
|
const isVerified = userOrChannel?.verified;
|
||||||
const isForum = channel?.forum || user?.botForumView;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isMin,
|
isMin,
|
||||||
@ -114,8 +113,7 @@ function buildApiChatFieldsFromPeerEntity(
|
|||||||
profileColor,
|
profileColor,
|
||||||
isJoinToSend: channel?.joinToSend,
|
isJoinToSend: channel?.joinToSend,
|
||||||
isJoinRequest: channel?.joinRequest,
|
isJoinRequest: channel?.joinRequest,
|
||||||
isForum,
|
isForum: channel?.forum,
|
||||||
isBotForum: user?.botForumView,
|
|
||||||
isMonoforum: channel?.monoforum,
|
isMonoforum: channel?.monoforum,
|
||||||
linkedMonoforumId: channel?.linkedMonoforumId !== undefined
|
linkedMonoforumId: channel?.linkedMonoforumId !== undefined
|
||||||
? buildApiPeerId(channel.linkedMonoforumId, 'channel') : undefined,
|
? buildApiPeerId(channel.linkedMonoforumId, 'channel') : undefined,
|
||||||
|
|||||||
@ -36,7 +36,6 @@ import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../types';
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
DELETED_COMMENTS_CHANNEL_ID,
|
DELETED_COMMENTS_CHANNEL_ID,
|
||||||
LOCAL_MESSAGES_LIMIT,
|
|
||||||
SERVICE_NOTIFICATIONS_USER_ID,
|
SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
SPONSORED_MESSAGE_CACHE_MS,
|
SPONSORED_MESSAGE_CACHE_MS,
|
||||||
SUPPORTED_AUDIO_CONTENT_TYPES,
|
SUPPORTED_AUDIO_CONTENT_TYPES,
|
||||||
@ -77,6 +76,8 @@ import { buildApiRestrictionReasons } from './misc';
|
|||||||
import { buildApiPeerColor, buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
import { buildApiPeerColor, buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
||||||
import { buildMessageReactions } from './reactions';
|
import { buildMessageReactions } from './reactions';
|
||||||
|
|
||||||
|
const LOCAL_MESSAGES_LIMIT = 1e6; // 1M
|
||||||
|
|
||||||
const LOCAL_MEDIA_UPLOADING_TEMP_ID = 'temp';
|
const LOCAL_MEDIA_UPLOADING_TEMP_ID = 'temp';
|
||||||
const INPUT_WAVEFORM_LENGTH = 63;
|
const INPUT_WAVEFORM_LENGTH = 63;
|
||||||
const MIN_SCHEDULED_PERIOD = 10;
|
const MIN_SCHEDULED_PERIOD = 10;
|
||||||
@ -86,10 +87,6 @@ function getNextLocalMessageId(lastMessageId = 0) {
|
|||||||
return lastMessageId + (++localMessageCounter / LOCAL_MESSAGES_LIMIT);
|
return lastMessageId + (++localMessageCounter / LOCAL_MESSAGES_LIMIT);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function incrementLocalMessageCounter() {
|
|
||||||
localMessageCounter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
let currentUserId!: string;
|
let currentUserId!: string;
|
||||||
|
|
||||||
export function setMessageBuilderCurrentUserId(_currentUserId: string) {
|
export function setMessageBuilderCurrentUserId(_currentUserId: string) {
|
||||||
|
|||||||
@ -112,7 +112,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
|||||||
const {
|
const {
|
||||||
id, firstName, lastName, fake, scam, support, closeFriend, storiesUnavailable, storiesMaxId,
|
id, firstName, lastName, fake, scam, support, closeFriend, storiesUnavailable, storiesMaxId,
|
||||||
bot, botActiveUsers, botVerificationIcon, botInlinePlaceholder, botAttachMenu, botCanEdit,
|
bot, botActiveUsers, botVerificationIcon, botInlinePlaceholder, botAttachMenu, botCanEdit,
|
||||||
sendPaidMessagesStars, profileColor, botForumView,
|
sendPaidMessagesStars, profileColor,
|
||||||
} = mtpUser;
|
} = mtpUser;
|
||||||
const hasVideoAvatar = mtpUser.photo instanceof GramJs.UserProfilePhoto ? Boolean(mtpUser.photo.hasVideo) : undefined;
|
const hasVideoAvatar = mtpUser.photo instanceof GramJs.UserProfilePhoto ? Boolean(mtpUser.photo.hasVideo) : undefined;
|
||||||
const avatarPhotoId = mtpUser.photo && buildAvatarPhotoId(mtpUser.photo);
|
const avatarPhotoId = mtpUser.photo && buildAvatarPhotoId(mtpUser.photo);
|
||||||
@ -155,7 +155,6 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
|||||||
color: mtpUser.color && buildApiPeerColor(mtpUser.color),
|
color: mtpUser.color && buildApiPeerColor(mtpUser.color),
|
||||||
profileColor: profileColor && buildApiPeerColor(profileColor),
|
profileColor: profileColor && buildApiPeerColor(profileColor),
|
||||||
paidMessagesStars: toJSNumber(sendPaidMessagesStars),
|
paidMessagesStars: toJSNumber(sendPaidMessagesStars),
|
||||||
isBotForum: botForumView,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,14 +16,13 @@ import { processAffectedHistory } from '../updates/updateManager';
|
|||||||
import { invokeRequest } from './client';
|
import { invokeRequest } from './client';
|
||||||
|
|
||||||
export async function createTopic({
|
export async function createTopic({
|
||||||
chat, title, iconColor, iconEmojiId, sendAs, isTitleMissing,
|
chat, title, iconColor, iconEmojiId, sendAs,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
title: string;
|
title: string;
|
||||||
iconColor?: number;
|
iconColor?: number;
|
||||||
iconEmojiId?: string;
|
iconEmojiId?: string;
|
||||||
sendAs?: ApiPeer;
|
sendAs?: ApiPeer;
|
||||||
isTitleMissing?: true;
|
|
||||||
}) {
|
}) {
|
||||||
const { id, accessHash } = chat;
|
const { id, accessHash } = chat;
|
||||||
|
|
||||||
@ -34,7 +33,6 @@ export async function createTopic({
|
|||||||
iconEmojiId: iconEmojiId ? BigInt(iconEmojiId) : undefined,
|
iconEmojiId: iconEmojiId ? BigInt(iconEmojiId) : undefined,
|
||||||
sendAs: sendAs ? buildInputPeer(sendAs.id, sendAs.accessHash) : undefined,
|
sendAs: sendAs ? buildInputPeer(sendAs.id, sendAs.accessHash) : undefined,
|
||||||
randomId: generateRandomBigInt(),
|
randomId: generateRandomBigInt(),
|
||||||
titleMissing: isTitleMissing,
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (!(updates instanceof GramJs.Updates) || !updates.updates.length) {
|
if (!(updates instanceof GramJs.Updates) || !updates.updates.length) {
|
||||||
@ -77,10 +75,9 @@ export async function fetchTopics({
|
|||||||
|
|
||||||
if (!result) return undefined;
|
if (!result) return undefined;
|
||||||
|
|
||||||
const { orderByCreateDate } = result;
|
const { count, orderByCreateDate } = result;
|
||||||
|
|
||||||
const topics = result.topics.map(buildApiTopic).filter(Boolean);
|
const topics = result.topics.map(buildApiTopic).filter(Boolean);
|
||||||
const count = result.count === 0 ? topics.length : result.count; // Sometimes count is 0 in result, but we have topics
|
|
||||||
const messages = result.messages.map(buildApiMessage).filter(Boolean);
|
const messages = result.messages.map(buildApiMessage).filter(Boolean);
|
||||||
const draftsById = result.topics.reduce((acc, topic) => {
|
const draftsById = result.topics.reduce((acc, topic) => {
|
||||||
if (topic instanceof GramJs.ForumTopic && topic.draft) {
|
if (topic instanceof GramJs.ForumTopic && topic.draft) {
|
||||||
|
|||||||
@ -75,7 +75,6 @@ import {
|
|||||||
buildLocalMessage,
|
buildLocalMessage,
|
||||||
buildPreparedInlineMessage,
|
buildPreparedInlineMessage,
|
||||||
buildUploadingMedia,
|
buildUploadingMedia,
|
||||||
incrementLocalMessageCounter,
|
|
||||||
} from '../apiBuilders/messages';
|
} from '../apiBuilders/messages';
|
||||||
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
||||||
import { buildApiUser, buildApiUserStatuses } from '../apiBuilders/users';
|
import { buildApiUser, buildApiUserStatuses } from '../apiBuilders/users';
|
||||||
@ -1256,21 +1255,23 @@ export async function markMessageListRead({
|
|||||||
}) {
|
}) {
|
||||||
const isChannel = getEntityTypeById(chat.id) === 'channel';
|
const isChannel = getEntityTypeById(chat.id) === 'channel';
|
||||||
|
|
||||||
|
// Workaround for local message IDs overflowing some internal `Buffer` range check
|
||||||
|
const fixedMaxId = Math.min(maxId, MAX_INT_32);
|
||||||
if (isChannel && threadId === MAIN_THREAD_ID) {
|
if (isChannel && threadId === MAIN_THREAD_ID) {
|
||||||
await invokeRequest(new GramJs.channels.ReadHistory({
|
await invokeRequest(new GramJs.channels.ReadHistory({
|
||||||
channel: buildInputChannel(chat.id, chat.accessHash),
|
channel: buildInputChannel(chat.id, chat.accessHash),
|
||||||
maxId,
|
maxId: fixedMaxId,
|
||||||
}));
|
}));
|
||||||
} else if (threadId !== MAIN_THREAD_ID) {
|
} 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: Number(threadId),
|
msgId: Number(threadId),
|
||||||
readMaxId: maxId,
|
readMaxId: fixedMaxId,
|
||||||
}));
|
}));
|
||||||
} else {
|
} else {
|
||||||
const result = 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,
|
maxId: fixedMaxId,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
@ -2554,7 +2555,3 @@ export async function fetchPreparedInlineMessage({
|
|||||||
|
|
||||||
return buildPreparedInlineMessage(result);
|
return buildPreparedInlineMessage(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function incrementLocalMessagesCounter() {
|
|
||||||
incrementLocalMessageCounter();
|
|
||||||
}
|
|
||||||
|
|||||||
@ -28,7 +28,6 @@ import {
|
|||||||
buildChatTypingStatus,
|
buildChatTypingStatus,
|
||||||
} from '../apiBuilders/chats';
|
} from '../apiBuilders/chats';
|
||||||
import {
|
import {
|
||||||
buildApiFormattedText,
|
|
||||||
buildApiPhoto, buildApiUsernames, buildPrivacyRules,
|
buildApiPhoto, buildApiUsernames, buildPrivacyRules,
|
||||||
} from '../apiBuilders/common';
|
} from '../apiBuilders/common';
|
||||||
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
import { omitVirtualClassFields } from '../apiBuilders/helpers';
|
||||||
@ -497,9 +496,10 @@ export function updater(update: Update) {
|
|||||||
sendApiUpdate({
|
sendApiUpdate({
|
||||||
'@type': 'updateChatInbox',
|
'@type': 'updateChatInbox',
|
||||||
id: getApiChatIdFromMtpPeer(update.peer),
|
id: getApiChatIdFromMtpPeer(update.peer),
|
||||||
lastReadInboxMessageId: update.maxId,
|
chat: {
|
||||||
unreadCount: update.stillUnreadCount,
|
lastReadInboxMessageId: update.maxId,
|
||||||
threadId: update.topMsgId,
|
unreadCount: update.stillUnreadCount,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
} else if (update instanceof GramJs.UpdateReadHistoryOutbox) {
|
} else if (update instanceof GramJs.UpdateReadHistoryOutbox) {
|
||||||
sendApiUpdate({
|
sendApiUpdate({
|
||||||
@ -648,33 +648,22 @@ export function updater(update: Update) {
|
|||||||
update instanceof GramJs.UpdateUserTyping
|
update instanceof GramJs.UpdateUserTyping
|
||||||
|| update instanceof GramJs.UpdateChatUserTyping
|
|| update instanceof GramJs.UpdateChatUserTyping
|
||||||
) {
|
) {
|
||||||
const chatId = update instanceof GramJs.UpdateUserTyping
|
const id = update instanceof GramJs.UpdateUserTyping
|
||||||
? buildApiPeerId(update.userId, 'user')
|
? buildApiPeerId(update.userId, 'user')
|
||||||
: buildApiPeerId(update.chatId, 'chat');
|
: buildApiPeerId(update.chatId, 'chat');
|
||||||
|
|
||||||
const threadId = update instanceof GramJs.UpdateUserTyping ? update.topMsgId : undefined;
|
|
||||||
|
|
||||||
if (update.action instanceof GramJs.SendMessageEmojiInteraction) {
|
if (update.action instanceof GramJs.SendMessageEmojiInteraction) {
|
||||||
sendApiUpdate({
|
sendApiUpdate({
|
||||||
'@type': 'updateStartEmojiInteraction',
|
'@type': 'updateStartEmojiInteraction',
|
||||||
id: chatId,
|
id,
|
||||||
emoji: update.action.emoticon,
|
emoji: update.action.emoticon,
|
||||||
messageId: update.action.msgId,
|
messageId: update.action.msgId,
|
||||||
interaction: buildApiEmojiInteraction(JSON.parse(update.action.interaction.data)),
|
interaction: buildApiEmojiInteraction(JSON.parse(update.action.interaction.data)),
|
||||||
});
|
});
|
||||||
} else if (update.action instanceof GramJs.SendMessageTextDraftAction) {
|
|
||||||
sendApiUpdate({
|
|
||||||
'@type': 'updateChatTypingDraft',
|
|
||||||
chatId,
|
|
||||||
id: update.action.randomId.toString(),
|
|
||||||
threadId,
|
|
||||||
text: buildApiFormattedText(update.action.text),
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
sendApiUpdate({
|
sendApiUpdate({
|
||||||
'@type': 'updateChatTypingStatus',
|
'@type': 'updateChatTypingStatus',
|
||||||
id: chatId,
|
id,
|
||||||
threadId,
|
|
||||||
typingStatus: buildChatTypingStatus(update),
|
typingStatus: buildChatTypingStatus(update),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -57,7 +57,6 @@ export interface ApiChat {
|
|||||||
isForum?: boolean;
|
isForum?: boolean;
|
||||||
isForumAsMessages?: true;
|
isForumAsMessages?: true;
|
||||||
isMonoforum?: boolean;
|
isMonoforum?: boolean;
|
||||||
isBotForum?: boolean;
|
|
||||||
withForumTabs?: boolean;
|
withForumTabs?: boolean;
|
||||||
linkedMonoforumId?: string;
|
linkedMonoforumId?: string;
|
||||||
areChannelMessagesAllowed?: boolean;
|
areChannelMessagesAllowed?: boolean;
|
||||||
|
|||||||
@ -489,7 +489,7 @@ export type ApiMessageEntityDefault = {
|
|||||||
type: Exclude<
|
type: Exclude<
|
||||||
`${ApiMessageEntityTypes}`,
|
`${ApiMessageEntityTypes}`,
|
||||||
`${ApiMessageEntityTypes.Pre}` | `${ApiMessageEntityTypes.TextUrl}` | `${ApiMessageEntityTypes.MentionName}` |
|
`${ApiMessageEntityTypes.Pre}` | `${ApiMessageEntityTypes.TextUrl}` | `${ApiMessageEntityTypes.MentionName}` |
|
||||||
`${ApiMessageEntityTypes.Blockquote}` | `${ApiMessageEntityTypes.CustomEmoji}` | `${ApiMessageEntityTypes.Timestamp}`
|
`${ApiMessageEntityTypes.CustomEmoji}` | `${ApiMessageEntityTypes.Blockquote}` | `${ApiMessageEntityTypes.Timestamp}`
|
||||||
>;
|
>;
|
||||||
offset: number;
|
offset: number;
|
||||||
length: number;
|
length: number;
|
||||||
@ -538,8 +538,15 @@ export type ApiMessageEntityTimestamp = {
|
|||||||
timestamp: number;
|
timestamp: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiMessageEntityQuoteFocus = {
|
||||||
|
type: 'quoteFocus';
|
||||||
|
offset: number;
|
||||||
|
length: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type ApiMessageEntity = ApiMessageEntityDefault | ApiMessageEntityPre | ApiMessageEntityTextUrl |
|
export type ApiMessageEntity = ApiMessageEntityDefault | ApiMessageEntityPre | ApiMessageEntityTextUrl |
|
||||||
ApiMessageEntityMentionName | ApiMessageEntityCustomEmoji | ApiMessageEntityBlockquote | ApiMessageEntityTimestamp;
|
ApiMessageEntityMentionName | ApiMessageEntityCustomEmoji | ApiMessageEntityBlockquote | ApiMessageEntityTimestamp |
|
||||||
|
ApiMessageEntityQuoteFocus;
|
||||||
|
|
||||||
export enum ApiMessageEntityTypes {
|
export enum ApiMessageEntityTypes {
|
||||||
Bold = 'MessageEntityBold',
|
Bold = 'MessageEntityBold',
|
||||||
@ -676,8 +683,6 @@ export interface ApiMessage {
|
|||||||
reportDeliveryUntilDate?: number;
|
reportDeliveryUntilDate?: number;
|
||||||
paidMessageStars?: number;
|
paidMessageStars?: number;
|
||||||
restrictionReasons?: ApiRestrictionReason[];
|
restrictionReasons?: ApiRestrictionReason[];
|
||||||
|
|
||||||
isTypingDraft?: boolean; // Local field
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiReactions {
|
export interface ApiReactions {
|
||||||
@ -917,18 +922,13 @@ interface ApiKeyboardButtonCopy {
|
|||||||
copyText: string;
|
copyText: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KeyboardButtonSuggestedMessage {
|
export interface ApiKeyboardButtonSuggestedMessage {
|
||||||
type: 'suggestedMessage';
|
type: 'suggestedMessage';
|
||||||
text: string;
|
text: string;
|
||||||
buttonType: 'approve' | 'decline' | 'suggestChanges';
|
buttonType: 'approve' | 'decline' | 'suggestChanges';
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface KeyboardButtonOpenThread {
|
|
||||||
type: 'openThread';
|
|
||||||
text: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ApiKeyboardButton = (
|
export type ApiKeyboardButton = (
|
||||||
ApiKeyboardButtonSimple
|
ApiKeyboardButtonSimple
|
||||||
| ApiKeyboardButtonReceipt
|
| ApiKeyboardButtonReceipt
|
||||||
@ -941,8 +941,7 @@ export type ApiKeyboardButton = (
|
|||||||
| ApiKeyboardButtonSimpleWebView
|
| ApiKeyboardButtonSimpleWebView
|
||||||
| ApiKeyboardButtonUrlAuth
|
| ApiKeyboardButtonUrlAuth
|
||||||
| ApiKeyboardButtonCopy
|
| ApiKeyboardButtonCopy
|
||||||
| KeyboardButtonSuggestedMessage
|
| ApiKeyboardButtonSuggestedMessage
|
||||||
| KeyboardButtonOpenThread
|
|
||||||
);
|
);
|
||||||
|
|
||||||
export type ApiKeyboardButtons = ApiKeyboardButton[][];
|
export type ApiKeyboardButtons = ApiKeyboardButton[][];
|
||||||
|
|||||||
@ -274,7 +274,6 @@ export interface ApiAppConfig {
|
|||||||
verifyAgeBotUsername?: string;
|
verifyAgeBotUsername?: string;
|
||||||
verifyAgeCountry?: string;
|
verifyAgeCountry?: string;
|
||||||
verifyAgeMin?: number;
|
verifyAgeMin?: number;
|
||||||
typingDraftTtl: number;
|
|
||||||
contactNoteLimit?: number;
|
contactNoteLimit?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -132,9 +132,7 @@ export type ApiUpdateChatLeave = {
|
|||||||
export type ApiUpdateChatInbox = {
|
export type ApiUpdateChatInbox = {
|
||||||
'@type': 'updateChatInbox';
|
'@type': 'updateChatInbox';
|
||||||
id: string;
|
id: string;
|
||||||
threadId?: ThreadId;
|
chat: Partial<ApiChat>;
|
||||||
lastReadInboxMessageId: number;
|
|
||||||
unreadCount: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ApiUpdateChatTypingStatus = {
|
export type ApiUpdateChatTypingStatus = {
|
||||||
@ -144,14 +142,6 @@ export type ApiUpdateChatTypingStatus = {
|
|||||||
typingStatus: ApiTypingStatus | undefined;
|
typingStatus: ApiTypingStatus | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ApiUpdateChatTypingDraft = {
|
|
||||||
'@type': 'updateChatTypingDraft';
|
|
||||||
chatId: string;
|
|
||||||
id: string;
|
|
||||||
threadId?: ThreadId;
|
|
||||||
text: ApiFormattedText;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ApiUpdateStartEmojiInteraction = {
|
export type ApiUpdateStartEmojiInteraction = {
|
||||||
'@type': 'updateStartEmojiInteraction';
|
'@type': 'updateStartEmojiInteraction';
|
||||||
id: string;
|
id: string;
|
||||||
@ -888,7 +878,7 @@ export type ApiUpdate = (
|
|||||||
ApiUpdateRecentStickers | ApiUpdateSavedGifs | ApiUpdateNewScheduledMessage | ApiUpdateMoveStickerSetToTop |
|
ApiUpdateRecentStickers | ApiUpdateSavedGifs | ApiUpdateNewScheduledMessage | ApiUpdateMoveStickerSetToTop |
|
||||||
ApiUpdateScheduledMessageSendSucceeded | ApiUpdateScheduledMessage | ApiUpdateStarPaymentStateCompleted |
|
ApiUpdateScheduledMessageSendSucceeded | ApiUpdateScheduledMessage | ApiUpdateStarPaymentStateCompleted |
|
||||||
ApiUpdateDeleteScheduledMessages | ApiUpdateResetMessages | ApiUpdateMessageTranslations |
|
ApiUpdateDeleteScheduledMessages | ApiUpdateResetMessages | ApiUpdateMessageTranslations |
|
||||||
ApiUpdateFailedMessageTranslations | ApiUpdateWebPage | ApiUpdateChatTypingDraft |
|
ApiUpdateFailedMessageTranslations | ApiUpdateWebPage |
|
||||||
ApiUpdateTwoFaError | ApiUpdateTwoFaStateWaitCode | ApiUpdateWebViewResultSent |
|
ApiUpdateTwoFaError | ApiUpdateTwoFaStateWaitCode | ApiUpdateWebViewResultSent |
|
||||||
ApiUpdateDefaultNotifySettings | ApiUpdatePeerNotifySettings | ApiUpdatePeerBlocked | ApiUpdatePrivacy |
|
ApiUpdateDefaultNotifySettings | ApiUpdatePeerNotifySettings | ApiUpdatePeerBlocked | ApiUpdatePrivacy |
|
||||||
ApiUpdateServerTimeOffset | ApiUpdateMessageReactions | ApiUpdateSavedReactionTags |
|
ApiUpdateServerTimeOffset | ApiUpdateMessageReactions | ApiUpdateSavedReactionTags |
|
||||||
|
|||||||
@ -47,7 +47,6 @@ export interface ApiUser {
|
|||||||
botActiveUsers?: number;
|
botActiveUsers?: number;
|
||||||
botVerificationIconId?: string;
|
botVerificationIconId?: string;
|
||||||
paidMessagesStars?: number;
|
paidMessagesStars?: number;
|
||||||
isBotForum?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiUserFullInfo {
|
export interface ApiUserFullInfo {
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="43" height="43" viewBox="0 0 43 43"><path d="M31.934 20.178q.856.001 1.678.128.032.005.065.012c5.21.835 9.19 5.348 9.19 10.793 0 6.038-4.895 10.933-10.933 10.933-4.066 0-7.61-2.22-9.495-5.514h-.002l-.06-.106a11 11 0 0 1-.502-1.023l-.06-.147a11 11 0 0 1-.189-.495 11 11 0 0 1-.104-.306l-.053-.17a11 11 0 0 1-.11-.389l-.039-.156a11 11 0 0 1-.076-.331l-.034-.16a11 11 0 0 1-.071-.4l-.02-.13q-.031-.21-.055-.425-.008-.082-.015-.166A11 11 0 0 1 21 31.11c0-5.85 4.593-10.626 10.37-10.92q.28-.013.563-.013M21 .133c5.64 0 10.735 2.118 14.377 5.526 3.507 3.281 5.667 7.76 5.667 12.697q-.002 1.418-.232 2.778a13.3 13.3 0 0 0-8.878-3.38c-7.377 0-13.358 5.981-13.358 13.358 0 1.932.41 3.768 1.149 5.426q-.15-.007-.301-.016l-.047-.003a22 22 0 0 1-3.297-.494c-.298-.068-.636.244-1.2.766-.683.63-1.696 1.566-3.37 2.504-2.137 1.199-5.098 1.095-5.6.885-.48-.201-.11-.603.541-1.308.45-.489 1.036-1.123 1.568-1.938 1.3-1.993.763-4.367.18-4.794C3.636 28.8.955 24.096.955 18.356.956 8.292 9.931.133 21.001.133m10.934 24.3c-.67 0-1.212.543-1.212 1.212V29.9h-4.255a1.212 1.212 0 0 0 0 2.424h4.255v4.255a1.213 1.213 0 0 0 2.424 0v-4.255H37.4a1.213 1.213 0 0 0 0-2.424h-4.254v-4.255c0-.669-.544-1.212-1.212-1.212"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.2 KiB |
@ -2301,10 +2301,6 @@
|
|||||||
"TitleGiftLocked" = "Gift Locked";
|
"TitleGiftLocked" = "Gift Locked";
|
||||||
"GiftLockedMessage" = "This gift is currently only available to earlier Telegram users. It will unlock for your account in about **{relativeDate}**.";
|
"GiftLockedMessage" = "This gift is currently only available to earlier Telegram users. It will unlock for your account in about **{relativeDate}**.";
|
||||||
"QuickPreview" = "Quick Preview";
|
"QuickPreview" = "Quick Preview";
|
||||||
"BotForumContinueThreadButton" = "Continue Last Thread";
|
|
||||||
"BotForumActionNew" = "New Thread";
|
|
||||||
"BotForumActionNewDescription" = "Type any message to create a new thread.";
|
|
||||||
"BotForumTopicTitlePlaceholder" = "New Thread";
|
|
||||||
"DropOriginalDetailsTransaction" = "Removed Gift Description";
|
"DropOriginalDetailsTransaction" = "Removed Gift Description";
|
||||||
"StarGiftReasonDropOriginalDetails" = "Removed Description";
|
"StarGiftReasonDropOriginalDetails" = "Removed Description";
|
||||||
"GiftAnUpgradeButton" = "Gift an Upgrade";
|
"GiftAnUpgradeButton" = "Gift an Upgrade";
|
||||||
@ -2314,8 +2310,6 @@
|
|||||||
"UserNoteTitle" = "Notes";
|
"UserNoteTitle" = "Notes";
|
||||||
"UserNoteHint" = "only visible to you";
|
"UserNoteHint" = "only visible to you";
|
||||||
"EditUserNoteHint" = "Notes are only visible to you.";
|
"EditUserNoteHint" = "Notes are only visible to you.";
|
||||||
"BotForumAllTopicTitle" = "All Messages";
|
|
||||||
"BotForumAllTopicDescription" = "All messages from all topics";
|
|
||||||
"AriaStoryTogglerOpen" = "Open Story List";
|
"AriaStoryTogglerOpen" = "Open Story List";
|
||||||
"FileTransferProgress" = "{currentSize} / {totalSize}";
|
"FileTransferProgress" = "{currentSize} / {totalSize}";
|
||||||
"MediaSizeB_one" = "{size}B";
|
"MediaSizeB_one" = "{size}B";
|
||||||
|
|||||||
@ -1705,7 +1705,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
return lang('ComposerPlaceholderAnonymous');
|
return lang('ComposerPlaceholderAnonymous');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chat?.isForum && !chat.isBotForum && chat.isForumAsMessages && threadId === MAIN_THREAD_ID) {
|
if (chat?.isForum && chat?.isForumAsMessages && threadId === MAIN_THREAD_ID) {
|
||||||
return replyToTopic
|
return replyToTopic
|
||||||
? lang('ComposerPlaceholderTopic', { topic: replyToTopic.title })
|
? lang('ComposerPlaceholderTopic', { topic: replyToTopic.title })
|
||||||
: lang('ComposerPlaceholderTopicGeneral');
|
: lang('ComposerPlaceholderTopicGeneral');
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
|
import type { FC } from '../../lib/teact/teact';
|
||||||
|
import type React from '../../lib/teact/teact';
|
||||||
import { memo, useEffect, useMemo } from '../../lib/teact/teact';
|
import { memo, useEffect, useMemo } from '../../lib/teact/teact';
|
||||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiChat, ApiTopic, ApiTypingStatus, ApiUser,
|
ApiChat, ApiThreadInfo, ApiTopic, ApiTypingStatus, ApiUser,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { IconName } from '../../types/icons';
|
import type { IconName } from '../../types/icons';
|
||||||
import { MediaViewerOrigin, type StoryViewerOrigin, type ThreadId } from '../../types';
|
import { MediaViewerOrigin, type StoryViewerOrigin, type ThreadId } from '../../types';
|
||||||
@ -19,6 +21,7 @@ import {
|
|||||||
selectChatOnlineCount,
|
selectChatOnlineCount,
|
||||||
selectIsChatRestricted,
|
selectIsChatRestricted,
|
||||||
selectMonoforumChannel,
|
selectMonoforumChannel,
|
||||||
|
selectThreadInfo,
|
||||||
selectThreadMessagesCount,
|
selectThreadMessagesCount,
|
||||||
selectTopic,
|
selectTopic,
|
||||||
selectUser,
|
selectUser,
|
||||||
@ -65,11 +68,12 @@ type OwnProps = {
|
|||||||
isSavedDialog?: boolean;
|
isSavedDialog?: boolean;
|
||||||
withMonoforumStatus?: boolean;
|
withMonoforumStatus?: boolean;
|
||||||
onClick?: VoidFunction;
|
onClick?: VoidFunction;
|
||||||
onEmojiStatusClick?: VoidFunction;
|
onEmojiStatusClick?: NoneToVoidFunction;
|
||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
|
threadInfo?: ApiThreadInfo;
|
||||||
topic?: ApiTopic;
|
topic?: ApiTopic;
|
||||||
onlineCount?: number;
|
onlineCount?: number;
|
||||||
areMessagesLoaded: boolean;
|
areMessagesLoaded: boolean;
|
||||||
@ -78,7 +82,7 @@ type StateProps = {
|
|||||||
monoforumChannel?: ApiChat;
|
monoforumChannel?: ApiChat;
|
||||||
};
|
};
|
||||||
|
|
||||||
const GroupChatInfo = ({
|
const GroupChatInfo: FC<OwnProps & StateProps> = ({
|
||||||
typingStatus,
|
typingStatus,
|
||||||
className,
|
className,
|
||||||
statusIcon,
|
statusIcon,
|
||||||
@ -91,6 +95,7 @@ const GroupChatInfo = ({
|
|||||||
withFullInfo,
|
withFullInfo,
|
||||||
withUpdatingStatus,
|
withUpdatingStatus,
|
||||||
withChatType,
|
withChatType,
|
||||||
|
threadInfo,
|
||||||
noRtl,
|
noRtl,
|
||||||
chat: realChat,
|
chat: realChat,
|
||||||
onlineCount,
|
onlineCount,
|
||||||
@ -108,7 +113,7 @@ const GroupChatInfo = ({
|
|||||||
monoforumChannel,
|
monoforumChannel,
|
||||||
onClick,
|
onClick,
|
||||||
onEmojiStatusClick,
|
onEmojiStatusClick,
|
||||||
}: OwnProps & StateProps) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
loadFullChat,
|
loadFullChat,
|
||||||
openMediaViewer,
|
openMediaViewer,
|
||||||
@ -121,7 +126,7 @@ const GroupChatInfo = ({
|
|||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const isSuperGroup = chat && isChatSuperGroup(chat);
|
const isSuperGroup = chat && isChatSuperGroup(chat);
|
||||||
const isTopic = Boolean(chat?.isForum && topic);
|
const isTopic = Boolean(chat?.isForum && threadInfo && topic);
|
||||||
const { id: chatId, isMin } = chat || {};
|
const { id: chatId, isMin } = chat || {};
|
||||||
const isRestricted = selectIsChatRestricted(getGlobal(), chatId!);
|
const isRestricted = selectIsChatRestricted(getGlobal(), chatId!);
|
||||||
|
|
||||||
@ -199,7 +204,7 @@ const GroupChatInfo = ({
|
|||||||
activeKey={messagesCount !== undefined ? 1 : 2}
|
activeKey={messagesCount !== undefined ? 1 : 2}
|
||||||
className="message-count-transition"
|
className="message-count-transition"
|
||||||
>
|
>
|
||||||
{messagesCount !== undefined ? oldLang('messages', messagesCount, 'i') : oldLang('lng_forum_no_messages')}
|
{messagesCount !== undefined && oldLang('messages', messagesCount, 'i')}
|
||||||
</Transition>
|
</Transition>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@ -285,6 +290,7 @@ const GroupChatInfo = ({
|
|||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { chatId, threadId }): Complete<StateProps> => {
|
(global, { chatId, threadId }): Complete<StateProps> => {
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
|
const threadInfo = threadId ? selectThreadInfo(global, chatId, threadId) : undefined;
|
||||||
const onlineCount = chat ? selectChatOnlineCount(global, chat) : undefined;
|
const onlineCount = chat ? selectChatOnlineCount(global, chat) : undefined;
|
||||||
const areMessagesLoaded = Boolean(selectChatMessages(global, chatId));
|
const areMessagesLoaded = Boolean(selectChatMessages(global, chatId));
|
||||||
const topic = threadId ? selectTopic(global, chatId, threadId) : undefined;
|
const topic = threadId ? selectTopic(global, chatId, threadId) : undefined;
|
||||||
@ -294,6 +300,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
chat,
|
chat,
|
||||||
|
threadInfo,
|
||||||
onlineCount,
|
onlineCount,
|
||||||
topic,
|
topic,
|
||||||
areMessagesLoaded,
|
areMessagesLoaded,
|
||||||
|
|||||||
@ -12,12 +12,9 @@ import trimText from '../../util/trimText';
|
|||||||
import { insertTextEntity, renderTextWithEntities } from './helpers/renderTextWithEntities';
|
import { insertTextEntity, renderTextWithEntities } from './helpers/renderTextWithEntities';
|
||||||
|
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
|
||||||
import useSyncEffect from '../../hooks/useSyncEffect';
|
import useSyncEffect from '../../hooks/useSyncEffect';
|
||||||
import useUniqueId from '../../hooks/useUniqueId';
|
import useUniqueId from '../../hooks/useUniqueId';
|
||||||
|
|
||||||
import TypingWrapper from './TypingWrapper';
|
|
||||||
|
|
||||||
interface OwnProps {
|
interface OwnProps {
|
||||||
messageOrStory: ApiMessage | ApiStory;
|
messageOrStory: ApiMessage | ApiStory;
|
||||||
threadId?: ThreadId;
|
threadId?: ThreadId;
|
||||||
@ -39,7 +36,6 @@ interface OwnProps {
|
|||||||
isInSelectMode?: boolean;
|
isInSelectMode?: boolean;
|
||||||
canBeEmpty?: boolean;
|
canBeEmpty?: boolean;
|
||||||
maxTimestamp?: number;
|
maxTimestamp?: number;
|
||||||
shouldAnimateTyping?: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const MIN_CUSTOM_EMOJIS_FOR_SHARED_CANVAS = 3;
|
const MIN_CUSTOM_EMOJIS_FOR_SHARED_CANVAS = 3;
|
||||||
@ -65,7 +61,6 @@ function MessageText({
|
|||||||
canBeEmpty,
|
canBeEmpty,
|
||||||
maxTimestamp,
|
maxTimestamp,
|
||||||
threadId,
|
threadId,
|
||||||
shouldAnimateTyping,
|
|
||||||
}: OwnProps) {
|
}: OwnProps) {
|
||||||
const sharedCanvasRef = useRef<HTMLCanvasElement>();
|
const sharedCanvasRef = useRef<HTMLCanvasElement>();
|
||||||
const sharedCanvasHqRef = useRef<HTMLCanvasElement>();
|
const sharedCanvasHqRef = useRef<HTMLCanvasElement>();
|
||||||
@ -112,48 +107,37 @@ function MessageText({
|
|||||||
return customEmojisCount >= MIN_CUSTOM_EMOJIS_FOR_SHARED_CANVAS;
|
return customEmojisCount >= MIN_CUSTOM_EMOJIS_FOR_SHARED_CANVAS;
|
||||||
}, [entitiesWithFocusedQuote]) || 0;
|
}, [entitiesWithFocusedQuote]) || 0;
|
||||||
|
|
||||||
const renderText = useLastCallback((t: ApiFormattedText) => {
|
|
||||||
return renderTextWithEntities({
|
|
||||||
text: t.text,
|
|
||||||
entities: t.entities,
|
|
||||||
highlight,
|
|
||||||
emojiSize,
|
|
||||||
shouldRenderAsHtml,
|
|
||||||
containerId,
|
|
||||||
asPreview,
|
|
||||||
isProtected,
|
|
||||||
observeIntersectionForLoading,
|
|
||||||
observeIntersectionForPlaying,
|
|
||||||
withTranslucentThumbs,
|
|
||||||
sharedCanvasRef,
|
|
||||||
sharedCanvasHqRef,
|
|
||||||
cacheBuster: textCacheBusterRef.current.toString(),
|
|
||||||
forcePlayback,
|
|
||||||
isInSelectMode,
|
|
||||||
maxTimestamp,
|
|
||||||
chatId: 'chatId' in messageOrStory ? messageOrStory.chatId : undefined,
|
|
||||||
messageId: messageOrStory.id,
|
|
||||||
threadId,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!text && !canBeEmpty) {
|
if (!text && !canBeEmpty) {
|
||||||
return <span className="content-unsupported">{lang('MessageUnsupported')}</span>;
|
return <span className="content-unsupported">{lang('MessageUnsupported')}</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const textToRender: ApiFormattedText = {
|
|
||||||
text: trimText(text || '', truncateLength),
|
|
||||||
entities: entitiesWithFocusedQuote,
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{[
|
{[
|
||||||
withSharedCanvas && <canvas key="shared-canvas" ref={sharedCanvasRef} className="shared-canvas" />,
|
withSharedCanvas && <canvas ref={sharedCanvasRef} className="shared-canvas" />,
|
||||||
withSharedCanvas && <canvas key="shared-canvas-hq" ref={sharedCanvasHqRef} className="shared-canvas" />,
|
withSharedCanvas && <canvas ref={sharedCanvasHqRef} className="shared-canvas" />,
|
||||||
shouldAnimateTyping ? (
|
renderTextWithEntities({
|
||||||
<TypingWrapper key="typing-wrapper" text={textToRender}>{renderText}</TypingWrapper>
|
text: trimText(text!, truncateLength),
|
||||||
) : renderText(textToRender),
|
entities: entitiesWithFocusedQuote,
|
||||||
|
highlight,
|
||||||
|
emojiSize,
|
||||||
|
shouldRenderAsHtml,
|
||||||
|
containerId,
|
||||||
|
asPreview,
|
||||||
|
isProtected,
|
||||||
|
observeIntersectionForLoading,
|
||||||
|
observeIntersectionForPlaying,
|
||||||
|
withTranslucentThumbs,
|
||||||
|
sharedCanvasRef,
|
||||||
|
sharedCanvasHqRef,
|
||||||
|
cacheBuster: textCacheBusterRef.current.toString(),
|
||||||
|
forcePlayback,
|
||||||
|
isInSelectMode,
|
||||||
|
maxTimestamp,
|
||||||
|
chatId: 'chatId' in messageOrStory ? messageOrStory.chatId : undefined,
|
||||||
|
messageId: messageOrStory.id,
|
||||||
|
threadId,
|
||||||
|
}),
|
||||||
].flat().filter(Boolean)}
|
].flat().filter(Boolean)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,25 +1,19 @@
|
|||||||
|
import type { FC } from '../../lib/teact/teact';
|
||||||
import { memo, useEffect, useMemo } from '../../lib/teact/teact';
|
import { memo, useEffect, useMemo } from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiChatMember, ApiTopic, ApiTypingStatus, ApiUser, ApiUserStatus,
|
ApiChatMember, ApiTypingStatus, ApiUser, ApiUserStatus,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type { CustomPeer, StoryViewerOrigin, ThreadId } from '../../types';
|
import type { CustomPeer, StoryViewerOrigin } from '../../types';
|
||||||
import type { IconName } from '../../types/icons';
|
import type { IconName } from '../../types/icons';
|
||||||
import { MediaViewerOrigin } from '../../types';
|
import { MediaViewerOrigin } from '../../types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getMainUsername, getUserStatus, isSystemBot, isUserOnline,
|
getMainUsername, getUserStatus, isSystemBot, isUserOnline,
|
||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import {
|
import { selectChatMessages, selectUser, selectUserStatus } from '../../global/selectors';
|
||||||
selectChatMessages,
|
|
||||||
selectThreadMessagesCount,
|
|
||||||
selectTopic,
|
|
||||||
selectUser,
|
|
||||||
selectUserStatus,
|
|
||||||
} from '../../global/selectors';
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { REM } from './helpers/mediaDimensions';
|
|
||||||
import renderText from './helpers/renderText';
|
import renderText from './helpers/renderText';
|
||||||
|
|
||||||
import useIntervalForceUpdate from '../../hooks/schedulers/useIntervalForceUpdate';
|
import useIntervalForceUpdate from '../../hooks/schedulers/useIntervalForceUpdate';
|
||||||
@ -28,17 +22,15 @@ import useLastCallback from '../../hooks/useLastCallback';
|
|||||||
import useOldLang from '../../hooks/useOldLang';
|
import useOldLang from '../../hooks/useOldLang';
|
||||||
|
|
||||||
import RippleEffect from '../ui/RippleEffect';
|
import RippleEffect from '../ui/RippleEffect';
|
||||||
import Transition from '../ui/Transition';
|
|
||||||
import Avatar from './Avatar';
|
import Avatar from './Avatar';
|
||||||
import DotAnimation from './DotAnimation';
|
import DotAnimation from './DotAnimation';
|
||||||
import FullNameTitle from './FullNameTitle';
|
import FullNameTitle from './FullNameTitle';
|
||||||
import Icon from './icons/Icon';
|
import Icon from './icons/Icon';
|
||||||
import TopicIcon from './TopicIcon';
|
|
||||||
import TypingStatus from './TypingStatus';
|
import TypingStatus from './TypingStatus';
|
||||||
|
|
||||||
const TOPIC_ICON_SIZE = 2.5 * REM;
|
type OwnProps = {
|
||||||
|
userId?: string;
|
||||||
type BaseOwnProps = {
|
customPeer?: CustomPeer;
|
||||||
typingStatus?: ApiTypingStatus;
|
typingStatus?: ApiTypingStatus;
|
||||||
avatarSize?: 'tiny' | 'small' | 'medium' | 'large' | 'jumbo';
|
avatarSize?: 'tiny' | 'small' | 'medium' | 'large' | 'jumbo';
|
||||||
forceShowSelf?: boolean;
|
forceShowSelf?: boolean;
|
||||||
@ -60,39 +52,25 @@ type BaseOwnProps = {
|
|||||||
noRtl?: boolean;
|
noRtl?: boolean;
|
||||||
adminMember?: ApiChatMember;
|
adminMember?: ApiChatMember;
|
||||||
isSavedDialog?: boolean;
|
isSavedDialog?: boolean;
|
||||||
noAvatar?: boolean;
|
|
||||||
className?: string;
|
className?: string;
|
||||||
|
onEmojiStatusClick?: NoneToVoidFunction;
|
||||||
iconElement?: React.ReactNode;
|
iconElement?: React.ReactNode;
|
||||||
rightElement?: React.ReactNode;
|
rightElement?: React.ReactNode;
|
||||||
onClick?: VoidFunction;
|
|
||||||
onEmojiStatusClick?: VoidFunction;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
type OwnProps = BaseOwnProps & ({
|
type StateProps =
|
||||||
userId: string;
|
{
|
||||||
threadId?: ThreadId;
|
user?: ApiUser;
|
||||||
customPeer?: never;
|
userStatus?: ApiUserStatus;
|
||||||
} | {
|
self?: ApiUser;
|
||||||
userId?: never;
|
isSavedMessages?: boolean;
|
||||||
threadId?: never;
|
areMessagesLoaded: boolean;
|
||||||
customPeer: CustomPeer;
|
isSynced?: boolean;
|
||||||
});
|
};
|
||||||
|
|
||||||
type StateProps = {
|
|
||||||
user?: ApiUser;
|
|
||||||
userStatus?: ApiUserStatus;
|
|
||||||
self?: ApiUser;
|
|
||||||
isSavedMessages?: boolean;
|
|
||||||
areMessagesLoaded: boolean;
|
|
||||||
isSynced?: boolean;
|
|
||||||
topic?: ApiTopic;
|
|
||||||
messagesCount?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const UPDATE_INTERVAL = 1000 * 60; // 1 min
|
const UPDATE_INTERVAL = 1000 * 60; // 1 min
|
||||||
|
|
||||||
const PrivateChatInfo = ({
|
const PrivateChatInfo: FC<OwnProps & StateProps> = ({
|
||||||
userId,
|
|
||||||
customPeer,
|
customPeer,
|
||||||
typingStatus,
|
typingStatus,
|
||||||
avatarSize = 'medium',
|
avatarSize = 'medium',
|
||||||
@ -113,8 +91,6 @@ const PrivateChatInfo = ({
|
|||||||
user,
|
user,
|
||||||
userStatus,
|
userStatus,
|
||||||
self,
|
self,
|
||||||
topic,
|
|
||||||
messagesCount,
|
|
||||||
isSavedMessages,
|
isSavedMessages,
|
||||||
isSavedDialog,
|
isSavedDialog,
|
||||||
areMessagesLoaded,
|
areMessagesLoaded,
|
||||||
@ -122,13 +98,11 @@ const PrivateChatInfo = ({
|
|||||||
ripple,
|
ripple,
|
||||||
className,
|
className,
|
||||||
storyViewerOrigin,
|
storyViewerOrigin,
|
||||||
noAvatar,
|
|
||||||
isSynced,
|
isSynced,
|
||||||
|
onEmojiStatusClick,
|
||||||
iconElement,
|
iconElement,
|
||||||
rightElement,
|
rightElement,
|
||||||
onClick,
|
}) => {
|
||||||
onEmojiStatusClick,
|
|
||||||
}: OwnProps & StateProps) => {
|
|
||||||
const {
|
const {
|
||||||
loadFullUser,
|
loadFullUser,
|
||||||
openMediaViewer,
|
openMediaViewer,
|
||||||
@ -138,7 +112,8 @@ const PrivateChatInfo = ({
|
|||||||
const oldLang = useOldLang();
|
const oldLang = useOldLang();
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
const isTopic = Boolean(user?.isBotForum && topic);
|
const { id: userId } = user || {};
|
||||||
|
|
||||||
const hasAvatarMediaViewer = withMediaViewer && !isSavedMessages;
|
const hasAvatarMediaViewer = withMediaViewer && !isSavedMessages;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -152,11 +127,11 @@ const PrivateChatInfo = ({
|
|||||||
|
|
||||||
const handleAvatarViewerOpen = useLastCallback(
|
const handleAvatarViewerOpen = useLastCallback(
|
||||||
(e: React.MouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => {
|
(e: React.MouseEvent<HTMLDivElement, MouseEvent>, hasMedia: boolean) => {
|
||||||
if (hasMedia) {
|
if (user && hasMedia) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
openMediaViewer({
|
openMediaViewer({
|
||||||
isAvatarView: true,
|
isAvatarView: true,
|
||||||
chatId: userId,
|
chatId: user.id,
|
||||||
mediaIndex: 0,
|
mediaIndex: 0,
|
||||||
origin: avatarSize === 'jumbo' ? MediaViewerOrigin.ProfileAvatar : MediaViewerOrigin.MiddleHeaderAvatar,
|
origin: avatarSize === 'jumbo' ? MediaViewerOrigin.ProfileAvatar : MediaViewerOrigin.MiddleHeaderAvatar,
|
||||||
});
|
});
|
||||||
@ -204,21 +179,6 @@ const PrivateChatInfo = ({
|
|||||||
return <TypingStatus typingStatus={typingStatus} />;
|
return <TypingStatus typingStatus={typingStatus} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isTopic) {
|
|
||||||
return (
|
|
||||||
<span className="status" dir="auto">
|
|
||||||
<Transition
|
|
||||||
name="fade"
|
|
||||||
shouldRestoreHeight
|
|
||||||
activeKey={messagesCount !== undefined ? 1 : 2}
|
|
||||||
className="message-count-transition"
|
|
||||||
>
|
|
||||||
{messagesCount !== undefined ? oldLang('messages', messagesCount, 'i') : oldLang('lng_forum_no_messages')}
|
|
||||||
</Transition>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isSystemBot(user.id)) {
|
if (isSystemBot(user.id)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@ -238,12 +198,6 @@ const PrivateChatInfo = ({
|
|||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
function renderNameTitle() {
|
function renderNameTitle() {
|
||||||
if (isTopic) {
|
|
||||||
return (
|
|
||||||
<h3 dir="auto" className="fullName">{renderText(topic!.title)}</h3>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (customTitle) {
|
if (customTitle) {
|
||||||
return (
|
return (
|
||||||
<div className="info-name-title">
|
<div className="info-name-title">
|
||||||
@ -276,11 +230,7 @@ const PrivateChatInfo = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div className={buildClassName('ChatInfo', className)} dir={!noRtl && lang.isRtl ? 'rtl' : undefined}>
|
||||||
className={buildClassName('ChatInfo', className)}
|
|
||||||
dir={!noRtl && lang.isRtl ? 'rtl' : undefined}
|
|
||||||
onClick={onClick}
|
|
||||||
>
|
|
||||||
{isSavedDialog && self && (
|
{isSavedDialog && self && (
|
||||||
<Avatar
|
<Avatar
|
||||||
key="saved-messages"
|
key="saved-messages"
|
||||||
@ -290,27 +240,18 @@ const PrivateChatInfo = ({
|
|||||||
className="saved-dialog-avatar"
|
className="saved-dialog-avatar"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!noAvatar && !isTopic && (
|
<Avatar
|
||||||
<Avatar
|
key={user?.id}
|
||||||
key={user?.id}
|
size={avatarSize}
|
||||||
size={avatarSize}
|
peer={customPeer || user}
|
||||||
peer={customPeer || user}
|
className={buildClassName(isSavedDialog && 'overlay-avatar')}
|
||||||
className={buildClassName(isSavedDialog && 'overlay-avatar')}
|
isSavedMessages={isSavedMessages}
|
||||||
isSavedMessages={isSavedMessages}
|
isSavedDialog={isSavedDialog}
|
||||||
isSavedDialog={isSavedDialog}
|
withStory={withStory}
|
||||||
withStory={withStory}
|
storyViewerOrigin={storyViewerOrigin}
|
||||||
storyViewerOrigin={storyViewerOrigin}
|
storyViewerMode="single-peer"
|
||||||
storyViewerMode="single-peer"
|
onClick={hasAvatarMediaViewer ? handleAvatarViewerOpen : undefined}
|
||||||
onClick={hasAvatarMediaViewer ? handleAvatarViewerOpen : undefined}
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{isTopic && (
|
|
||||||
<TopicIcon
|
|
||||||
topic={topic!}
|
|
||||||
className="topic-header-icon"
|
|
||||||
size={TOPIC_ICON_SIZE}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<div className="info">
|
<div className="info">
|
||||||
{renderNameTitle()}
|
{renderNameTitle()}
|
||||||
{(status || (!isSavedMessages && !noStatusOrTyping)) && renderStatusOrTyping()}
|
{(status || (!isSavedMessages && !noStatusOrTyping)) && renderStatusOrTyping()}
|
||||||
@ -322,16 +263,13 @@ const PrivateChatInfo = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { userId, threadId, forceShowSelf }): Complete<StateProps> => {
|
(global, { userId, forceShowSelf }): Complete<StateProps> => {
|
||||||
const { isSynced } = global;
|
const { isSynced } = global;
|
||||||
const user = userId ? selectUser(global, userId) : undefined;
|
const user = userId ? selectUser(global, userId) : undefined;
|
||||||
const userStatus = userId ? selectUserStatus(global, userId) : undefined;
|
const userStatus = userId ? selectUserStatus(global, userId) : undefined;
|
||||||
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
const isSavedMessages = !forceShowSelf && user && user.isSelf;
|
||||||
const self = isSavedMessages ? user : selectUser(global, global.currentUserId!);
|
const self = isSavedMessages ? user : selectUser(global, global.currentUserId!);
|
||||||
const areMessagesLoaded = Boolean(userId ? selectChatMessages(global, userId) : undefined);
|
const areMessagesLoaded = Boolean(userId && selectChatMessages(global, userId));
|
||||||
|
|
||||||
const topic = threadId ? selectTopic(global, userId, threadId) : undefined;
|
|
||||||
const messagesCount = topic && userId ? selectThreadMessagesCount(global, userId, threadId!) : undefined;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
user,
|
user,
|
||||||
@ -340,8 +278,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
areMessagesLoaded,
|
areMessagesLoaded,
|
||||||
self,
|
self,
|
||||||
isSynced,
|
isSynced,
|
||||||
topic,
|
|
||||||
messagesCount,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
)(PrivateChatInfo));
|
)(PrivateChatInfo));
|
||||||
|
|||||||
@ -1,71 +0,0 @@
|
|||||||
import {
|
|
||||||
memo, useEffect, useRef, useSignal, useUnmountCleanup,
|
|
||||||
} from '../../lib/teact/teact';
|
|
||||||
|
|
||||||
import {
|
|
||||||
type ApiFormattedText,
|
|
||||||
} from '../../api/types';
|
|
||||||
|
|
||||||
import useDerivedState from '../../hooks/useDerivedState';
|
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
|
||||||
|
|
||||||
type OwnProps = {
|
|
||||||
text: ApiFormattedText;
|
|
||||||
duration?: number;
|
|
||||||
children: (text: ApiFormattedText) => React.ReactNode;
|
|
||||||
};
|
|
||||||
|
|
||||||
const DEFAULT_HEADWAY_DURATION = 1000;
|
|
||||||
const MIN_TIMEOUT_DURATION = 1000 / 60; // 60 FPS
|
|
||||||
const MAX_SYMBOLS_BATCH = 10;
|
|
||||||
|
|
||||||
const TypingWrapper = ({
|
|
||||||
text,
|
|
||||||
duration = DEFAULT_HEADWAY_DURATION,
|
|
||||||
children,
|
|
||||||
}: OwnProps) => {
|
|
||||||
const [getCurrentTextLength, setCurrentTextLength] = useSignal(text.text.length);
|
|
||||||
const intervalRef = useRef<number>();
|
|
||||||
|
|
||||||
const animate = useLastCallback(() => {
|
|
||||||
const msPerSymbol = duration / text.text.length;
|
|
||||||
const timeoutDuration = Math.max(msPerSymbol, MIN_TIMEOUT_DURATION);
|
|
||||||
const nextSymbolBatchLength = Math.min(Math.ceil(timeoutDuration / msPerSymbol), MAX_SYMBOLS_BATCH);
|
|
||||||
|
|
||||||
intervalRef.current = window.setTimeout(() => {
|
|
||||||
if (getCurrentTextLength() >= text.text.length) {
|
|
||||||
clearTimeout(intervalRef.current);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setCurrentTextLength(getCurrentTextLength() + nextSymbolBatchLength);
|
|
||||||
}, timeoutDuration);
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Text got shorter, skip animation
|
|
||||||
if (text.text.length < getCurrentTextLength()) {
|
|
||||||
clearTimeout(intervalRef.current);
|
|
||||||
setCurrentTextLength(text.text.length);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clearTimeout(intervalRef.current);
|
|
||||||
animate();
|
|
||||||
}, [getCurrentTextLength, setCurrentTextLength, text.text.length]);
|
|
||||||
|
|
||||||
useUnmountCleanup(() => {
|
|
||||||
clearTimeout(intervalRef.current);
|
|
||||||
});
|
|
||||||
|
|
||||||
const displayedText = useDerivedState(() => {
|
|
||||||
return {
|
|
||||||
...text,
|
|
||||||
text: text.text.slice(0, getCurrentTextLength()),
|
|
||||||
};
|
|
||||||
}, [getCurrentTextLength, text]);
|
|
||||||
|
|
||||||
return children(displayedText);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(TypingWrapper);
|
|
||||||
@ -1,4 +1,5 @@
|
|||||||
import type { ElementRef } from '../../../lib/teact/teact';
|
import type { ElementRef } from '../../../lib/teact/teact';
|
||||||
|
import type React from '../../../lib/teact/teact';
|
||||||
import { getActions } from '../../../global';
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
import type { ApiFormattedText, ApiMessageEntity } from '../../../api/types';
|
import type { ApiFormattedText, ApiMessageEntity } from '../../../api/types';
|
||||||
@ -9,6 +10,7 @@ import { ApiMessageEntityTypes } from '../../../api/types';
|
|||||||
|
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { copyTextToClipboard } from '../../../util/clipboard';
|
import { copyTextToClipboard } from '../../../util/clipboard';
|
||||||
|
import { oldTranslate } from '../../../util/oldLangProvider';
|
||||||
import { buildCustomEmojiHtmlFromEntity } from '../../middle/composer/helpers/customEmoji';
|
import { buildCustomEmojiHtmlFromEntity } from '../../middle/composer/helpers/customEmoji';
|
||||||
import renderText from './renderText';
|
import renderText from './renderText';
|
||||||
|
|
||||||
@ -299,12 +301,6 @@ function renderMessagePart({
|
|||||||
return renderText(content, filters, params);
|
return renderText(content, filters, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function insertTextEntities(entities: ApiMessageEntity[], newEntities: ApiMessageEntity[]) {
|
|
||||||
return newEntities.reduce((acc, newEntity) => {
|
|
||||||
return insertTextEntity(acc, newEntity);
|
|
||||||
}, entities);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function insertTextEntity(entities: ApiMessageEntity[], newEntity: ApiMessageEntity) {
|
export function insertTextEntity(entities: ApiMessageEntity[], newEntity: ApiMessageEntity) {
|
||||||
const resultEntities: ApiMessageEntity[] = [];
|
const resultEntities: ApiMessageEntity[] = [];
|
||||||
|
|
||||||
@ -765,9 +761,7 @@ function handleHashtagClick(hashtag?: string, username?: string) {
|
|||||||
function handleCodeClick(e: React.MouseEvent<HTMLElement>) {
|
function handleCodeClick(e: React.MouseEvent<HTMLElement>) {
|
||||||
copyTextToClipboard(e.currentTarget.innerText);
|
copyTextToClipboard(e.currentTarget.innerText);
|
||||||
getActions().showNotification({
|
getActions().showNotification({
|
||||||
message: {
|
message: oldTranslate('TextCopied'),
|
||||||
key: 'TextCopied',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -23,7 +23,7 @@ import Button from '../ui/Button';
|
|||||||
import DropdownMenu from '../ui/DropdownMenu';
|
import DropdownMenu from '../ui/DropdownMenu';
|
||||||
import MenuItem from '../ui/MenuItem';
|
import MenuItem from '../ui/MenuItem';
|
||||||
import ChatList from './main/ChatList';
|
import ChatList from './main/ChatList';
|
||||||
import ForumPanel from './main/forum/ForumPanel';
|
import ForumPanel from './main/ForumPanel';
|
||||||
|
|
||||||
import './ArchivedChats.scss';
|
import './ArchivedChats.scss';
|
||||||
|
|
||||||
|
|||||||
@ -103,7 +103,7 @@
|
|||||||
font-size: 0.875rem !important;
|
font-size: 0.875rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.selected:not(.onAvatar) {
|
.selected {
|
||||||
.badge:not(.pinned) {
|
.badge:not(.pinned) {
|
||||||
color: var(--color-chat-active);
|
color: var(--color-chat-active);
|
||||||
background: var(--color-white);
|
background: var(--color-white);
|
||||||
|
|||||||
@ -1,20 +1,19 @@
|
|||||||
import { memo } from '../../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../../global';
|
import { memo, useCallback } from '../../../lib/teact/teact';
|
||||||
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiSticker } from '../../../../api/types';
|
import type { ApiSticker } from '../../../api/types';
|
||||||
|
|
||||||
import { getHasAdminRight } from '../../../../global/helpers';
|
import { getHasAdminRight } from '../../../global/helpers';
|
||||||
import { selectAnimatedEmoji, selectChat } from '../../../../global/selectors';
|
import { selectAnimatedEmoji, selectChat } from '../../../global/selectors';
|
||||||
import buildClassName from '../../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { REM } from '../../../common/helpers/mediaDimensions';
|
import { REM } from '../../common/helpers/mediaDimensions';
|
||||||
|
|
||||||
import useAppLayout from '../../../../hooks/useAppLayout';
|
import useAppLayout from '../../../hooks/useAppLayout';
|
||||||
import useLang from '../../../../hooks/useLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
|
||||||
import useOldLang from '../../../../hooks/useOldLang';
|
|
||||||
|
|
||||||
import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker';
|
import AnimatedIconFromSticker from '../../common/AnimatedIconFromSticker';
|
||||||
import Button from '../../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
|
|
||||||
import styles from './EmptyForum.module.scss';
|
import styles from './EmptyForum.module.scss';
|
||||||
|
|
||||||
@ -29,27 +28,26 @@ type StateProps = {
|
|||||||
|
|
||||||
const ICON_SIZE = 7 * REM;
|
const ICON_SIZE = 7 * REM;
|
||||||
|
|
||||||
const EmptyForum = ({
|
const EmptyForum: FC<OwnProps & StateProps> = ({
|
||||||
chatId, animatedEmoji, canManageTopics,
|
chatId, animatedEmoji, canManageTopics,
|
||||||
}: OwnProps & StateProps) => {
|
}) => {
|
||||||
const { openCreateTopicPanel } = getActions();
|
const { openCreateTopicPanel } = getActions();
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useOldLang();
|
||||||
const oldLang = useOldLang();
|
|
||||||
const { isMobile } = useAppLayout();
|
const { isMobile } = useAppLayout();
|
||||||
|
|
||||||
const handleCreateTopic = useLastCallback(() => {
|
const handleCreateTopic = useCallback(() => {
|
||||||
openCreateTopicPanel({ chatId });
|
openCreateTopicPanel({ chatId });
|
||||||
});
|
}, [chatId, openCreateTopicPanel]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div className={styles.root}>
|
||||||
<div className={styles.sticker}>
|
<div className={styles.sticker}>
|
||||||
{animatedEmoji && <AnimatedIconFromSticker sticker={animatedEmoji} size={ICON_SIZE} />}
|
{animatedEmoji && <AnimatedIconFromSticker sticker={animatedEmoji} size={ICON_SIZE} />}
|
||||||
</div>
|
</div>
|
||||||
<h3 className={styles.title} dir="auto">{oldLang('ChatList.EmptyTopicsTitle')}</h3>
|
<h3 className={styles.title} dir="auto">{lang('ChatList.EmptyTopicsTitle')}</h3>
|
||||||
<p className={buildClassName(styles.description, styles.centered)} dir="auto">
|
<p className={buildClassName(styles.description, styles.centered)} dir="auto">
|
||||||
{oldLang('ChatList.EmptyTopicsDescription')}
|
{lang('ChatList.EmptyTopicsDescription')}
|
||||||
</p>
|
</p>
|
||||||
{canManageTopics && (
|
{canManageTopics && (
|
||||||
<Button
|
<Button
|
||||||
@ -59,7 +57,7 @@ const EmptyForum = ({
|
|||||||
isRtl={lang.isRtl}
|
isRtl={lang.isRtl}
|
||||||
>
|
>
|
||||||
<div className={styles.buttonText}>
|
<div className={styles.buttonText}>
|
||||||
{oldLang('ChatList.EmptyTopicsCreate')}
|
{lang('ChatList.EmptyTopicsCreate')}
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
@ -1,18 +1,19 @@
|
|||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import {
|
import {
|
||||||
beginHeavyAnimation,
|
beginHeavyAnimation,
|
||||||
memo, useEffect, useMemo, useRef, useState,
|
memo, useEffect, useMemo, useRef, useState,
|
||||||
} from '../../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiChat } from '../../../../api/types';
|
import type { ApiChat } from '../../../api/types';
|
||||||
import type { TopicsInfo } from '../../../../types';
|
import type { TopicsInfo } from '../../../types';
|
||||||
import { MAIN_THREAD_ID } from '../../../../api/types';
|
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
GENERAL_TOPIC_ID, TOPIC_HEIGHT_PX, TOPIC_LIST_SENSITIVE_AREA, TOPICS_SLICE,
|
GENERAL_TOPIC_ID, TOPIC_HEIGHT_PX, TOPIC_LIST_SENSITIVE_AREA, TOPICS_SLICE,
|
||||||
} from '../../../../config';
|
} from '../../../config';
|
||||||
import { requestNextMutation } from '../../../../lib/fasterdom/fasterdom';
|
import { requestNextMutation } from '../../../lib/fasterdom/fasterdom';
|
||||||
import { getOrderedTopics } from '../../../../global/helpers';
|
import { getOrderedTopics } from '../../../global/helpers';
|
||||||
import {
|
import {
|
||||||
selectCanAnimateInterface,
|
selectCanAnimateInterface,
|
||||||
selectChat,
|
selectChat,
|
||||||
@ -20,32 +21,29 @@ import {
|
|||||||
selectIsForumPanelOpen,
|
selectIsForumPanelOpen,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
selectTopicsInfo,
|
selectTopicsInfo,
|
||||||
} from '../../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import { IS_TOUCH_ENV } from '../../../../util/browser/windowEnvironment';
|
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
|
||||||
import buildClassName from '../../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import captureEscKeyListener from '../../../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||||
import { captureEvents, SwipeDirection } from '../../../../util/captureEvents';
|
import { captureEvents, SwipeDirection } from '../../../util/captureEvents';
|
||||||
import { waitForTransitionEnd } from '../../../../util/cssAnimationEndListeners';
|
import { waitForTransitionEnd } from '../../../util/cssAnimationEndListeners';
|
||||||
import { isUserId } from '../../../../util/entities/ids';
|
|
||||||
|
|
||||||
import useAppLayout from '../../../../hooks/useAppLayout';
|
import useAppLayout from '../../../hooks/useAppLayout';
|
||||||
import useHistoryBack from '../../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
import useInfiniteScroll from '../../../../hooks/useInfiniteScroll';
|
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
||||||
import { useIntersectionObserver, useOnIntersect } from '../../../../hooks/useIntersectionObserver';
|
import { useIntersectionObserver, useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
||||||
import useLang from '../../../../hooks/useLang';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
import usePreviousDeprecated from '../../../../hooks/usePreviousDeprecated';
|
import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated';
|
||||||
import useOrderDiff from '../hooks/useOrderDiff';
|
import useOrderDiff from './hooks/useOrderDiff';
|
||||||
|
|
||||||
import GroupCallTopPane from '../../../calls/group/GroupCallTopPane';
|
import GroupCallTopPane from '../../calls/group/GroupCallTopPane';
|
||||||
import GroupChatInfo from '../../../common/GroupChatInfo';
|
import GroupChatInfo from '../../common/GroupChatInfo';
|
||||||
import Icon from '../../../common/icons/Icon';
|
import Icon from '../../common/icons/Icon';
|
||||||
import PrivateChatInfo from '../../../common/PrivateChatInfo';
|
import HeaderActions from '../../middle/HeaderActions';
|
||||||
import HeaderActions from '../../../middle/HeaderActions';
|
import Button from '../../ui/Button';
|
||||||
import Button from '../../../ui/Button';
|
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||||
import InfiniteScroll from '../../../ui/InfiniteScroll';
|
import Loading from '../../ui/Loading';
|
||||||
import Loading from '../../../ui/Loading';
|
|
||||||
import AllMessagesTopic from './AllMessagesTopic';
|
|
||||||
import EmptyForum from './EmptyForum';
|
import EmptyForum from './EmptyForum';
|
||||||
import Topic from './Topic';
|
import Topic from './Topic';
|
||||||
|
|
||||||
@ -68,17 +66,17 @@ type StateProps = {
|
|||||||
|
|
||||||
const INTERSECTION_THROTTLE = 200;
|
const INTERSECTION_THROTTLE = 200;
|
||||||
|
|
||||||
const ForumPanel = ({
|
const ForumPanel: FC<OwnProps & StateProps> = ({
|
||||||
chat,
|
chat,
|
||||||
currentTopicId,
|
currentTopicId,
|
||||||
isOpen,
|
isOpen,
|
||||||
isHidden,
|
isHidden,
|
||||||
topicsInfo,
|
topicsInfo,
|
||||||
withInterfaceAnimations,
|
|
||||||
onTopicSearch,
|
onTopicSearch,
|
||||||
onCloseAnimationEnd,
|
onCloseAnimationEnd,
|
||||||
onOpenAnimationStart,
|
onOpenAnimationStart,
|
||||||
}: OwnProps & StateProps) => {
|
withInterfaceAnimations,
|
||||||
|
}) => {
|
||||||
const {
|
const {
|
||||||
closeForumPanel, openChatWithInfo, loadTopics,
|
closeForumPanel, openChatWithInfo, loadTopics,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
@ -97,7 +95,7 @@ const ForumPanel = ({
|
|||||||
}, [topicsInfo, chatId]);
|
}, [topicsInfo, chatId]);
|
||||||
|
|
||||||
const [isScrolled, setIsScrolled] = useState(false);
|
const [isScrolled, setIsScrolled] = useState(false);
|
||||||
const lang = useLang();
|
const lang = useOldLang();
|
||||||
|
|
||||||
const handleClose = useLastCallback(() => {
|
const handleClose = useLastCallback(() => {
|
||||||
closeForumPanel();
|
closeForumPanel();
|
||||||
@ -124,17 +122,13 @@ const ForumPanel = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const orderedIds = useMemo(() => {
|
const orderedIds = useMemo(() => {
|
||||||
const ids = topicsInfo
|
return topicsInfo
|
||||||
? getOrderedTopics(
|
? getOrderedTopics(
|
||||||
Object.values(topicsInfo.topicsById),
|
Object.values(topicsInfo.topicsById),
|
||||||
topicsInfo.orderedPinnedTopicIds,
|
topicsInfo.orderedPinnedTopicIds,
|
||||||
).map(({ id }) => id)
|
).map(({ id }) => id)
|
||||||
: [];
|
: [];
|
||||||
|
}, [topicsInfo]);
|
||||||
if (!chat?.isBotForum) return ids;
|
|
||||||
|
|
||||||
return [MAIN_THREAD_ID, ...ids];
|
|
||||||
}, [chat?.isBotForum, topicsInfo]);
|
|
||||||
|
|
||||||
const { orderDiffById, getAnimationType, onReorderAnimationEnd } = useOrderDiff(orderedIds, chat?.id);
|
const { orderDiffById, getAnimationType, onReorderAnimationEnd } = useOrderDiff(orderedIds, chat?.id);
|
||||||
|
|
||||||
@ -203,37 +197,23 @@ const ForumPanel = ({
|
|||||||
function renderTopics() {
|
function renderTopics() {
|
||||||
const viewportOffset = orderedIds.indexOf(viewportIds![0]);
|
const viewportOffset = orderedIds.indexOf(viewportIds![0]);
|
||||||
|
|
||||||
return viewportIds?.map((id, i) => {
|
return viewportIds?.map((id, i) => (
|
||||||
if (id === MAIN_THREAD_ID) {
|
<Topic
|
||||||
return (
|
key={id}
|
||||||
<AllMessagesTopic
|
chatId={chat!.id}
|
||||||
key={id}
|
topic={topicsInfo!.topicsById[id]}
|
||||||
chatId={chat!.id}
|
style={`top: ${(viewportOffset + i) * TOPIC_HEIGHT_PX}px;`}
|
||||||
isSelected={currentTopicId === id}
|
isSelected={currentTopicId === id}
|
||||||
/>
|
observeIntersection={observe}
|
||||||
);
|
animationType={getAnimationType(id)}
|
||||||
}
|
orderDiff={orderDiffById[id]}
|
||||||
|
onReorderAnimationEnd={onReorderAnimationEnd}
|
||||||
return (
|
/>
|
||||||
<Topic
|
));
|
||||||
key={id}
|
|
||||||
chatId={chat!.id}
|
|
||||||
topic={topicsInfo!.topicsById[id]}
|
|
||||||
style={`top: ${(viewportOffset + i) * TOPIC_HEIGHT_PX}px;`}
|
|
||||||
isSelected={currentTopicId === id}
|
|
||||||
observeIntersection={observe}
|
|
||||||
animationType={getAnimationType(id)}
|
|
||||||
orderDiff={orderDiffById[id]}
|
|
||||||
onReorderAnimationEnd={onReorderAnimationEnd}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const isLoading = topicsInfo === undefined;
|
const isLoading = topicsInfo === undefined;
|
||||||
|
|
||||||
if (!chat) return undefined;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@ -256,14 +236,7 @@ const ForumPanel = ({
|
|||||||
<Icon name="close" />
|
<Icon name="close" />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{isUserId(chat.id) ? (
|
{chat && (
|
||||||
<PrivateChatInfo
|
|
||||||
noAvatar
|
|
||||||
className={styles.info}
|
|
||||||
userId={chat.id}
|
|
||||||
onClick={handleToggleChatInfo}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<GroupChatInfo
|
<GroupChatInfo
|
||||||
noAvatar
|
noAvatar
|
||||||
className={styles.info}
|
className={styles.info}
|
||||||
@ -272,18 +245,21 @@ const ForumPanel = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<HeaderActions
|
{chat
|
||||||
chatId={chat.id}
|
&& (
|
||||||
threadId={MAIN_THREAD_ID}
|
<HeaderActions
|
||||||
messageListType="thread"
|
chatId={chat.id}
|
||||||
canExpandActions={false}
|
threadId={MAIN_THREAD_ID}
|
||||||
isForForum
|
messageListType="thread"
|
||||||
isMobile={isMobile}
|
canExpandActions={false}
|
||||||
onTopicSearch={onTopicSearch}
|
isForForum
|
||||||
/>
|
isMobile={isMobile}
|
||||||
|
onTopicSearch={onTopicSearch}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{!isUserId(chat.id) && <GroupCallTopPane chatId={chat.id} />}
|
{chat && <GroupCallTopPane chatId={chat.id} />}
|
||||||
|
|
||||||
<div className={styles.notch} />
|
<div className={styles.notch} />
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ import NewChatButton from '../NewChatButton';
|
|||||||
import LeftSearch from '../search/LeftSearch.async';
|
import LeftSearch from '../search/LeftSearch.async';
|
||||||
import ChatFolders from './ChatFolders';
|
import ChatFolders from './ChatFolders';
|
||||||
import ContactList from './ContactList.async';
|
import ContactList from './ContactList.async';
|
||||||
import ForumPanel from './forum/ForumPanel';
|
import ForumPanel from './ForumPanel';
|
||||||
import LeftMainHeader from './LeftMainHeader';
|
import LeftMainHeader from './LeftMainHeader';
|
||||||
|
|
||||||
import './LeftMain.scss';
|
import './LeftMain.scss';
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
import type { FC } from '../../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import { memo } from '../../../../lib/teact/teact';
|
import { memo } from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiChat, ApiDraft, ApiMessage, ApiMessageOutgoingStatus,
|
ApiChat, ApiDraft, ApiMessage, ApiMessageOutgoingStatus,
|
||||||
ApiPeer, ApiTopic, ApiTypeStory, ApiTypingStatus,
|
ApiPeer, ApiTopic, ApiTypeStory, ApiTypingStatus,
|
||||||
} from '../../../../api/types';
|
} from '../../../api/types';
|
||||||
import type { ObserveFn } from '../../../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||||
import type { ChatAnimationTypes } from '../hooks';
|
import type { ChatAnimationTypes } from './hooks';
|
||||||
|
|
||||||
import { UNMUTE_TIMESTAMP } from '../../../../config';
|
import { UNMUTE_TIMESTAMP } from '../../../config';
|
||||||
import { groupStatefulContent } from '../../../../global/helpers';
|
import { groupStatefulContent } from '../../../global/helpers';
|
||||||
import { getIsChatMuted } from '../../../../global/helpers/notifications';
|
import { getIsChatMuted } from '../../../global/helpers/notifications';
|
||||||
import {
|
import {
|
||||||
selectCanAnimateInterface,
|
selectCanAnimateInterface,
|
||||||
selectCanDeleteTopic,
|
selectCanDeleteTopic,
|
||||||
@ -27,25 +27,25 @@ import {
|
|||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
selectThreadParam,
|
selectThreadParam,
|
||||||
selectTopics,
|
selectTopics,
|
||||||
} from '../../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../../util/browser/windowEnvironment';
|
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../util/browser/windowEnvironment';
|
||||||
import buildClassName from '../../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { createLocationHash } from '../../../../util/routing';
|
import { createLocationHash } from '../../../util/routing';
|
||||||
import renderText from '../../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
|
|
||||||
import useFlag from '../../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useOldLang from '../../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
import useChatListEntry from '../hooks/useChatListEntry';
|
import useChatListEntry from './hooks/useChatListEntry';
|
||||||
import useTopicContextActions from '../hooks/useTopicContextActions';
|
import useTopicContextActions from './hooks/useTopicContextActions';
|
||||||
|
|
||||||
import Icon from '../../../common/icons/Icon';
|
import Icon from '../../common/icons/Icon';
|
||||||
import LastMessageMeta from '../../../common/LastMessageMeta';
|
import LastMessageMeta from '../../common/LastMessageMeta';
|
||||||
import TopicIcon from '../../../common/TopicIcon';
|
import TopicIcon from '../../common/TopicIcon';
|
||||||
import ConfirmDialog from '../../../ui/ConfirmDialog';
|
import ConfirmDialog from '../../ui/ConfirmDialog';
|
||||||
import ListItem from '../../../ui/ListItem';
|
import ListItem from '../../ui/ListItem';
|
||||||
import MuteChatModal from '../../MuteChatModal.async';
|
import MuteChatModal from '../MuteChatModal.async';
|
||||||
import ChatBadge from '../ChatBadge';
|
import ChatBadge from './ChatBadge';
|
||||||
|
|
||||||
import styles from './Topic.module.scss';
|
import styles from './Topic.module.scss';
|
||||||
|
|
||||||
@ -165,7 +165,7 @@ const Topic: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
openThread({ chatId, threadId: topic.id, shouldReplaceHistory: true });
|
openThread({ chatId, threadId: topic.id, shouldReplaceHistory: true });
|
||||||
if (!chat.isBotForum && !chat.isMonoforum) setViewForumAsMessages({ chatId, isEnabled: false });
|
setViewForumAsMessages({ chatId, isEnabled: false });
|
||||||
|
|
||||||
if (canScrollDown) {
|
if (canScrollDown) {
|
||||||
scrollMessageListToBottom();
|
scrollMessageListToBottom();
|
||||||
@ -263,7 +263,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const typingStatus = selectThreadParam(global, chatId, topic.id, 'typingStatus');
|
const typingStatus = selectThreadParam(global, chatId, topic.id, 'typingStatus');
|
||||||
const draft = selectDraft(global, chatId, topic.id);
|
const draft = selectDraft(global, chatId, topic.id);
|
||||||
const threadInfo = selectThreadInfo(global, chatId, topic.id);
|
const threadInfo = selectThreadInfo(global, chatId, topic.id);
|
||||||
const wasTopicOpened = chat?.isBotForum || Boolean(threadInfo?.lastReadInboxMessageId);
|
const wasTopicOpened = Boolean(threadInfo?.lastReadInboxMessageId);
|
||||||
const topics = selectTopics(global, chatId);
|
const topics = selectTopics(global, chatId);
|
||||||
|
|
||||||
const { chatId: currentChatId, threadId: currentThreadId } = selectCurrentMessageList(global) || {};
|
const { chatId: currentChatId, threadId: currentThreadId } = selectCurrentMessageList(global) || {};
|
||||||
@ -1,87 +0,0 @@
|
|||||||
import { memo } from '@teact';
|
|
||||||
import { getActions, withGlobal } from '../../../../global';
|
|
||||||
|
|
||||||
import { type ApiMessage, MAIN_THREAD_ID } from '../../../../api/types';
|
|
||||||
|
|
||||||
import { selectChatLastMessage } from '../../../../global/selectors';
|
|
||||||
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../../util/browser/windowEnvironment';
|
|
||||||
import buildClassName from '../../../../util/buildClassName';
|
|
||||||
import { createLocationHash } from '../../../../util/routing';
|
|
||||||
|
|
||||||
import useLang from '../../../../hooks/useLang';
|
|
||||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
|
||||||
|
|
||||||
import LastMessageMeta from '../../../common/LastMessageMeta';
|
|
||||||
import ListItem from '../../../ui/ListItem';
|
|
||||||
|
|
||||||
import styles from './Topic.module.scss';
|
|
||||||
|
|
||||||
type OwnProps = {
|
|
||||||
chatId: string;
|
|
||||||
isSelected: boolean;
|
|
||||||
style?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type StateProps = {
|
|
||||||
lastMessage?: ApiMessage;
|
|
||||||
};
|
|
||||||
|
|
||||||
const AllMessagesTopic = ({
|
|
||||||
chatId, isSelected, style, lastMessage,
|
|
||||||
}: OwnProps & StateProps) => {
|
|
||||||
const { openThread, openQuickPreview } = getActions();
|
|
||||||
|
|
||||||
const lang = useLang();
|
|
||||||
|
|
||||||
const handleOpenTopic = useLastCallback((e: React.MouseEvent) => {
|
|
||||||
if (e.altKey) {
|
|
||||||
e.preventDefault();
|
|
||||||
openQuickPreview({ id: chatId });
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
openThread({ chatId, threadId: MAIN_THREAD_ID, shouldReplaceHistory: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ListItem
|
|
||||||
className={buildClassName(
|
|
||||||
styles.root,
|
|
||||||
'Chat',
|
|
||||||
isSelected && 'selected',
|
|
||||||
'chat-item-clickable',
|
|
||||||
)}
|
|
||||||
onClick={handleOpenTopic}
|
|
||||||
style={style}
|
|
||||||
href={IS_OPEN_IN_NEW_TAB_SUPPORTED ? `#${createLocationHash(chatId, 'thread', MAIN_THREAD_ID)}` : undefined}
|
|
||||||
>
|
|
||||||
<div className="info">
|
|
||||||
<div className="info-row">
|
|
||||||
<div className={buildClassName('title')}>
|
|
||||||
<h3 dir="auto" className="fullName">{lang('BotForumAllTopicTitle')}</h3>
|
|
||||||
</div>
|
|
||||||
<div className="separator" />
|
|
||||||
{lastMessage && (
|
|
||||||
<LastMessageMeta
|
|
||||||
message={lastMessage}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="subtitle">
|
|
||||||
<span className="last-message">
|
|
||||||
{lang('BotForumAllTopicDescription')}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</ListItem>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
|
||||||
(global, { chatId }): Complete<StateProps> => {
|
|
||||||
const lastMessage = selectChatLastMessage(global, chatId, 'all');
|
|
||||||
return {
|
|
||||||
lastMessage,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
)(AllMessagesTopic));
|
|
||||||
@ -5,7 +5,6 @@ import type { ApiChat, ApiTopic } from '../../../../api/types';
|
|||||||
import type { MenuItemContextAction } from '../../../ui/ListItem';
|
import type { MenuItemContextAction } from '../../../ui/ListItem';
|
||||||
|
|
||||||
import { getCanManageTopic, getHasAdminRight } from '../../../../global/helpers';
|
import { getCanManageTopic, getHasAdminRight } from '../../../../global/helpers';
|
||||||
import { IS_TAURI } from '../../../../util/browser/globalEnvironment';
|
|
||||||
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../../util/browser/windowEnvironment';
|
import { IS_OPEN_IN_NEW_TAB_SUPPORTED } from '../../../../util/browser/windowEnvironment';
|
||||||
import { compact } from '../../../../util/iteratees';
|
import { compact } from '../../../../util/iteratees';
|
||||||
|
|
||||||
@ -49,11 +48,11 @@ export default function useTopicContextActions({
|
|||||||
openQuickPreview,
|
openQuickPreview,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const canToggleClosed = getCanManageTopic(chat, topic) && !chat.isBotForum;
|
const canToggleClosed = getCanManageTopic(chat, topic);
|
||||||
const canTogglePinned = chat.isCreator || getHasAdminRight(chat, 'manageTopics');
|
const canTogglePinned = chat.isCreator || getHasAdminRight(chat, 'manageTopics');
|
||||||
|
|
||||||
const actionOpenInNewTab = IS_OPEN_IN_NEW_TAB_SUPPORTED && {
|
const actionOpenInNewTab = IS_OPEN_IN_NEW_TAB_SUPPORTED && {
|
||||||
title: IS_TAURI ? lang('ChatListOpenInNewWindow') : lang('ChatListOpenInNewTab'),
|
title: 'Open in new tab',
|
||||||
icon: 'open-in-new-tab',
|
icon: 'open-in-new-tab',
|
||||||
handler: () => {
|
handler: () => {
|
||||||
openChatInNewTab({ chatId: chat.id, threadId: topicId });
|
openChatInNewTab({ chatId: chat.id, threadId: topicId });
|
||||||
|
|||||||
@ -863,8 +863,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const canGift = selectCanGift(global, chatId);
|
const canGift = selectCanGift(global, chatId);
|
||||||
|
|
||||||
const topic = selectTopic(global, chatId, threadId);
|
const topic = selectTopic(global, chatId, threadId);
|
||||||
// Disable manual creation for bot forums
|
const canCreateTopic = chat.isForum && (
|
||||||
const canCreateTopic = chat.isForum && !chat.isBotForum && (
|
|
||||||
chat.isCreator || !isUserRightBanned(chat, 'manageTopics') || getHasAdminRight(chat, 'manageTopics')
|
chat.isCreator || !isUserRightBanned(chat, 'manageTopics') || getHasAdminRight(chat, 'manageTopics')
|
||||||
);
|
);
|
||||||
const canEditTopic = topic && getCanManageTopic(chat, topic);
|
const canEditTopic = topic && getCanManageTopic(chat, topic);
|
||||||
|
|||||||
@ -1,8 +1,6 @@
|
|||||||
.MessageList {
|
.MessageList {
|
||||||
--action-message-bg: var(--pattern-color);
|
--action-message-bg: var(--pattern-color);
|
||||||
|
|
||||||
scroll-snap-type: y proximity;
|
|
||||||
|
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
@ -38,10 +36,6 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.no-bottom-snap {
|
|
||||||
scroll-snap-type: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.messages-container {
|
.messages-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -57,10 +51,6 @@
|
|||||||
margin-top: 100vh !important;
|
margin-top: 100vh !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.fab-trigger {
|
|
||||||
scroll-snap-align: end;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
// Patch for an issue on Android when rotating device
|
// Patch for an issue on Android when rotating device
|
||||||
|
|||||||
@ -14,7 +14,7 @@ import {
|
|||||||
MESSAGE_LIST_SLICE,
|
MESSAGE_LIST_SLICE,
|
||||||
SERVICE_NOTIFICATIONS_USER_ID,
|
SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
} from '../../config';
|
} from '../../config';
|
||||||
import { forceMeasure, requestForcedReflow, requestMeasure, requestMutation } from '../../lib/fasterdom/fasterdom';
|
import { forceMeasure, requestForcedReflow, requestMeasure } from '../../lib/fasterdom/fasterdom';
|
||||||
import {
|
import {
|
||||||
getIsSavedDialog,
|
getIsSavedDialog,
|
||||||
getMessageHtmlId,
|
getMessageHtmlId,
|
||||||
@ -54,7 +54,6 @@ import {
|
|||||||
import { selectIsChatRestricted } from '../../global/selectors/chats';
|
import { selectIsChatRestricted } from '../../global/selectors/chats';
|
||||||
import { selectActiveRestrictionReasons, selectCurrentMessageList } from '../../global/selectors/messages';
|
import { selectActiveRestrictionReasons, selectCurrentMessageList } from '../../global/selectors/messages';
|
||||||
import animateScroll, { isAnimatingScroll, restartCurrentScrollAnimation } from '../../util/animateScroll';
|
import animateScroll, { isAnimatingScroll, restartCurrentScrollAnimation } from '../../util/animateScroll';
|
||||||
import { IS_FIREFOX } from '../../util/browser/windowEnvironment';
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { isUserId } from '../../util/entities/ids';
|
import { isUserId } from '../../util/entities/ids';
|
||||||
import { orderBy } from '../../util/iteratees';
|
import { orderBy } from '../../util/iteratees';
|
||||||
@ -146,7 +145,6 @@ type StateProps = {
|
|||||||
translationLanguage?: string;
|
translationLanguage?: string;
|
||||||
shouldAutoTranslate?: boolean;
|
shouldAutoTranslate?: boolean;
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
isBotForum?: boolean;
|
|
||||||
shouldScrollToBottom?: boolean;
|
shouldScrollToBottom?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -168,19 +166,13 @@ const MESSAGE_REACTIONS_POLLING_INTERVAL = 20 * 1000;
|
|||||||
const MESSAGE_COMMENTS_POLLING_INTERVAL = 20 * 1000;
|
const MESSAGE_COMMENTS_POLLING_INTERVAL = 20 * 1000;
|
||||||
const MESSAGE_FACT_CHECK_UPDATE_INTERVAL = 5 * 1000;
|
const MESSAGE_FACT_CHECK_UPDATE_INTERVAL = 5 * 1000;
|
||||||
const MESSAGE_STORY_POLLING_INTERVAL = 5 * 60 * 1000;
|
const MESSAGE_STORY_POLLING_INTERVAL = 5 * 60 * 1000;
|
||||||
|
|
||||||
const BOTTOM_THRESHOLD = 50;
|
const BOTTOM_THRESHOLD = 50;
|
||||||
const BOTTOM_SNAP_THRESHOLD = 10;
|
|
||||||
|
|
||||||
const UNREAD_DIVIDER_TOP = 10;
|
const UNREAD_DIVIDER_TOP = 10;
|
||||||
const SCROLL_DEBOUNCE = 200;
|
const SCROLL_DEBOUNCE = 200;
|
||||||
const MESSAGE_ANIMATION_DURATION = 500;
|
const MESSAGE_ANIMATION_DURATION = 500;
|
||||||
const BOTTOM_FOCUS_MARGIN = 0.5 * REM;
|
const BOTTOM_FOCUS_MARGIN = 0.5 * REM;
|
||||||
const SELECT_MODE_ANIMATION_DURATION = 200;
|
const SELECT_MODE_ANIMATION_DURATION = 200;
|
||||||
|
|
||||||
const UNREAD_DIVIDER_CLASS = 'unread-divider';
|
const UNREAD_DIVIDER_CLASS = 'unread-divider';
|
||||||
const FORCE_MESSAGES_SCROLL_CLASS = 'force-messages-scroll';
|
|
||||||
const NO_BOTTOM_SNAP_CLASS = 'no-bottom-snap';
|
|
||||||
|
|
||||||
const runDebouncedForScroll = debounce((cb) => cb(), SCROLL_DEBOUNCE, false);
|
const runDebouncedForScroll = debounce((cb) => cb(), SCROLL_DEBOUNCE, false);
|
||||||
|
|
||||||
@ -196,7 +188,6 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
canPost,
|
canPost,
|
||||||
isSynced,
|
isSynced,
|
||||||
isActive,
|
isActive,
|
||||||
isBotForum,
|
|
||||||
shouldScrollToBottom,
|
shouldScrollToBottom,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-shadow
|
// eslint-disable-next-line @typescript-eslint/no-shadow
|
||||||
isChatMonoforum,
|
isChatMonoforum,
|
||||||
@ -267,7 +258,6 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
const memoFocusingIdRef = useRef<number>();
|
const memoFocusingIdRef = useRef<number>();
|
||||||
const isScrollTopJustUpdatedRef = useRef(false);
|
const isScrollTopJustUpdatedRef = useRef(false);
|
||||||
const shouldAnimateAppearanceRef = useRef(Boolean(lastMessage));
|
const shouldAnimateAppearanceRef = useRef(Boolean(lastMessage));
|
||||||
const scrollSnapDisabledTimerRef = useRef<number>();
|
|
||||||
|
|
||||||
const isSavedDialog = getIsSavedDialog(chatId, threadId, currentUserId);
|
const isSavedDialog = getIsSavedDialog(chatId, threadId, currentUserId);
|
||||||
const hasOpenChatButton = isSavedDialog && threadId !== ANONYMOUS_USER_ID;
|
const hasOpenChatButton = isSavedDialog && threadId !== ANONYMOUS_USER_ID;
|
||||||
@ -508,33 +498,6 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const [getContainerHeight, prevContainerHeightRef] = useContainerHeight(containerRef, canPost && !isSelectModeActive);
|
const [getContainerHeight, prevContainerHeightRef] = useContainerHeight(containerRef, canPost && !isSelectModeActive);
|
||||||
|
|
||||||
const handleWheel = useLastCallback((e: React.WheelEvent<HTMLDivElement>) => {
|
|
||||||
// Firefox is finicky about bottom scroll snapping, so we enable it only when nearing the bottom
|
|
||||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1753188
|
|
||||||
if (!IS_FIREFOX) return;
|
|
||||||
|
|
||||||
const container = containerRef.current;
|
|
||||||
if (!container) return;
|
|
||||||
|
|
||||||
const scrollTop = container.scrollTop;
|
|
||||||
const scrollHeight = container.scrollHeight;
|
|
||||||
const offsetHeight = container.offsetHeight;
|
|
||||||
const isNearBottomForSnap = scrollTop >= scrollHeight - offsetHeight - BOTTOM_SNAP_THRESHOLD;
|
|
||||||
if (!isNearBottomForSnap) return;
|
|
||||||
|
|
||||||
if (e.deltaY < 0) {
|
|
||||||
clearTimeout(scrollSnapDisabledTimerRef.current);
|
|
||||||
requestMutation(() => {
|
|
||||||
addExtraClass(container, NO_BOTTOM_SNAP_CLASS);
|
|
||||||
container.scrollBy(0, -BOTTOM_SNAP_THRESHOLD); // Manually scroll to prevent ignoring first event
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
requestMutation(() => {
|
|
||||||
removeExtraClass(container, NO_BOTTOM_SNAP_CLASS);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initial message loading
|
// Initial message loading
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!loadMoreAround || !isChatLoaded || isRestricted || focusingId) {
|
if (!loadMoreAround || !isChatLoaded || isRestricted || focusingId) {
|
||||||
@ -628,30 +591,21 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
isViewportNewest
|
isViewportNewest
|
||||||
&& wasMessageAdded
|
&& wasMessageAdded
|
||||||
&& (messageIds && messageIds.length < MESSAGE_LIST_SLICE / 2)
|
&& (messageIds && messageIds.length < MESSAGE_LIST_SLICE / 2)
|
||||||
&& !container.parentElement!.classList.contains(FORCE_MESSAGES_SCROLL_CLASS)
|
&& !container.parentElement!.classList.contains('force-messages-scroll')
|
||||||
&& forceMeasure(() => (
|
&& forceMeasure(() => (
|
||||||
(container.firstElementChild as HTMLDivElement).clientHeight <= container.offsetHeight * 2
|
(container.firstElementChild as HTMLDivElement).clientHeight <= container.offsetHeight * 2
|
||||||
))
|
))
|
||||||
) {
|
) {
|
||||||
addExtraClass(container.parentElement!, FORCE_MESSAGES_SCROLL_CLASS);
|
addExtraClass(container.parentElement!, 'force-messages-scroll');
|
||||||
|
container.parentElement!.classList.add('force-messages-scroll');
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (container.parentElement) {
|
if (container.parentElement) {
|
||||||
removeExtraClass(container.parentElement, FORCE_MESSAGES_SCROLL_CLASS);
|
removeExtraClass(container.parentElement, 'force-messages-scroll');
|
||||||
}
|
}
|
||||||
}, MESSAGE_ANIMATION_DURATION);
|
}, MESSAGE_ANIMATION_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wasMessageAdded) {
|
|
||||||
clearTimeout(scrollSnapDisabledTimerRef.current);
|
|
||||||
|
|
||||||
addExtraClass(container, NO_BOTTOM_SNAP_CLASS);
|
|
||||||
|
|
||||||
scrollSnapDisabledTimerRef.current = window.setTimeout(() => {
|
|
||||||
removeExtraClass(container, NO_BOTTOM_SNAP_CLASS);
|
|
||||||
}, MESSAGE_ANIMATION_DURATION);
|
|
||||||
}
|
|
||||||
|
|
||||||
requestForcedReflow(() => {
|
requestForcedReflow(() => {
|
||||||
const { scrollTop, scrollHeight, offsetHeight } = container;
|
const { scrollTop, scrollHeight, offsetHeight } = container;
|
||||||
const scrollOffset = scrollOffsetRef.current;
|
const scrollOffset = scrollOffsetRef.current;
|
||||||
@ -768,7 +722,6 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
!isReady && 'is-animating',
|
!isReady && 'is-animating',
|
||||||
hasOpenChatButton && 'saved-dialog',
|
hasOpenChatButton && 'saved-dialog',
|
||||||
isChatProtected && 'hide-on-print',
|
isChatProtected && 'hide-on-print',
|
||||||
IS_FIREFOX && NO_BOTTOM_SNAP_CLASS,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasMessages = Boolean((messageIds && messageGroups) || lastMessage);
|
const hasMessages = Boolean((messageIds && messageGroups) || lastMessage);
|
||||||
@ -851,7 +804,6 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
noAppearanceAnimation={!messageGroups || !shouldAnimateAppearanceRef.current}
|
noAppearanceAnimation={!messageGroups || !shouldAnimateAppearanceRef.current}
|
||||||
isQuickPreview={isQuickPreview}
|
isQuickPreview={isQuickPreview}
|
||||||
canPost={canPost}
|
canPost={canPost}
|
||||||
isBotForum={isBotForum}
|
|
||||||
shouldScrollToBottom={shouldScrollToBottom}
|
shouldScrollToBottom={shouldScrollToBottom}
|
||||||
onScrollDownToggle={onScrollDownToggle}
|
onScrollDownToggle={onScrollDownToggle}
|
||||||
onNotchToggle={onNotchToggle}
|
onNotchToggle={onNotchToggle}
|
||||||
@ -870,7 +822,6 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
|||||||
activeKey={activeKey}
|
activeKey={activeKey}
|
||||||
shouldCleanup
|
shouldCleanup
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
onWheel={handleWheel}
|
|
||||||
onMouseDown={preventMessageInputBlur}
|
onMouseDown={preventMessageInputBlur}
|
||||||
>
|
>
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
@ -986,7 +937,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
canTranslate,
|
canTranslate,
|
||||||
translationLanguage,
|
translationLanguage,
|
||||||
shouldAutoTranslate,
|
shouldAutoTranslate,
|
||||||
isBotForum: chat.isBotForum,
|
|
||||||
shouldScrollToBottom,
|
shouldScrollToBottom,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -37,7 +37,6 @@ import usePreviousDeprecated from '../../hooks/usePreviousDeprecated';
|
|||||||
import useMessageObservers from './hooks/useMessageObservers';
|
import useMessageObservers from './hooks/useMessageObservers';
|
||||||
import useScrollHooks from './hooks/useScrollHooks';
|
import useScrollHooks from './hooks/useScrollHooks';
|
||||||
|
|
||||||
import Icon from '../common/icons/Icon';
|
|
||||||
import MiniTable, { type TableEntry } from '../common/MiniTable';
|
import MiniTable, { type TableEntry } from '../common/MiniTable';
|
||||||
import ActionMessage from './message/ActionMessage';
|
import ActionMessage from './message/ActionMessage';
|
||||||
import Message from './message/Message';
|
import Message from './message/Message';
|
||||||
@ -60,7 +59,6 @@ interface OwnProps {
|
|||||||
withUsers: boolean;
|
withUsers: boolean;
|
||||||
isChannelChat: boolean | undefined;
|
isChannelChat: boolean | undefined;
|
||||||
isChatMonoforum?: boolean;
|
isChatMonoforum?: boolean;
|
||||||
isBotForum?: boolean;
|
|
||||||
isEmptyThread?: boolean;
|
isEmptyThread?: boolean;
|
||||||
isComments?: boolean;
|
isComments?: boolean;
|
||||||
noAvatars: boolean;
|
noAvatars: boolean;
|
||||||
@ -101,7 +99,6 @@ const MessageListContent = ({
|
|||||||
withUsers,
|
withUsers,
|
||||||
isChannelChat,
|
isChannelChat,
|
||||||
isChatMonoforum,
|
isChatMonoforum,
|
||||||
isBotForum,
|
|
||||||
noAvatars,
|
noAvatars,
|
||||||
containerRef,
|
containerRef,
|
||||||
anchorIdRef,
|
anchorIdRef,
|
||||||
@ -246,20 +243,6 @@ const MessageListContent = ({
|
|||||||
return undefined;
|
return undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderBotForumTopicAction = () => {
|
|
||||||
if (!isBotForum || threadId !== MAIN_THREAD_ID) return undefined;
|
|
||||||
return (
|
|
||||||
<div className={buildClassName('local-action-message', actionMessageStyles.root)} key="botforum-new-topic">
|
|
||||||
<div className={actionMessageStyles.contentBox}>
|
|
||||||
<Icon className={actionMessageStyles.botForumTopicIcon} name="topic-new" />
|
|
||||||
<h3 className={actionMessageStyles.botForumTopicTitle}>{lang('BotForumActionNew')}</h3>
|
|
||||||
<span className={actionMessageStyles.botForumTopicDescription}>{lang('BotForumActionNewDescription')}</span>
|
|
||||||
<Icon className={actionMessageStyles.botForumTopicArrow} name="down" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const messageCountToAnimate = noAppearanceAnimation ? 0 : messageGroups.reduce((acc, messageGroup) => {
|
const messageCountToAnimate = noAppearanceAnimation ? 0 : messageGroups.reduce((acc, messageGroup) => {
|
||||||
return acc + messageGroup.senderGroups.flat().length;
|
return acc + messageGroup.senderGroups.flat().length;
|
||||||
}, 0);
|
}, 0);
|
||||||
@ -465,7 +448,6 @@ const MessageListContent = ({
|
|||||||
{shouldRenderAccountInfo
|
{shouldRenderAccountInfo
|
||||||
&& <MessageListAccountInfo key={`account_info_${chatId}`} chatId={chatId} hasMessages />}
|
&& <MessageListAccountInfo key={`account_info_${chatId}`} chatId={chatId} hasMessages />}
|
||||||
{dateGroups.flat()}
|
{dateGroups.flat()}
|
||||||
{isViewportNewest && renderBotForumTopicAction()}
|
|
||||||
{withHistoryTriggers && (
|
{withHistoryTriggers && (
|
||||||
<div
|
<div
|
||||||
ref={forwardsTriggerRef}
|
ref={forwardsTriggerRef}
|
||||||
|
|||||||
@ -279,12 +279,11 @@ const MiddleHeader: FC<OwnProps & StateProps> = ({
|
|||||||
<PrivateChatInfo
|
<PrivateChatInfo
|
||||||
key={displayChatId}
|
key={displayChatId}
|
||||||
userId={displayChatId}
|
userId={displayChatId}
|
||||||
threadId={!isSavedDialog ? threadId : undefined}
|
|
||||||
typingStatus={typingStatus}
|
typingStatus={typingStatus}
|
||||||
status={connectionStatusText || savedMessagesStatus}
|
status={connectionStatusText || savedMessagesStatus}
|
||||||
withDots={Boolean(connectionStatusText)}
|
withDots={Boolean(connectionStatusText)}
|
||||||
withFullInfo={threadId === MAIN_THREAD_ID}
|
withFullInfo
|
||||||
withMediaViewer={threadId === MAIN_THREAD_ID}
|
withMediaViewer
|
||||||
withStory={!isChatWithSelf}
|
withStory={!isChatWithSelf}
|
||||||
withUpdatingStatus
|
withUpdatingStatus
|
||||||
isSavedDialog={isSavedDialog}
|
isSavedDialog={isSavedDialog}
|
||||||
|
|||||||
@ -9,8 +9,8 @@ import { selectChatMessage, selectCurrentMessageList } from '../../../global/sel
|
|||||||
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
|
import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment';
|
||||||
import renderKeyboardButtonText from './helpers/renderKeyboardButtonText';
|
import renderKeyboardButtonText from './helpers/renderKeyboardButtonText';
|
||||||
|
|
||||||
import useLang from '../../../hooks/useLang';
|
|
||||||
import useMouseInside from '../../../hooks/useMouseInside';
|
import useMouseInside from '../../../hooks/useMouseInside';
|
||||||
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import Menu from '../../ui/Menu';
|
import Menu from '../../ui/Menu';
|
||||||
@ -33,7 +33,7 @@ const BotKeyboardMenu: FC<OwnProps & StateProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { clickBotInlineButton } = getActions();
|
const { clickBotInlineButton } = getActions();
|
||||||
|
|
||||||
const lang = useLang();
|
const lang = useOldLang();
|
||||||
|
|
||||||
const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose);
|
const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose);
|
||||||
const { isKeyboardSingleUse } = message || {};
|
const { isKeyboardSingleUse } = message || {};
|
||||||
|
|||||||
@ -7,9 +7,11 @@ import { STARS_ICON_PLACEHOLDER } from '../../../../config';
|
|||||||
import { replaceWithTeact } from '../../../../util/replaceWithTeact';
|
import { replaceWithTeact } from '../../../../util/replaceWithTeact';
|
||||||
import renderText from '../../../common/helpers/renderText';
|
import renderText from '../../../common/helpers/renderText';
|
||||||
|
|
||||||
|
import { type OldLangFn } from '../../../../hooks/useOldLang';
|
||||||
|
|
||||||
import Icon from '../../../common/icons/Icon';
|
import Icon from '../../../common/icons/Icon';
|
||||||
|
|
||||||
export default function renderKeyboardButtonText(lang: LangFn, button: ApiKeyboardButton): TeactNode {
|
export default function renderKeyboardButtonText(lang: OldLangFn | LangFn, button: ApiKeyboardButton): TeactNode {
|
||||||
if (button.type === 'receipt') {
|
if (button.type === 'receipt') {
|
||||||
return lang('PaymentReceipt');
|
return lang('PaymentReceipt');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -83,7 +83,7 @@ export default function useMessageObservers(
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!isQuickPreview) {
|
if (!isQuickPreview) {
|
||||||
if (memoFirstUnreadIdRef.current && maxId && maxId >= memoFirstUnreadIdRef.current) {
|
if (memoFirstUnreadIdRef.current && maxId >= memoFirstUnreadIdRef.current) {
|
||||||
markMessageListRead({ maxId });
|
markMessageListRead({ maxId });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -283,23 +283,3 @@
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
.botForumTopicIcon {
|
|
||||||
padding: 1.25rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
font-size: 2.5rem;
|
|
||||||
background-color: var(--action-message-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.botForumTopicTitle {
|
|
||||||
margin-block: 0.5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.botForumTopicDescription {
|
|
||||||
font-weight: var(--font-weight-normal);
|
|
||||||
}
|
|
||||||
|
|
||||||
.botForumTopicArrow {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -40,6 +40,7 @@ import useEnsureMessage from '../../../hooks/useEnsureMessage';
|
|||||||
import useFlag from '../../../hooks/useFlag';
|
import useFlag from '../../../hooks/useFlag';
|
||||||
import { type ObserveFn, useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
import { type ObserveFn, useOnIntersect } from '../../../hooks/useIntersectionObserver';
|
||||||
import useLastCallback from '../../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
|
import useMessageResizeObserver from '../../../hooks/useResizeMessageObserver';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import { type OnIntersectPinnedMessage } from '../hooks/usePinnedMessage';
|
import { type OnIntersectPinnedMessage } from '../hooks/usePinnedMessage';
|
||||||
import useFluidBackgroundFilter from './hooks/useFluidBackgroundFilter';
|
import useFluidBackgroundFilter from './hooks/useFluidBackgroundFilter';
|
||||||
@ -124,11 +125,11 @@ const ActionMessage = ({
|
|||||||
hasUnreadReaction,
|
hasUnreadReaction,
|
||||||
isResizingContainer,
|
isResizingContainer,
|
||||||
scrollTargetPosition,
|
scrollTargetPosition,
|
||||||
isAccountFrozen,
|
|
||||||
onIntersectPinnedMessage,
|
onIntersectPinnedMessage,
|
||||||
observeIntersectionForBottom,
|
observeIntersectionForBottom,
|
||||||
observeIntersectionForLoading,
|
observeIntersectionForLoading,
|
||||||
observeIntersectionForPlaying,
|
observeIntersectionForPlaying,
|
||||||
|
isAccountFrozen,
|
||||||
}: OwnProps & StateProps) => {
|
}: OwnProps & StateProps) => {
|
||||||
const {
|
const {
|
||||||
requestConfetti,
|
requestConfetti,
|
||||||
@ -167,6 +168,8 @@ const ActionMessage = ({
|
|||||||
|
|
||||||
useOnIntersect(ref, !shouldSkipRender ? observeIntersectionForBottom : undefined);
|
useOnIntersect(ref, !shouldSkipRender ? observeIntersectionForBottom : undefined);
|
||||||
|
|
||||||
|
useMessageResizeObserver(ref, !shouldSkipRender && isLastInList && action.type !== 'channelJoined');
|
||||||
|
|
||||||
useEnsureMessage(
|
useEnsureMessage(
|
||||||
replyToPeerId || chatId,
|
replyToPeerId || chatId,
|
||||||
replyToMsgId,
|
replyToMsgId,
|
||||||
|
|||||||
@ -40,7 +40,6 @@ import {
|
|||||||
getPinnedMediaValue,
|
getPinnedMediaValue,
|
||||||
renderMessageLink,
|
renderMessageLink,
|
||||||
renderPeerLink,
|
renderPeerLink,
|
||||||
renderTopicLink,
|
|
||||||
translateWithYou,
|
translateWithYou,
|
||||||
} from './helpers/messageActions';
|
} from './helpers/messageActions';
|
||||||
|
|
||||||
@ -82,6 +81,7 @@ const ActionMessageText = ({
|
|||||||
asPreview,
|
asPreview,
|
||||||
}: OwnProps & StateProps) => {
|
}: OwnProps & StateProps) => {
|
||||||
const {
|
const {
|
||||||
|
openThread,
|
||||||
openTelegramLink,
|
openTelegramLink,
|
||||||
openUrl,
|
openUrl,
|
||||||
} = getActions();
|
} = getActions();
|
||||||
@ -231,15 +231,18 @@ const ActionMessageText = ({
|
|||||||
|
|
||||||
const topicId = selectThreadIdFromMessage(global, message);
|
const topicId = selectThreadIdFromMessage(global, message);
|
||||||
|
|
||||||
const topicLinkContent = (
|
const topicLink = (
|
||||||
<>
|
<Link
|
||||||
|
className={styles.topicLink}
|
||||||
|
|
||||||
|
onClick={() => openThread({ chatId, threadId: topicId })}
|
||||||
|
>
|
||||||
{iconEmojiId ? <CustomEmoji documentId={iconEmojiId} isSelectable />
|
{iconEmojiId ? <CustomEmoji documentId={iconEmojiId} isSelectable />
|
||||||
: <TopicDefaultIcon topicId={topicId} title={title} iconColor={iconColor} />}
|
: <TopicDefaultIcon topicId={topicId} title={title} iconColor={iconColor} />}
|
||||||
{NBSP}
|
{NBSP}
|
||||||
{renderText(title)}
|
{renderText(title)}
|
||||||
</>
|
</Link>
|
||||||
);
|
);
|
||||||
const topicLink = renderTopicLink(chatId, Number(topicId), topicLinkContent, asPreview);
|
|
||||||
return lang('ActionTopicCreated', { topic: topicLink }, { withNodes: true });
|
return lang('ActionTopicCreated', { topic: topicLink }, { withNodes: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,8 +253,12 @@ const ActionMessageText = ({
|
|||||||
|
|
||||||
const topicId = selectThreadIdFromMessage(global, message);
|
const topicId = selectThreadIdFromMessage(global, message);
|
||||||
const currentTopic = selectTopic(global, chatId, topicId);
|
const currentTopic = selectTopic(global, chatId, topicId);
|
||||||
const topicLinkContent = (
|
const topicLink = (
|
||||||
<>
|
<Link
|
||||||
|
className={styles.topicLink}
|
||||||
|
|
||||||
|
onClick={() => openThread({ chatId, threadId: topicId })}
|
||||||
|
>
|
||||||
{iconEmojiId && iconEmojiId !== DEFAULT_TOPIC_ICON_ID
|
{iconEmojiId && iconEmojiId !== DEFAULT_TOPIC_ICON_ID
|
||||||
? <CustomEmoji documentId={iconEmojiId} isSelectable />
|
? <CustomEmoji documentId={iconEmojiId} isSelectable />
|
||||||
: (
|
: (
|
||||||
@ -263,12 +270,17 @@ const ActionMessageText = ({
|
|||||||
)}
|
)}
|
||||||
{topicId !== GENERAL_TOPIC_ID && NBSP}
|
{topicId !== GENERAL_TOPIC_ID && NBSP}
|
||||||
{renderText(title || currentTopic?.title || lang('ActionTopicPlaceholder'))}
|
{renderText(title || currentTopic?.title || lang('ActionTopicPlaceholder'))}
|
||||||
</>
|
</Link>
|
||||||
);
|
);
|
||||||
const topicLink = renderTopicLink(chatId, Number(topicId), topicLinkContent, asPreview);
|
|
||||||
|
|
||||||
const topicPlaceholderLink = renderTopicLink(
|
const topicPlaceholderLink = (
|
||||||
chatId, Number(topicId), lang('ActionTopicPlaceholder'), asPreview,
|
<Link
|
||||||
|
className={styles.topicLink}
|
||||||
|
|
||||||
|
onClick={() => openThread({ chatId, threadId: topicId })}
|
||||||
|
>
|
||||||
|
{lang('ActionTopicPlaceholder')}
|
||||||
|
</Link>
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isClosed !== undefined) {
|
if (isClosed !== undefined) {
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import type { TeactNode } from '../../../lib/teact/teact';
|
import type { FC, TeactNode } from '../../../lib/teact/teact';
|
||||||
import { memo, useMemo } from '../../../lib/teact/teact';
|
import { memo, useMemo } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
import type { ApiKeyboardButton } from '../../../api/types';
|
import type { ApiKeyboardButton, ApiMessage } from '../../../api/types';
|
||||||
|
import type { ActionPayloads } from '../../../global/types';
|
||||||
|
|
||||||
import { RE_TME_LINK, TME_LINK_PREFIX } from '../../../config';
|
import { RE_TME_LINK, TME_LINK_PREFIX } from '../../../config';
|
||||||
import renderKeyboardButtonText from '../composer/helpers/renderKeyboardButtonText';
|
import renderKeyboardButtonText from '../composer/helpers/renderKeyboardButtonText';
|
||||||
|
|
||||||
import useLang from '../../../hooks/useLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
|
|
||||||
import Icon from '../../common/icons/Icon';
|
import Icon from '../../common/icons/Icon';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
@ -14,12 +15,12 @@ import Button from '../../ui/Button';
|
|||||||
import './InlineButtons.scss';
|
import './InlineButtons.scss';
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps = {
|
||||||
inlineButtons: ApiKeyboardButton[][];
|
message: ApiMessage;
|
||||||
onClick: (payload: ApiKeyboardButton) => void;
|
onClick: (payload: ActionPayloads['clickBotInlineButton']) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
const InlineButtons = ({ inlineButtons, onClick }: OwnProps) => {
|
const InlineButtons: FC<OwnProps> = ({ message, onClick }) => {
|
||||||
const lang = useLang();
|
const lang = useOldLang();
|
||||||
|
|
||||||
const renderIcon = (button: ApiKeyboardButton) => {
|
const renderIcon = (button: ApiKeyboardButton) => {
|
||||||
const { type } = button;
|
const { type } = button;
|
||||||
@ -65,15 +66,15 @@ const InlineButtons = ({ inlineButtons, onClick }: OwnProps) => {
|
|||||||
|
|
||||||
const buttonTexts = useMemo(() => {
|
const buttonTexts = useMemo(() => {
|
||||||
const texts: TeactNode[][] = [];
|
const texts: TeactNode[][] = [];
|
||||||
inlineButtons.forEach((row) => {
|
message.inlineButtons!.forEach((row) => {
|
||||||
texts.push(row.map((button) => renderKeyboardButtonText(lang, button)));
|
texts.push(row.map((button) => renderKeyboardButtonText(lang, button)));
|
||||||
});
|
});
|
||||||
return texts;
|
return texts;
|
||||||
}, [lang, inlineButtons]);
|
}, [lang, message.inlineButtons]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="InlineButtons">
|
<div className="InlineButtons">
|
||||||
{inlineButtons.map((row, i) => (
|
{message.inlineButtons!.map((row, i) => (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
{row.map((button, j) => (
|
{row.map((button, j) => (
|
||||||
<Button
|
<Button
|
||||||
@ -81,7 +82,7 @@ const InlineButtons = ({ inlineButtons, onClick }: OwnProps) => {
|
|||||||
ripple
|
ripple
|
||||||
disabled={button.type === 'unsupported' || (button.type === 'suggestedMessage' && button.disabled)}
|
disabled={button.type === 'unsupported' || (button.type === 'suggestedMessage' && button.disabled)}
|
||||||
|
|
||||||
onClick={() => onClick(button)}
|
onClick={() => onClick({ chatId: message.chatId, messageId: message.id, button })}
|
||||||
>
|
>
|
||||||
{renderIcon(button)}
|
{renderIcon(button)}
|
||||||
<span className="inline-button-text">
|
<span className="inline-button-text">
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import type {
|
|||||||
ApiAvailableReaction,
|
ApiAvailableReaction,
|
||||||
ApiChat,
|
ApiChat,
|
||||||
ApiChatMember,
|
ApiChatMember,
|
||||||
ApiKeyboardButton,
|
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiMessageOutgoingStatus,
|
ApiMessageOutgoingStatus,
|
||||||
ApiPeer,
|
ApiPeer,
|
||||||
@ -28,6 +27,7 @@ import type {
|
|||||||
ApiUser,
|
ApiUser,
|
||||||
ApiWebPage,
|
ApiWebPage,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
|
import type { ActionPayloads } from '../../../global/types';
|
||||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||||
import type {
|
import type {
|
||||||
ActiveEmojiInteraction,
|
ActiveEmojiInteraction,
|
||||||
@ -150,6 +150,7 @@ import useLastCallback from '../../../hooks/useLastCallback';
|
|||||||
import useOldLang from '../../../hooks/useOldLang';
|
import useOldLang from '../../../hooks/useOldLang';
|
||||||
import usePeerColor from '../../../hooks/usePeerColor';
|
import usePeerColor from '../../../hooks/usePeerColor';
|
||||||
import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated';
|
import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated';
|
||||||
|
import useMessageResizeObserver from '../../../hooks/useResizeMessageObserver';
|
||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import useTextLanguage from '../../../hooks/useTextLanguage';
|
import useTextLanguage from '../../../hooks/useTextLanguage';
|
||||||
import useDetectChatLanguage from './hooks/useDetectChatLanguage';
|
import useDetectChatLanguage from './hooks/useDetectChatLanguage';
|
||||||
@ -214,25 +215,27 @@ type MessagePositionProperties = {
|
|||||||
isLastInList: boolean;
|
isLastInList: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
type OwnProps = {
|
type OwnProps =
|
||||||
message: ApiMessage;
|
{
|
||||||
album?: IAlbum;
|
message: ApiMessage;
|
||||||
noAvatars?: boolean;
|
album?: IAlbum;
|
||||||
withAvatar?: boolean;
|
noAvatars?: boolean;
|
||||||
withSenderName?: boolean;
|
withAvatar?: boolean;
|
||||||
threadId: ThreadId;
|
withSenderName?: boolean;
|
||||||
messageListType: MessageListType;
|
threadId: ThreadId;
|
||||||
noComments: boolean;
|
messageListType: MessageListType;
|
||||||
noReplies: boolean;
|
noComments: boolean;
|
||||||
appearanceOrder: number;
|
noReplies: boolean;
|
||||||
isJustAdded: boolean;
|
appearanceOrder: number;
|
||||||
memoFirstUnreadIdRef?: { current: number | undefined };
|
isJustAdded: boolean;
|
||||||
getIsMessageListReady?: Signal<boolean>;
|
memoFirstUnreadIdRef?: { current: number | undefined };
|
||||||
observeIntersectionForBottom?: ObserveFn;
|
getIsMessageListReady?: Signal<boolean>;
|
||||||
observeIntersectionForLoading?: ObserveFn;
|
observeIntersectionForBottom?: ObserveFn;
|
||||||
observeIntersectionForPlaying?: ObserveFn;
|
observeIntersectionForLoading?: ObserveFn;
|
||||||
onIntersectPinnedMessage?: OnIntersectPinnedMessage;
|
observeIntersectionForPlaying?: ObserveFn;
|
||||||
} & MessagePositionProperties;
|
onIntersectPinnedMessage?: OnIntersectPinnedMessage;
|
||||||
|
}
|
||||||
|
& MessagePositionProperties;
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
theme: ThemeKey;
|
theme: ThemeKey;
|
||||||
@ -264,7 +267,6 @@ type StateProps = {
|
|||||||
isResizingContainer?: boolean;
|
isResizingContainer?: boolean;
|
||||||
isForwarding?: boolean;
|
isForwarding?: boolean;
|
||||||
isChatWithSelf?: boolean;
|
isChatWithSelf?: boolean;
|
||||||
isBotForum?: boolean;
|
|
||||||
isRepliesChat?: boolean;
|
isRepliesChat?: boolean;
|
||||||
isAnonymousForwards?: boolean;
|
isAnonymousForwards?: boolean;
|
||||||
isChannel?: boolean;
|
isChannel?: boolean;
|
||||||
@ -393,7 +395,6 @@ const Message = ({
|
|||||||
isResizingContainer,
|
isResizingContainer,
|
||||||
isForwarding,
|
isForwarding,
|
||||||
isChatWithSelf,
|
isChatWithSelf,
|
||||||
isBotForum,
|
|
||||||
isRepliesChat,
|
isRepliesChat,
|
||||||
isAnonymousForwards,
|
isAnonymousForwards,
|
||||||
isChannel,
|
isChannel,
|
||||||
@ -463,7 +464,6 @@ const Message = ({
|
|||||||
animateUnreadReaction,
|
animateUnreadReaction,
|
||||||
focusMessage,
|
focusMessage,
|
||||||
markMentionsRead,
|
markMentionsRead,
|
||||||
openThread,
|
|
||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const ref = useRef<HTMLDivElement>();
|
const ref = useRef<HTMLDivElement>();
|
||||||
@ -524,7 +524,6 @@ const Message = ({
|
|||||||
|
|
||||||
const {
|
const {
|
||||||
id: messageId, chatId, forwardInfo, viaBotId, isTranscriptionError, factCheck,
|
id: messageId, chatId, forwardInfo, viaBotId, isTranscriptionError, factCheck,
|
||||||
isTypingDraft,
|
|
||||||
} = message;
|
} = message;
|
||||||
|
|
||||||
useUnmountCleanup(() => {
|
useUnmountCleanup(() => {
|
||||||
@ -910,6 +909,8 @@ const Message = ({
|
|||||||
|| ((asForwarded || isChatWithSelf) && forwardInfo?.postAuthorTitle)
|
|| ((asForwarded || isChatWithSelf) && forwardInfo?.postAuthorTitle)
|
||||||
|| undefined;
|
|| undefined;
|
||||||
|
|
||||||
|
useMessageResizeObserver(ref, isLastInList);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const bottomMarker = bottomMarkerRef.current;
|
const bottomMarker = bottomMarkerRef.current;
|
||||||
if (!bottomMarker || !isElementInViewport(bottomMarker)) return;
|
if (!bottomMarker || !isElementInViewport(bottomMarker)) return;
|
||||||
@ -1026,7 +1027,6 @@ const Message = ({
|
|||||||
canBeEmpty={hasFactCheck}
|
canBeEmpty={hasFactCheck}
|
||||||
maxTimestamp={maxTimestamp}
|
maxTimestamp={maxTimestamp}
|
||||||
threadId={threadId}
|
threadId={threadId}
|
||||||
shouldAnimateTyping={isTypingDraft}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1540,45 +1540,25 @@ const Message = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleInlineButtonClick = useLastCallback((button: ApiKeyboardButton) => {
|
const handleSuggestedMessageButton = useLastCallback((payload: ActionPayloads['clickBotInlineButton']) => {
|
||||||
clickBotInlineButton({
|
if (payload.button.type !== 'suggestedMessage') return;
|
||||||
chatId,
|
if (payload.button.buttonType === 'approve') {
|
||||||
messageId: message.id,
|
openSuggestedPostApprovalModal({
|
||||||
threadId,
|
|
||||||
button,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleLocalInlineButtonClick = useLastCallback((button: ApiKeyboardButton) => {
|
|
||||||
if (button.type === 'openThread') {
|
|
||||||
openThread({
|
|
||||||
chatId,
|
|
||||||
threadId: messageTopic!.id,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (button.type === 'suggestedMessage') {
|
|
||||||
if (button.buttonType === 'approve') {
|
|
||||||
openSuggestedPostApprovalModal({
|
|
||||||
chatId,
|
|
||||||
messageId: message.id,
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (button.buttonType === 'decline') {
|
|
||||||
openDeclineDialog();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
clickSuggestedMessageButton({
|
|
||||||
chatId,
|
chatId,
|
||||||
messageId: message.id,
|
messageId: message.id,
|
||||||
button,
|
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (payload.button.buttonType === 'decline') {
|
||||||
|
openDeclineDialog();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
clickSuggestedMessageButton({
|
||||||
|
...payload,
|
||||||
|
button: payload.button,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleDeclineReasonChange = useLastCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
const handleDeclineReasonChange = useLastCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
@ -1692,52 +1672,11 @@ const Message = ({
|
|||||||
const shouldRenderSuggestedPostButtons = message.suggestedPostInfo
|
const shouldRenderSuggestedPostButtons = message.suggestedPostInfo
|
||||||
&& !message.isOutgoing && !message.suggestedPostInfo.isAccepted && !message.suggestedPostInfo.isRejected;
|
&& !message.isOutgoing && !message.suggestedPostInfo.isAccepted && !message.suggestedPostInfo.isRejected;
|
||||||
|
|
||||||
const isSuggestedPostExpired = (() => {
|
const isSuggestedPostExpired = useMemo(() => {
|
||||||
if (!message.suggestedPostInfo?.scheduleDate || !minFutureTime) return false;
|
if (!message.suggestedPostInfo?.scheduleDate || !minFutureTime) return false;
|
||||||
const now = getServerTime();
|
const now = getServerTime();
|
||||||
return message.suggestedPostInfo.scheduleDate <= now + minFutureTime;
|
return message.suggestedPostInfo.scheduleDate <= now + minFutureTime;
|
||||||
})();
|
}, [message.suggestedPostInfo, minFutureTime]);
|
||||||
|
|
||||||
const suggestedPostButtons: ApiKeyboardButton[][] | undefined = useMemo(() => {
|
|
||||||
if (!shouldRenderSuggestedPostButtons) return undefined;
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
{
|
|
||||||
type: 'suggestedMessage',
|
|
||||||
buttonType: 'decline',
|
|
||||||
text: lang('SuggestedPostDecline'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'suggestedMessage',
|
|
||||||
buttonType: 'approve',
|
|
||||||
text: lang('SuggestedPostApprove'),
|
|
||||||
disabled: isSuggestedPostExpired,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
{
|
|
||||||
type: 'suggestedMessage',
|
|
||||||
buttonType: 'suggestChanges',
|
|
||||||
text: lang('SuggestedPostSuggestChanges'),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}, [isSuggestedPostExpired, lang, shouldRenderSuggestedPostButtons]);
|
|
||||||
|
|
||||||
const openThreadButtons: ApiKeyboardButton[][] | undefined = useMemo(() => {
|
|
||||||
if (!isBotForum || message.inlineButtons || !messageTopic || !isLastInList ||
|
|
||||||
threadId !== MAIN_THREAD_ID
|
|
||||||
) return undefined;
|
|
||||||
|
|
||||||
return [
|
|
||||||
[{
|
|
||||||
type: 'openThread',
|
|
||||||
text: lang('BotForumContinueThreadButton'),
|
|
||||||
}],
|
|
||||||
];
|
|
||||||
}, [isBotForum, lang, message.inlineButtons, messageTopic, isLastInList, threadId]);
|
|
||||||
|
|
||||||
const additionalInlineButtons = suggestedPostButtons || openThreadButtons;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -1852,12 +1791,36 @@ const Message = ({
|
|||||||
{withQuickReactionButton && quickReactionPosition === 'in-content' && renderQuickReactionButton()}
|
{withQuickReactionButton && quickReactionPosition === 'in-content' && renderQuickReactionButton()}
|
||||||
</div>
|
</div>
|
||||||
{message.inlineButtons && (
|
{message.inlineButtons && (
|
||||||
<InlineButtons inlineButtons={message.inlineButtons} onClick={handleInlineButtonClick} />
|
<InlineButtons message={message} onClick={clickBotInlineButton} />
|
||||||
)}
|
)}
|
||||||
{additionalInlineButtons && (
|
{shouldRenderSuggestedPostButtons && (
|
||||||
<InlineButtons
|
<InlineButtons
|
||||||
inlineButtons={additionalInlineButtons}
|
message={{
|
||||||
onClick={handleLocalInlineButtonClick}
|
...message,
|
||||||
|
inlineButtons: [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: 'suggestedMessage',
|
||||||
|
buttonType: 'decline',
|
||||||
|
text: lang('SuggestedPostDecline'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'suggestedMessage',
|
||||||
|
buttonType: 'approve',
|
||||||
|
text: lang('SuggestedPostApprove'),
|
||||||
|
disabled: isSuggestedPostExpired,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
type: 'suggestedMessage',
|
||||||
|
buttonType: 'suggestChanges',
|
||||||
|
text: lang('SuggestedPostSuggestChanges'),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
],
|
||||||
|
}}
|
||||||
|
onClick={handleSuggestedMessageButton}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{reactionsPosition === 'outside' && !isStoryMention && (
|
{reactionsPosition === 'outside' && !isStoryMention && (
|
||||||
@ -2022,8 +1985,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const hasUnreadReaction = chat?.unreadReactions?.includes(message.id);
|
const hasUnreadReaction = chat?.unreadReactions?.includes(message.id);
|
||||||
|
|
||||||
const hasTopicChip = threadId === MAIN_THREAD_ID && chat?.isForum && !chat.isBotForum && isFirstInGroup;
|
const hasTopicChip = threadId === MAIN_THREAD_ID && chat?.isForum && isFirstInGroup;
|
||||||
const messageTopic = selectTopicFromMessage(global, message);
|
const messageTopic = hasTopicChip ? selectTopicFromMessage(global, message) : undefined;
|
||||||
|
|
||||||
const chatTranslations = selectChatTranslations(global, chatId);
|
const chatTranslations = selectChatTranslations(global, chatId);
|
||||||
|
|
||||||
@ -2084,7 +2047,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isForwarding,
|
isForwarding,
|
||||||
reactionMessage,
|
reactionMessage,
|
||||||
isChatWithSelf,
|
isChatWithSelf,
|
||||||
isBotForum: chat?.isBotForum,
|
|
||||||
isRepliesChat: isSystemBotChat,
|
isRepliesChat: isSystemBotChat,
|
||||||
isAnonymousForwards,
|
isAnonymousForwards,
|
||||||
isChannel,
|
isChannel,
|
||||||
|
|||||||
@ -96,7 +96,6 @@ export function renderPeerLink(peerId: string | undefined, text: string, asPrevi
|
|||||||
getActions().openChat({ id: peerId });
|
getActions().openChat({ id: peerId });
|
||||||
}}
|
}}
|
||||||
// box-decoration-break: clone; is broken when child has `dir` attribute
|
// box-decoration-break: clone; is broken when child has `dir` attribute
|
||||||
// https://bugs.webkit.org/show_bug.cgi?id=296990
|
|
||||||
withMultilineFix={IS_SAFARI}
|
withMultilineFix={IS_SAFARI}
|
||||||
>
|
>
|
||||||
{renderText(text)}
|
{renderText(text)}
|
||||||
@ -130,23 +129,6 @@ export function renderMessageLink(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderTopicLink(chatId: string, topicId: number, content: TeactNode, asPreview?: boolean) {
|
|
||||||
if (asPreview) return content;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Link
|
|
||||||
className={styles.topicLink}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
getActions().openThread({ chatId, threadId: topicId });
|
|
||||||
}}
|
|
||||||
withMultilineFix={IS_SAFARI}
|
|
||||||
>
|
|
||||||
{content}
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getCallMessageKey(action: ApiMessageActionPhoneCall, isOutgoing: boolean): RegularLangKey {
|
export function getCallMessageKey(action: ApiMessageActionPhoneCall, isOutgoing: boolean): RegularLangKey {
|
||||||
const isMissed = action.reason === 'missed';
|
const isMissed = action.reason === 'missed';
|
||||||
const isCancelled = action.reason === 'busy' || action.duration === undefined;
|
const isCancelled = action.reason === 'busy' || action.duration === undefined;
|
||||||
|
|||||||
@ -42,15 +42,14 @@ export type TransitionProps = {
|
|||||||
slideClassName?: string;
|
slideClassName?: string;
|
||||||
withSwipeControl?: boolean;
|
withSwipeControl?: boolean;
|
||||||
isBlockingAnimation?: boolean;
|
isBlockingAnimation?: boolean;
|
||||||
|
onStart?: NoneToVoidFunction;
|
||||||
|
onStop?: NoneToVoidFunction;
|
||||||
|
onScroll?: NoneToVoidFunction;
|
||||||
|
onMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
||||||
children: React.ReactNode | ChildrenFn;
|
children: React.ReactNode | ChildrenFn;
|
||||||
'data-tauri-drag-region'?: true;
|
'data-tauri-drag-region'?: true;
|
||||||
contentSelector?: string;
|
contentSelector?: string;
|
||||||
restoreHeightKey?: number;
|
restoreHeightKey?: number;
|
||||||
onStart?: NoneToVoidFunction;
|
|
||||||
onStop?: NoneToVoidFunction;
|
|
||||||
onScroll?: NoneToVoidFunction;
|
|
||||||
onWheel?: (e: React.WheelEvent<HTMLDivElement>) => void;
|
|
||||||
onMouseDown?: (e: React.MouseEvent<HTMLDivElement>) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const FALLBACK_ANIMATION_END = 1000;
|
const FALLBACK_ANIMATION_END = 1000;
|
||||||
@ -88,15 +87,14 @@ function Transition({
|
|||||||
slideClassName,
|
slideClassName,
|
||||||
withSwipeControl,
|
withSwipeControl,
|
||||||
isBlockingAnimation,
|
isBlockingAnimation,
|
||||||
children,
|
|
||||||
'data-tauri-drag-region': dataTauriDragRegion,
|
|
||||||
contentSelector,
|
|
||||||
restoreHeightKey,
|
|
||||||
onStart,
|
onStart,
|
||||||
onStop,
|
onStop,
|
||||||
onScroll,
|
onScroll,
|
||||||
onMouseDown,
|
onMouseDown,
|
||||||
onWheel,
|
children,
|
||||||
|
'data-tauri-drag-region': dataTauriDragRegion,
|
||||||
|
contentSelector,
|
||||||
|
restoreHeightKey,
|
||||||
}: TransitionProps) {
|
}: TransitionProps) {
|
||||||
const currentKeyRef = useRef<number>();
|
const currentKeyRef = useRef<number>();
|
||||||
// No need for a container to update on change
|
// No need for a container to update on change
|
||||||
@ -413,7 +411,6 @@ function Transition({
|
|||||||
data-tauri-drag-region={dataTauriDragRegion}
|
data-tauri-drag-region={dataTauriDragRegion}
|
||||||
onScroll={onScroll}
|
onScroll={onScroll}
|
||||||
onMouseDown={onMouseDown}
|
onMouseDown={onMouseDown}
|
||||||
onWheel={onWheel}
|
|
||||||
>
|
>
|
||||||
{contents}
|
{contents}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -338,7 +338,6 @@ export const REPLIES_USER_ID = '1271266957'; // TODO For Test connection ID must
|
|||||||
export const VERIFICATION_CODES_USER_ID = '489000';
|
export const VERIFICATION_CODES_USER_ID = '489000';
|
||||||
export const ANONYMOUS_USER_ID = '2666000';
|
export const ANONYMOUS_USER_ID = '2666000';
|
||||||
export const RESTRICTED_EMOJI_SET_ID = '7173162320003080';
|
export const RESTRICTED_EMOJI_SET_ID = '7173162320003080';
|
||||||
export const LOCAL_MESSAGES_LIMIT = 1e6; // 1M
|
|
||||||
export const CHANNEL_ID_BASE = 10n ** 12n;
|
export const CHANNEL_ID_BASE = 10n ** 12n;
|
||||||
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;
|
||||||
|
|||||||
@ -2386,6 +2386,9 @@ addActionHandler('processAttachBotParameters', async (global, actions, payload):
|
|||||||
addActionHandler('loadTopics', async (global, actions, payload): Promise<void> => {
|
addActionHandler('loadTopics', async (global, actions, payload): Promise<void> => {
|
||||||
if (selectIsCurrentUserFrozen(global)) return;
|
if (selectIsCurrentUserFrozen(global)) return;
|
||||||
const { chatId, force } = payload;
|
const { chatId, force } = payload;
|
||||||
|
if (selectIsCurrentUserFrozen(global)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) return;
|
if (!chat) return;
|
||||||
|
|
||||||
|
|||||||
@ -148,7 +148,6 @@ import {
|
|||||||
selectTabState,
|
selectTabState,
|
||||||
selectThreadIdFromMessage,
|
selectThreadIdFromMessage,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
selectThreadParam,
|
|
||||||
selectTopic,
|
selectTopic,
|
||||||
selectTranslationLanguage,
|
selectTranslationLanguage,
|
||||||
selectUser,
|
selectUser,
|
||||||
@ -424,16 +423,6 @@ addActionHandler('sendMessage', async (global, actions, payload): Promise<void>
|
|||||||
suggestedMedia = suggestedMessage.content;
|
suggestedMedia = suggestedMessage.content;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chat.isBotForum && threadId === MAIN_THREAD_ID && replyInfo?.type === 'message') {
|
|
||||||
const replyMessage = selectChatMessage(global, chatId!, replyInfo.replyToMsgId);
|
|
||||||
const replyThreadId = replyMessage && selectThreadIdFromMessage(global, replyMessage);
|
|
||||||
actions.openThread({
|
|
||||||
chatId: chatId!,
|
|
||||||
threadId: replyThreadId || replyInfo?.replyToTopId || replyInfo?.replyToMsgId,
|
|
||||||
tabId,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const params: SendMessageParams = {
|
const params: SendMessageParams = {
|
||||||
...payload,
|
...payload,
|
||||||
chat,
|
chat,
|
||||||
@ -453,24 +442,6 @@ addActionHandler('sendMessage', async (global, actions, payload): Promise<void>
|
|||||||
actions.clearWebPagePreview({ tabId });
|
actions.clearWebPagePreview({ tabId });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new bot forum topic
|
|
||||||
if (chat.isBotForum && threadId === MAIN_THREAD_ID && replyInfo?.type !== 'message') {
|
|
||||||
const baseTitle = params.text ?? getTranslationFn()('BotForumTopicTitlePlaceholder');
|
|
||||||
const title = baseTitle.length > 12 ? `${baseTitle.slice(0, 12)}...` : baseTitle;
|
|
||||||
const topic = await callApi('createTopic', {
|
|
||||||
chat,
|
|
||||||
title,
|
|
||||||
isTitleMissing: true,
|
|
||||||
sendAs: params.sendAs,
|
|
||||||
});
|
|
||||||
if (topic) {
|
|
||||||
params.replyInfo = params.replyInfo?.type === 'message'
|
|
||||||
? { ...params.replyInfo, replyToTopId: topic }
|
|
||||||
: { type: 'message', replyToMsgId: topic, replyToTopId: topic };
|
|
||||||
getActions().openThread({ chatId: chat.id, threadId: topic });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isSingle = (!payload.attachments || payload.attachments.length <= 1) && !isForwarding;
|
const isSingle = (!payload.attachments || payload.attachments.length <= 1) && !isForwarding;
|
||||||
const isGrouped = !isSingle && payload.shouldGroupMessages;
|
const isGrouped = !isSingle && payload.shouldGroupMessages;
|
||||||
const localMessages: SendMessageParams[] = [];
|
const localMessages: SendMessageParams[] = [];
|
||||||
@ -1262,7 +1233,6 @@ addActionHandler('markMessageListRead', (global, actions, payload): ActionReturn
|
|||||||
|
|
||||||
const viewportIds = selectViewportIds(global, chatId, threadId, tabId);
|
const viewportIds = selectViewportIds(global, chatId, threadId, tabId);
|
||||||
const minId = selectFirstUnreadId(global, chatId, threadId);
|
const minId = selectFirstUnreadId(global, chatId, threadId);
|
||||||
const topic = selectTopic(global, chatId, threadId);
|
|
||||||
|
|
||||||
if (threadId !== MAIN_THREAD_ID && !chat.isForum) {
|
if (threadId !== MAIN_THREAD_ID && !chat.isForum) {
|
||||||
global = updateThreadInfo(global, chatId, threadId, {
|
global = updateThreadInfo(global, chatId, threadId, {
|
||||||
@ -1271,7 +1241,7 @@ addActionHandler('markMessageListRead', (global, actions, payload): ActionReturn
|
|||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!viewportIds || !minId || (!chat.unreadCount && !topic?.unreadCount)) {
|
if (!viewportIds || !minId || !chat.unreadCount) {
|
||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1280,17 +1250,17 @@ addActionHandler('markMessageListRead', (global, actions, payload): ActionReturn
|
|||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const topic = selectTopic(global, chatId, threadId);
|
||||||
if (chat.isForum && topic) {
|
if (chat.isForum && topic) {
|
||||||
global = updateThreadInfo(global, chatId, threadId, {
|
global = updateThreadInfo(global, chatId, threadId, {
|
||||||
lastReadInboxMessageId: maxId,
|
lastReadInboxMessageId: maxId,
|
||||||
});
|
});
|
||||||
const newTopicUnreadCount = Math.max(0, topic.unreadCount - readCount);
|
const newTopicUnreadCount = Math.max(0, topic.unreadCount - readCount);
|
||||||
if (newTopicUnreadCount === 0 && !chat.isBotForum && chat.unreadCount) {
|
if (newTopicUnreadCount === 0) {
|
||||||
global = updateChat(global, chatId, {
|
global = updateChat(global, chatId, {
|
||||||
unreadCount: Math.max(0, chat.unreadCount - 1),
|
unreadCount: Math.max(0, chat.unreadCount - 1),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return updateTopic(global, chatId, Number(threadId), {
|
return updateTopic(global, chatId, Number(threadId), {
|
||||||
unreadCount: newTopicUnreadCount,
|
unreadCount: newTopicUnreadCount,
|
||||||
});
|
});
|
||||||
@ -1298,7 +1268,7 @@ addActionHandler('markMessageListRead', (global, actions, payload): ActionReturn
|
|||||||
|
|
||||||
return updateChat(global, chatId, {
|
return updateChat(global, chatId, {
|
||||||
lastReadInboxMessageId: maxId,
|
lastReadInboxMessageId: maxId,
|
||||||
unreadCount: Math.max(0, (chat.unreadCount || 0) - readCount),
|
unreadCount: Math.max(0, chat.unreadCount - readCount),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1772,13 +1742,9 @@ async function loadViewportMessages<T extends GlobalState>(
|
|||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
|
|
||||||
const localTypingDrafts = selectThreadParam(global, chatId, threadId, 'typingDraftIdByRandomId');
|
|
||||||
const typingDraftMessages = localTypingDrafts ? Object.values(localTypingDrafts)
|
|
||||||
.map((id) => selectChatMessage(global, chatId, id))
|
|
||||||
.filter(Boolean) : [];
|
|
||||||
const localMessages = chatId === SERVICE_NOTIFICATIONS_USER_ID
|
const localMessages = chatId === SERVICE_NOTIFICATIONS_USER_ID
|
||||||
? global.serviceNotifications.filter(({ isDeleted }) => !isDeleted).map(({ message }) => message)
|
? global.serviceNotifications.filter(({ isDeleted }) => !isDeleted).map(({ message }) => message)
|
||||||
: typingDraftMessages;
|
: [];
|
||||||
const allMessages = ([] as ApiMessage[]).concat(messages, localMessages);
|
const allMessages = ([] as ApiMessage[]).concat(messages, localMessages);
|
||||||
const byId = buildCollectionByKey(allMessages, 'id');
|
const byId = buildCollectionByKey(allMessages, 'id');
|
||||||
const ids = Object.keys(byId).map(Number);
|
const ids = Object.keys(byId).map(Number);
|
||||||
|
|||||||
@ -156,8 +156,7 @@ async function loadAndReplaceMessages<T extends GlobalState>(global: T, actions:
|
|||||||
currentChatId,
|
currentChatId,
|
||||||
activeThreadId,
|
activeThreadId,
|
||||||
),
|
),
|
||||||
activeThreadId !== MAIN_THREAD_ID && !currentChat.isForum
|
activeThreadId !== MAIN_THREAD_ID && !getIsSavedDialog(currentChat.id, activeThreadId, global.currentUserId)
|
||||||
&& !getIsSavedDialog(currentChat.id, activeThreadId, global.currentUserId)
|
|
||||||
? callApi('fetchDiscussionMessage', {
|
? callApi('fetchDiscussionMessage', {
|
||||||
chat: currentChat,
|
chat: currentChat,
|
||||||
messageId: Number(activeThreadId),
|
messageId: Number(activeThreadId),
|
||||||
|
|||||||
@ -24,7 +24,6 @@ import {
|
|||||||
updateChatFullInfo,
|
updateChatFullInfo,
|
||||||
updateChatListType,
|
updateChatListType,
|
||||||
updatePeerStoriesHidden,
|
updatePeerStoriesHidden,
|
||||||
updateThreadInfo,
|
|
||||||
updateTopic,
|
updateTopic,
|
||||||
} from '../../reducers';
|
} from '../../reducers';
|
||||||
import { updateUnreadReactions } from '../../reducers/reactions';
|
import { updateUnreadReactions } from '../../reducers/reactions';
|
||||||
@ -149,21 +148,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'updateChatInbox': {
|
case 'updateChatInbox': {
|
||||||
const { id, threadId, lastReadInboxMessageId, unreadCount } = update;
|
return updateChat(global, update.id, update.chat);
|
||||||
const chat = selectChat(global, id);
|
|
||||||
if (chat?.isBotForum && threadId) {
|
|
||||||
global = updateTopic(global, id, Number(threadId), {
|
|
||||||
unreadCount,
|
|
||||||
});
|
|
||||||
return updateThreadInfo(global, id, threadId, {
|
|
||||||
lastReadInboxMessageId,
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return updateChat(global, id, {
|
|
||||||
lastReadInboxMessageId,
|
|
||||||
unreadCount,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'updateChatTypingStatus': {
|
case 'updateChatTypingStatus': {
|
||||||
@ -215,8 +200,6 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
unreadCount: topic.unreadCount ? topic.unreadCount + 1 : 1,
|
unreadCount: topic.unreadCount ? topic.unreadCount + 1 : 1,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Replace draft with new message
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|||||||
@ -20,15 +20,9 @@ import { getMessageKey, isLocalMessageId } from '../../../util/keys/messageKey';
|
|||||||
import { notifyAboutMessage } from '../../../util/notifications';
|
import { notifyAboutMessage } from '../../../util/notifications';
|
||||||
import { onTickEnd } from '../../../util/schedulers';
|
import { onTickEnd } from '../../../util/schedulers';
|
||||||
import { getServerTime } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
import { callApi } from '../../../api/gramjs';
|
|
||||||
import {
|
import {
|
||||||
addPaidReaction,
|
addPaidReaction,
|
||||||
checkIfHasUnreadReactions,
|
checkIfHasUnreadReactions, getIsSavedDialog, getMessageContent, getMessageText, isActionMessage,
|
||||||
createApiMessageFromTypingDraft,
|
|
||||||
getIsSavedDialog,
|
|
||||||
getMessageContent,
|
|
||||||
getMessageText,
|
|
||||||
isActionMessage,
|
|
||||||
isMessageLocal,
|
isMessageLocal,
|
||||||
} from '../../helpers';
|
} from '../../helpers';
|
||||||
import { getMessageReplyInfo, getStoryReplyInfo } from '../../helpers/replies';
|
import { getMessageReplyInfo, getStoryReplyInfo } from '../../helpers/replies';
|
||||||
@ -91,11 +85,9 @@ import {
|
|||||||
selectScheduledIds,
|
selectScheduledIds,
|
||||||
selectScheduledMessage,
|
selectScheduledMessage,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
selectThread,
|
|
||||||
selectThreadByMessage,
|
selectThreadByMessage,
|
||||||
selectThreadIdFromMessage,
|
selectThreadIdFromMessage,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
selectThreadParam,
|
|
||||||
selectTopic,
|
selectTopic,
|
||||||
selectTopicFromMessage,
|
selectTopicFromMessage,
|
||||||
selectViewportIds,
|
selectViewportIds,
|
||||||
@ -186,14 +178,6 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
actions.reportMessageDelivery({ chatId, messageId: id });
|
actions.reportMessageDelivery({ chatId, messageId: id });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chat?.isBotForum && !newMessage.isOutgoing && !isLocal) {
|
|
||||||
const threadId = selectThreadIdFromMessage(global, newMessage);
|
|
||||||
const typingDraftStore = selectThreadParam(global, chatId, threadId, 'typingDraftIdByRandomId');
|
|
||||||
const localDraftIds = Object.values(typingDraftStore || {});
|
|
||||||
global = deleteChatMessages(global, chatId, localDraftIds);
|
|
||||||
global = replaceThreadParam(global, chatId, threadId, 'typingDraftIdByRandomId', undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
// Reload dialogs if chat is not present in the list
|
// Reload dialogs if chat is not present in the list
|
||||||
@ -929,78 +913,6 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
global = updateMessageTranslations(global, chatId, messageIds, toLanguageCode, []);
|
global = updateMessageTranslations(global, chatId, messageIds, toLanguageCode, []);
|
||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'updateChatTypingDraft': {
|
|
||||||
const { id, chatId, threadId = MAIN_THREAD_ID, text } = update;
|
|
||||||
const thread = selectThread(global, chatId, threadId);
|
|
||||||
if (!thread) return undefined;
|
|
||||||
|
|
||||||
let typingDraftStore = selectThreadParam(global, chatId, threadId, 'typingDraftIdByRandomId');
|
|
||||||
const messageId = typingDraftStore?.[id];
|
|
||||||
|
|
||||||
const isUpdatingDraft = Boolean(messageId);
|
|
||||||
const updatingMessage = isUpdatingDraft ? selectChatMessage(global, chatId, messageId) : undefined;
|
|
||||||
|
|
||||||
const rescheduleDraftRemoval = () => {
|
|
||||||
// Clear typing draft after timeout
|
|
||||||
setTimeout(() => {
|
|
||||||
global = getGlobal();
|
|
||||||
const currentTypingDraftStore = selectThreadParam(global, chatId, threadId, 'typingDraftIdByRandomId');
|
|
||||||
if (currentTypingDraftStore?.[id]) {
|
|
||||||
const currentMessageId = currentTypingDraftStore[id];
|
|
||||||
const currentMessage = selectChatMessage(global, chatId, currentMessageId);
|
|
||||||
// Already deleted or replaced with a new message
|
|
||||||
if (!currentMessage || getServerTime() - currentMessage.editDate! < global.appConfig.typingDraftTtl) return;
|
|
||||||
|
|
||||||
const newTypingDraftIds = omit(currentTypingDraftStore, [id]);
|
|
||||||
global = replaceThreadParam(global, chatId, threadId, 'typingDraftIdByRandomId', newTypingDraftIds);
|
|
||||||
global = deleteChatMessages(global, chatId, [currentMessageId]);
|
|
||||||
setGlobal(global);
|
|
||||||
}
|
|
||||||
}, global.appConfig.typingDraftTtl * 1000);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (isUpdatingDraft && updatingMessage) {
|
|
||||||
global = updateChatMessage(global, chatId, messageId, {
|
|
||||||
content: {
|
|
||||||
text,
|
|
||||||
},
|
|
||||||
editDate: getServerTime(),
|
|
||||||
});
|
|
||||||
rescheduleDraftRemoval();
|
|
||||||
return global;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Let worker know that we have new local message
|
|
||||||
callApi('incrementLocalMessagesCounter');
|
|
||||||
|
|
||||||
const lastMessageId = selectChatLastMessageId(global, chatId);
|
|
||||||
|
|
||||||
const newMessage = createApiMessageFromTypingDraft({
|
|
||||||
lastMessageId: lastMessageId || 0,
|
|
||||||
chatId,
|
|
||||||
threadId,
|
|
||||||
text,
|
|
||||||
});
|
|
||||||
|
|
||||||
actions.apiUpdate({
|
|
||||||
'@type': 'newMessage',
|
|
||||||
chatId,
|
|
||||||
id: newMessage.id,
|
|
||||||
message: newMessage,
|
|
||||||
});
|
|
||||||
|
|
||||||
typingDraftStore = {
|
|
||||||
...typingDraftStore,
|
|
||||||
[id]: newMessage.id,
|
|
||||||
};
|
|
||||||
global = replaceThreadParam(global, chatId, threadId, 'typingDraftIdByRandomId', typingDraftStore);
|
|
||||||
|
|
||||||
rescheduleDraftRemoval();
|
|
||||||
|
|
||||||
return global;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -500,11 +500,6 @@ addActionHandler('scrollMessageListToBottom', (global, actions, payload): Action
|
|||||||
cancelScrollBlockingAnimation();
|
cancelScrollBlockingAnimation();
|
||||||
}
|
}
|
||||||
|
|
||||||
const isViewportNewest = selectIsViewportNewest(global, chatId, threadId, tabId);
|
|
||||||
if (isViewportNewest) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
actions.loadViewportMessages({
|
actions.loadViewportMessages({
|
||||||
chatId,
|
chatId,
|
||||||
threadId,
|
threadId,
|
||||||
|
|||||||
@ -665,7 +665,7 @@ function reduceMessages<T extends GlobalState>(global: T): GlobalState['messages
|
|||||||
}, {} as GlobalState['messages']['byChatId'][string]['threadsById']);
|
}, {} as GlobalState['messages']['byChatId'][string]['threadsById']);
|
||||||
|
|
||||||
const cleanedById = Object.values(byId).reduce((acc, message) => {
|
const cleanedById = Object.values(byId).reduce((acc, message) => {
|
||||||
if (!message || message.isTypingDraft) return acc;
|
if (!message) return acc;
|
||||||
|
|
||||||
let cleanedMessage = omitLocalMedia(message);
|
let cleanedMessage = omitLocalMedia(message);
|
||||||
cleanedMessage = omitLocalPaidReactions(cleanedMessage);
|
cleanedMessage = omitLocalPaidReactions(cleanedMessage);
|
||||||
|
|||||||
@ -9,8 +9,7 @@ import type {
|
|||||||
ApiTypeStory,
|
ApiTypeStory,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import type {
|
import type {
|
||||||
ApiFormattedText,
|
ApiPoll, ApiWebPage, MediaContainer, StatefulMediaContent,
|
||||||
ApiPoll, ApiReplyInfo, ApiWebPage, MediaContainer, StatefulMediaContent,
|
|
||||||
} from '../../api/types/messages';
|
} from '../../api/types/messages';
|
||||||
import type { ThreadId } from '../../types';
|
import type { ThreadId } from '../../types';
|
||||||
import type { LangFn } from '../../util/localization';
|
import type { LangFn } from '../../util/localization';
|
||||||
@ -18,7 +17,6 @@ import type { GlobalState } from '../types';
|
|||||||
import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../api/types';
|
import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../api/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
LOCAL_MESSAGES_LIMIT,
|
|
||||||
LOTTIE_STICKER_MIME_TYPE,
|
LOTTIE_STICKER_MIME_TYPE,
|
||||||
RE_LINK_TEMPLATE,
|
RE_LINK_TEMPLATE,
|
||||||
SERVICE_NOTIFICATIONS_USER_ID,
|
SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
@ -40,11 +38,6 @@ import { getMainUsername } from './users';
|
|||||||
|
|
||||||
const RE_LINK = new RegExp(RE_LINK_TEMPLATE, 'i');
|
const RE_LINK = new RegExp(RE_LINK_TEMPLATE, 'i');
|
||||||
|
|
||||||
let uiLocalMessageCounter = 0;
|
|
||||||
function getNextLocalMessageId(lastMessageId = 0) {
|
|
||||||
return lastMessageId + (++uiLocalMessageCounter / LOCAL_MESSAGES_LIMIT);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getMessageHtmlId(messageId: number, index?: number) {
|
export function getMessageHtmlId(messageId: number, index?: number) {
|
||||||
const parts = ['message', messageId.toString().replace('.', '-'), index].filter(Boolean);
|
const parts = ['message', messageId.toString().replace('.', '-'), index].filter(Boolean);
|
||||||
return parts.join('-');
|
return parts.join('-');
|
||||||
@ -511,38 +504,3 @@ export function getSuggestedChangesActionText(
|
|||||||
withMarkdown: true,
|
withMarkdown: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createApiMessageFromTypingDraft({
|
|
||||||
lastMessageId,
|
|
||||||
chatId,
|
|
||||||
threadId,
|
|
||||||
text,
|
|
||||||
}: {
|
|
||||||
lastMessageId: number;
|
|
||||||
chatId: string;
|
|
||||||
threadId: ThreadId;
|
|
||||||
text: ApiFormattedText;
|
|
||||||
}): ApiMessage {
|
|
||||||
const localId = getNextLocalMessageId(lastMessageId);
|
|
||||||
|
|
||||||
const replyInfo: ApiReplyInfo | undefined = typeof threadId === 'number' && threadId !== MAIN_THREAD_ID ? {
|
|
||||||
type: 'message',
|
|
||||||
replyToMsgId: threadId,
|
|
||||||
replyToTopId: threadId,
|
|
||||||
isForumTopic: true,
|
|
||||||
} : undefined;
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: localId,
|
|
||||||
chatId,
|
|
||||||
replyInfo,
|
|
||||||
isOutgoing: false,
|
|
||||||
date: getServerTime(),
|
|
||||||
content: {
|
|
||||||
text,
|
|
||||||
},
|
|
||||||
isSilent: true,
|
|
||||||
isTypingDraft: true,
|
|
||||||
editDate: getServerTime(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import type {
|
import type {
|
||||||
ApiFormattedText,
|
|
||||||
ApiMessage, ApiPoll, ApiPollResult, ApiQuickReply, ApiSponsoredMessage, ApiThreadInfo,
|
ApiMessage, ApiPoll, ApiPollResult, ApiQuickReply, ApiSponsoredMessage, ApiThreadInfo,
|
||||||
ApiWebPage,
|
ApiWebPage,
|
||||||
ApiWebPageFull,
|
ApiWebPageFull,
|
||||||
@ -47,7 +46,6 @@ import {
|
|||||||
selectTabState,
|
selectTabState,
|
||||||
selectThreadIdFromMessage,
|
selectThreadIdFromMessage,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
selectThreadParam,
|
|
||||||
selectViewportIds,
|
selectViewportIds,
|
||||||
selectWebPage,
|
selectWebPage,
|
||||||
} from '../selectors';
|
} from '../selectors';
|
||||||
@ -1066,24 +1064,3 @@ export function updatePollVote<T extends GlobalState>(
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function updateTypingDraft<T extends GlobalState>(
|
|
||||||
global: T,
|
|
||||||
chatId: string,
|
|
||||||
threadId: ThreadId | undefined = MAIN_THREAD_ID,
|
|
||||||
randomId: string,
|
|
||||||
text: ApiFormattedText,
|
|
||||||
) {
|
|
||||||
const typingDraftStore = selectThreadParam(global, chatId, threadId, 'typingDraftIdByRandomId');
|
|
||||||
const messageId = typingDraftStore?.[randomId];
|
|
||||||
if (!messageId) {
|
|
||||||
return global;
|
|
||||||
}
|
|
||||||
|
|
||||||
global = updateChatMessage(global, chatId, messageId, {
|
|
||||||
content: {
|
|
||||||
text,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return global;
|
|
||||||
}
|
|
||||||
|
|||||||
@ -127,7 +127,7 @@ export function updatePeerPhotos<T extends GlobalState>(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasFallbackPhoto = fallbackPhoto && currentPhotos.photos.at(-1)?.id === fallbackPhoto.id;
|
const hasFallbackPhoto = currentPhotos.photos.at(-1).id === fallbackPhoto?.id;
|
||||||
const currentPhotoArray = hasFallbackPhoto ? currentPhotos.photos.slice(0, -1) : currentPhotos.photos;
|
const currentPhotoArray = hasFallbackPhoto ? currentPhotos.photos.slice(0, -1) : currentPhotos.photos;
|
||||||
|
|
||||||
const photos = uniqueByField([...currentPhotoArray, ...newPhotos, fallbackPhoto].filter(Boolean), 'id');
|
const photos = uniqueByField([...currentPhotoArray, ...newPhotos, fallbackPhoto].filter(Boolean), 'id');
|
||||||
|
|||||||
@ -551,8 +551,7 @@ export function selectCanDeleteTopic<T extends GlobalState>(global: T, chatId: s
|
|||||||
|
|
||||||
if (topicId === GENERAL_TOPIC_ID) return false;
|
if (topicId === GENERAL_TOPIC_ID) return false;
|
||||||
|
|
||||||
return chat.isBotForum
|
return chat.isCreator
|
||||||
|| chat.isCreator
|
|
||||||
|| getHasAdminRight(chat, 'deleteMessages')
|
|| getHasAdminRight(chat, 'deleteMessages')
|
||||||
|| (chat.isForum
|
|| (chat.isForum
|
||||||
&& selectCanDeleteOwnerTopic(global, chat.id, topicId));
|
&& selectCanDeleteOwnerTopic(global, chat.id, topicId));
|
||||||
@ -1560,7 +1559,7 @@ export function selectTopicLink<T extends GlobalState>(
|
|||||||
global: T, chatId: string, topicId?: ThreadId,
|
global: T, chatId: string, topicId?: ThreadId,
|
||||||
) {
|
) {
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat || !chat.isForum || chat.isBotForum) {
|
if (!chat || !chat?.isForum) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -22,6 +22,7 @@ import type {
|
|||||||
ApiInputSavedStarGift,
|
ApiInputSavedStarGift,
|
||||||
ApiInputSuggestedPostInfo,
|
ApiInputSuggestedPostInfo,
|
||||||
ApiKeyboardButton,
|
ApiKeyboardButton,
|
||||||
|
ApiKeyboardButtonSuggestedMessage,
|
||||||
ApiLimitTypeWithModal,
|
ApiLimitTypeWithModal,
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiMessageEntity,
|
ApiMessageEntity,
|
||||||
@ -59,7 +60,6 @@ import type {
|
|||||||
ApiUser,
|
ApiUser,
|
||||||
ApiVideo,
|
ApiVideo,
|
||||||
BotsPrivacyType,
|
BotsPrivacyType,
|
||||||
KeyboardButtonSuggestedMessage,
|
|
||||||
LinkContext,
|
LinkContext,
|
||||||
PrivacyVisibility,
|
PrivacyVisibility,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
@ -2084,7 +2084,7 @@ export interface ActionPayloads {
|
|||||||
clickSuggestedMessageButton: {
|
clickSuggestedMessageButton: {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
messageId: number;
|
messageId: number;
|
||||||
button: KeyboardButtonSuggestedMessage;
|
button: ApiKeyboardButtonSuggestedMessage;
|
||||||
} & WithTabId;
|
} & WithTabId;
|
||||||
|
|
||||||
switchBotInline: {
|
switchBotInline: {
|
||||||
|
|||||||
53
src/hooks/useResizeMessageObserver.ts
Normal file
53
src/hooks/useResizeMessageObserver.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import type { ElementRef } from '../lib/teact/teact';
|
||||||
|
import { beginHeavyAnimation, useRef } from '../lib/teact/teact';
|
||||||
|
import { getActions } from '../global';
|
||||||
|
|
||||||
|
import { isAnimatingScroll } from '../util/animateScroll';
|
||||||
|
import useLastCallback from './useLastCallback';
|
||||||
|
import useResizeObserver from './useResizeObserver';
|
||||||
|
import useThrottledCallback from './useThrottledCallback';
|
||||||
|
|
||||||
|
const BOTTOM_FOCUS_SCROLL_THRESHOLD = 5;
|
||||||
|
const THROTTLE_MS = 300;
|
||||||
|
const RESIZE_ANIMATION_DURATION = 400;
|
||||||
|
|
||||||
|
function useMessageResizeObserver(
|
||||||
|
ref: ElementRef<HTMLElement> | undefined,
|
||||||
|
shouldFocusOnResize = false,
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
scrollMessageListToBottom,
|
||||||
|
} = getActions();
|
||||||
|
const messageHeightRef = useRef(0);
|
||||||
|
|
||||||
|
const handleResize = useLastCallback(
|
||||||
|
(entry) => {
|
||||||
|
const lastHeight = messageHeightRef.current;
|
||||||
|
|
||||||
|
const newHeight = entry.contentRect.height;
|
||||||
|
messageHeightRef.current = newHeight;
|
||||||
|
|
||||||
|
if (isAnimatingScroll() || !lastHeight || newHeight <= lastHeight) return;
|
||||||
|
|
||||||
|
const container = entry.target.closest('.MessageList');
|
||||||
|
if (!container) return;
|
||||||
|
|
||||||
|
beginHeavyAnimation(RESIZE_ANIMATION_DURATION);
|
||||||
|
|
||||||
|
const resizeDiff = newHeight - lastHeight;
|
||||||
|
const { offsetHeight, scrollHeight, scrollTop } = container;
|
||||||
|
const currentScrollBottom = Math.round(scrollHeight - scrollTop - offsetHeight);
|
||||||
|
const previousScrollBottom = currentScrollBottom - resizeDiff;
|
||||||
|
|
||||||
|
if (previousScrollBottom <= BOTTOM_FOCUS_SCROLL_THRESHOLD) {
|
||||||
|
scrollMessageListToBottom();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const throttledResize = useThrottledCallback(handleResize, [handleResize], THROTTLE_MS, false);
|
||||||
|
|
||||||
|
useResizeObserver(ref, throttledResize, !shouldFocusOnResize);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useMessageResizeObserver;
|
||||||
@ -157,5 +157,4 @@ export const DEFAULT_APP_CONFIG: ApiAppConfig = {
|
|||||||
'fragment.com',
|
'fragment.com',
|
||||||
'translations.telegram.org',
|
'translations.telegram.org',
|
||||||
],
|
],
|
||||||
typingDraftTtl: 10,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -3,8 +3,8 @@
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-display: block;
|
font-display: block;
|
||||||
src: url("./icons.woff2?33f6294c2f4a2ffb1e77473fb35bc539") format("woff2"),
|
src: url("./icons.woff2?cba4fd3111b01885aeeafb1a9ec56b6b") format("woff2"),
|
||||||
url("./icons.woff?33f6294c2f4a2ffb1e77473fb35bc539") format("woff");
|
url("./icons.woff?cba4fd3111b01885aeeafb1a9ec56b6b") format("woff");
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-char::before {
|
.icon-char::before {
|
||||||
@ -810,105 +810,102 @@ url("./icons.woff?33f6294c2f4a2ffb1e77473fb35bc539") format("woff");
|
|||||||
.icon-toncoin::before {
|
.icon-toncoin::before {
|
||||||
content: "\f207";
|
content: "\f207";
|
||||||
}
|
}
|
||||||
.icon-topic-new::before {
|
.icon-trade::before {
|
||||||
content: "\f208";
|
content: "\f208";
|
||||||
}
|
}
|
||||||
.icon-trade::before {
|
.icon-transcribe::before {
|
||||||
content: "\f209";
|
content: "\f209";
|
||||||
}
|
}
|
||||||
.icon-transcribe::before {
|
.icon-truck::before {
|
||||||
content: "\f20a";
|
content: "\f20a";
|
||||||
}
|
}
|
||||||
.icon-truck::before {
|
.icon-unarchive::before {
|
||||||
content: "\f20b";
|
content: "\f20b";
|
||||||
}
|
}
|
||||||
.icon-unarchive::before {
|
.icon-underlined::before {
|
||||||
content: "\f20c";
|
content: "\f20c";
|
||||||
}
|
}
|
||||||
.icon-underlined::before {
|
.icon-understood::before {
|
||||||
content: "\f20d";
|
content: "\f20d";
|
||||||
}
|
}
|
||||||
.icon-understood::before {
|
.icon-unique-profile::before {
|
||||||
content: "\f20e";
|
content: "\f20e";
|
||||||
}
|
}
|
||||||
.icon-unique-profile::before {
|
.icon-unlist-outline::before {
|
||||||
content: "\f20f";
|
content: "\f20f";
|
||||||
}
|
}
|
||||||
.icon-unlist-outline::before {
|
.icon-unlist::before {
|
||||||
content: "\f210";
|
content: "\f210";
|
||||||
}
|
}
|
||||||
.icon-unlist::before {
|
.icon-unlock-badge::before {
|
||||||
content: "\f211";
|
content: "\f211";
|
||||||
}
|
}
|
||||||
.icon-unlock-badge::before {
|
.icon-unlock::before {
|
||||||
content: "\f212";
|
content: "\f212";
|
||||||
}
|
}
|
||||||
.icon-unlock::before {
|
.icon-unmute::before {
|
||||||
content: "\f213";
|
content: "\f213";
|
||||||
}
|
}
|
||||||
.icon-unmute::before {
|
.icon-unpin::before {
|
||||||
content: "\f214";
|
content: "\f214";
|
||||||
}
|
}
|
||||||
.icon-unpin::before {
|
.icon-unread::before {
|
||||||
content: "\f215";
|
content: "\f215";
|
||||||
}
|
}
|
||||||
.icon-unread::before {
|
.icon-up::before {
|
||||||
content: "\f216";
|
content: "\f216";
|
||||||
}
|
}
|
||||||
.icon-up::before {
|
.icon-user-filled::before {
|
||||||
content: "\f217";
|
content: "\f217";
|
||||||
}
|
}
|
||||||
.icon-user-filled::before {
|
.icon-user-online::before {
|
||||||
content: "\f218";
|
content: "\f218";
|
||||||
}
|
}
|
||||||
.icon-user-online::before {
|
.icon-user-stars::before {
|
||||||
content: "\f219";
|
content: "\f219";
|
||||||
}
|
}
|
||||||
.icon-user-stars::before {
|
.icon-user::before {
|
||||||
content: "\f21a";
|
content: "\f21a";
|
||||||
}
|
}
|
||||||
.icon-user::before {
|
.icon-video-outlined::before {
|
||||||
content: "\f21b";
|
content: "\f21b";
|
||||||
}
|
}
|
||||||
.icon-video-outlined::before {
|
.icon-video-stop::before {
|
||||||
content: "\f21c";
|
content: "\f21c";
|
||||||
}
|
}
|
||||||
.icon-video-stop::before {
|
.icon-video::before {
|
||||||
content: "\f21d";
|
content: "\f21d";
|
||||||
}
|
}
|
||||||
.icon-video::before {
|
.icon-view-once::before {
|
||||||
content: "\f21e";
|
content: "\f21e";
|
||||||
}
|
}
|
||||||
.icon-view-once::before {
|
.icon-voice-chat::before {
|
||||||
content: "\f21f";
|
content: "\f21f";
|
||||||
}
|
}
|
||||||
.icon-voice-chat::before {
|
.icon-volume-1::before {
|
||||||
content: "\f220";
|
content: "\f220";
|
||||||
}
|
}
|
||||||
.icon-volume-1::before {
|
.icon-volume-2::before {
|
||||||
content: "\f221";
|
content: "\f221";
|
||||||
}
|
}
|
||||||
.icon-volume-2::before {
|
.icon-volume-3::before {
|
||||||
content: "\f222";
|
content: "\f222";
|
||||||
}
|
}
|
||||||
.icon-volume-3::before {
|
.icon-warning::before {
|
||||||
content: "\f223";
|
content: "\f223";
|
||||||
}
|
}
|
||||||
.icon-warning::before {
|
.icon-web::before {
|
||||||
content: "\f224";
|
content: "\f224";
|
||||||
}
|
}
|
||||||
.icon-web::before {
|
.icon-webapp::before {
|
||||||
content: "\f225";
|
content: "\f225";
|
||||||
}
|
}
|
||||||
.icon-webapp::before {
|
.icon-word-wrap::before {
|
||||||
content: "\f226";
|
content: "\f226";
|
||||||
}
|
}
|
||||||
.icon-word-wrap::before {
|
.icon-zoom-in::before {
|
||||||
content: "\f227";
|
content: "\f227";
|
||||||
}
|
}
|
||||||
.icon-zoom-in::before {
|
.icon-zoom-out::before {
|
||||||
content: "\f228";
|
content: "\f228";
|
||||||
}
|
}
|
||||||
.icon-zoom-out::before {
|
|
||||||
content: "\f229";
|
|
||||||
}
|
|
||||||
|
|||||||
@ -279,38 +279,37 @@ $icons-map: (
|
|||||||
"tag": "\f205",
|
"tag": "\f205",
|
||||||
"timer": "\f206",
|
"timer": "\f206",
|
||||||
"toncoin": "\f207",
|
"toncoin": "\f207",
|
||||||
"topic-new": "\f208",
|
"trade": "\f208",
|
||||||
"trade": "\f209",
|
"transcribe": "\f209",
|
||||||
"transcribe": "\f20a",
|
"truck": "\f20a",
|
||||||
"truck": "\f20b",
|
"unarchive": "\f20b",
|
||||||
"unarchive": "\f20c",
|
"underlined": "\f20c",
|
||||||
"underlined": "\f20d",
|
"understood": "\f20d",
|
||||||
"understood": "\f20e",
|
"unique-profile": "\f20e",
|
||||||
"unique-profile": "\f20f",
|
"unlist-outline": "\f20f",
|
||||||
"unlist-outline": "\f210",
|
"unlist": "\f210",
|
||||||
"unlist": "\f211",
|
"unlock-badge": "\f211",
|
||||||
"unlock-badge": "\f212",
|
"unlock": "\f212",
|
||||||
"unlock": "\f213",
|
"unmute": "\f213",
|
||||||
"unmute": "\f214",
|
"unpin": "\f214",
|
||||||
"unpin": "\f215",
|
"unread": "\f215",
|
||||||
"unread": "\f216",
|
"up": "\f216",
|
||||||
"up": "\f217",
|
"user-filled": "\f217",
|
||||||
"user-filled": "\f218",
|
"user-online": "\f218",
|
||||||
"user-online": "\f219",
|
"user-stars": "\f219",
|
||||||
"user-stars": "\f21a",
|
"user": "\f21a",
|
||||||
"user": "\f21b",
|
"video-outlined": "\f21b",
|
||||||
"video-outlined": "\f21c",
|
"video-stop": "\f21c",
|
||||||
"video-stop": "\f21d",
|
"video": "\f21d",
|
||||||
"video": "\f21e",
|
"view-once": "\f21e",
|
||||||
"view-once": "\f21f",
|
"voice-chat": "\f21f",
|
||||||
"voice-chat": "\f220",
|
"volume-1": "\f220",
|
||||||
"volume-1": "\f221",
|
"volume-2": "\f221",
|
||||||
"volume-2": "\f222",
|
"volume-3": "\f222",
|
||||||
"volume-3": "\f223",
|
"warning": "\f223",
|
||||||
"warning": "\f224",
|
"web": "\f224",
|
||||||
"web": "\f225",
|
"webapp": "\f225",
|
||||||
"webapp": "\f226",
|
"word-wrap": "\f226",
|
||||||
"word-wrap": "\f227",
|
"zoom-in": "\f227",
|
||||||
"zoom-in": "\f228",
|
"zoom-out": "\f228",
|
||||||
"zoom-out": "\f229",
|
|
||||||
);
|
);
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
@ -262,7 +262,6 @@ export type FontIconName =
|
|||||||
| 'tag'
|
| 'tag'
|
||||||
| 'timer'
|
| 'timer'
|
||||||
| 'toncoin'
|
| 'toncoin'
|
||||||
| 'topic-new'
|
|
||||||
| 'trade'
|
| 'trade'
|
||||||
| 'transcribe'
|
| 'transcribe'
|
||||||
| 'truck'
|
| 'truck'
|
||||||
|
|||||||
@ -588,13 +588,6 @@ export type StarGiftInfo = {
|
|||||||
shouldUpgrade?: boolean;
|
shouldUpgrade?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TypingDraft = {
|
|
||||||
senderId: string;
|
|
||||||
id: string;
|
|
||||||
date: number;
|
|
||||||
text: ApiFormattedText;
|
|
||||||
};
|
|
||||||
|
|
||||||
export interface TabThread {
|
export interface TabThread {
|
||||||
scrollOffset?: number;
|
scrollOffset?: number;
|
||||||
replyStack?: number[];
|
replyStack?: number[];
|
||||||
@ -617,7 +610,6 @@ export interface Thread {
|
|||||||
threadInfo?: ApiThreadInfo;
|
threadInfo?: ApiThreadInfo;
|
||||||
firstMessageId?: number;
|
firstMessageId?: number;
|
||||||
typingStatus?: ApiTypingStatus;
|
typingStatus?: ApiTypingStatus;
|
||||||
typingDraftIdByRandomId?: Record<string, number>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ServiceNotification {
|
export interface ServiceNotification {
|
||||||
|
|||||||
6
src/types/language.d.ts
vendored
6
src/types/language.d.ts
vendored
@ -1720,10 +1720,6 @@ export interface LangPair {
|
|||||||
'ConfirmBuyGiftForTonDescription': undefined;
|
'ConfirmBuyGiftForTonDescription': undefined;
|
||||||
'TitleGiftLocked': undefined;
|
'TitleGiftLocked': undefined;
|
||||||
'QuickPreview': undefined;
|
'QuickPreview': undefined;
|
||||||
'BotForumContinueThreadButton': undefined;
|
|
||||||
'BotForumActionNew': undefined;
|
|
||||||
'BotForumActionNewDescription': undefined;
|
|
||||||
'BotForumTopicTitlePlaceholder': undefined;
|
|
||||||
'DropOriginalDetailsTransaction': undefined;
|
'DropOriginalDetailsTransaction': undefined;
|
||||||
'StarGiftReasonDropOriginalDetails': undefined;
|
'StarGiftReasonDropOriginalDetails': undefined;
|
||||||
'GiftAnUpgradeButton': undefined;
|
'GiftAnUpgradeButton': undefined;
|
||||||
@ -1732,8 +1728,6 @@ export interface LangPair {
|
|||||||
'UserNoteTitle': undefined;
|
'UserNoteTitle': undefined;
|
||||||
'UserNoteHint': undefined;
|
'UserNoteHint': undefined;
|
||||||
'EditUserNoteHint': undefined;
|
'EditUserNoteHint': undefined;
|
||||||
'BotForumAllTopicTitle': undefined;
|
|
||||||
'BotForumAllTopicDescription': undefined;
|
|
||||||
'AriaStoryTogglerOpen': undefined;
|
'AriaStoryTogglerOpen': undefined;
|
||||||
'InviteBlockedTitle': undefined;
|
'InviteBlockedTitle': undefined;
|
||||||
'InviteBlockedOneMessage': undefined;
|
'InviteBlockedOneMessage': undefined;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user