diff --git a/src/@types/global.d.ts b/src/@types/global.d.ts index c112952cb..faf3b4260 100644 --- a/src/@types/global.d.ts +++ b/src/@types/global.d.ts @@ -175,6 +175,7 @@ interface BooleanConstructor { interface Array { filter(predicate: BooleanConstructor, thisArg?: unknown): Exclude[]; + at(index: number): T; // Make it behave like arr[arr.length - 1] } interface ReadonlyArray { filter(predicate: BooleanConstructor, thisArg?: unknown): Exclude[]; diff --git a/src/api/gramjs/apiBuilders/appConfig.ts b/src/api/gramjs/apiBuilders/appConfig.ts index 0e3985689..7cc39a647 100644 --- a/src/api/gramjs/apiBuilders/appConfig.ts +++ b/src/api/gramjs/apiBuilders/appConfig.ts @@ -118,7 +118,6 @@ export interface GramJsAppConfig extends LimitsConfig { verify_age_bot_username?: string; verify_age_country?: string; verify_age_min?: number; - message_typing_draft_ttl?: 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, verifyAgeCountry: appConfig.verify_age_country, verifyAgeMin: appConfig.verify_age_min, - typingDraftTtl: appConfig.message_typing_draft_ttl, }; return { diff --git a/src/api/gramjs/apiBuilders/chats.ts b/src/api/gramjs/apiBuilders/chats.ts index b1ba2cb9d..12a1dd640 100644 --- a/src/api/gramjs/apiBuilders/chats.ts +++ b/src/api/gramjs/apiBuilders/chats.ts @@ -89,7 +89,6 @@ function buildApiChatFieldsFromPeerEntity( const emojiStatus = userOrChannel?.emojiStatus ? buildApiEmojiStatus(userOrChannel.emojiStatus) : undefined; const paidMessagesStars = userOrChannel?.sendPaidMessagesStars; const isVerified = userOrChannel?.verified; - const isForum = channel?.forum || user?.botForumView; return { isMin, @@ -114,8 +113,7 @@ function buildApiChatFieldsFromPeerEntity( profileColor, isJoinToSend: channel?.joinToSend, isJoinRequest: channel?.joinRequest, - isForum, - isBotForum: user?.botForumView, + isForum: channel?.forum, isMonoforum: channel?.monoforum, linkedMonoforumId: channel?.linkedMonoforumId !== undefined ? buildApiPeerId(channel.linkedMonoforumId, 'channel') : undefined, diff --git a/src/api/gramjs/apiBuilders/messages.ts b/src/api/gramjs/apiBuilders/messages.ts index 0d0bb2891..399add47a 100644 --- a/src/api/gramjs/apiBuilders/messages.ts +++ b/src/api/gramjs/apiBuilders/messages.ts @@ -36,7 +36,6 @@ import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../types'; import { DELETED_COMMENTS_CHANNEL_ID, - LOCAL_MESSAGES_LIMIT, SERVICE_NOTIFICATIONS_USER_ID, SPONSORED_MESSAGE_CACHE_MS, SUPPORTED_AUDIO_CONTENT_TYPES, @@ -77,6 +76,8 @@ import { buildApiRestrictionReasons } from './misc'; import { buildApiPeerColor, buildApiPeerId, getApiChatIdFromMtpPeer } from './peers'; import { buildMessageReactions } from './reactions'; +const LOCAL_MESSAGES_LIMIT = 1e6; // 1M + const LOCAL_MEDIA_UPLOADING_TEMP_ID = 'temp'; const INPUT_WAVEFORM_LENGTH = 63; const MIN_SCHEDULED_PERIOD = 10; @@ -86,10 +87,6 @@ function getNextLocalMessageId(lastMessageId = 0) { return lastMessageId + (++localMessageCounter / LOCAL_MESSAGES_LIMIT); } -export function incrementLocalMessageCounter() { - localMessageCounter++; -} - let currentUserId!: string; export function setMessageBuilderCurrentUserId(_currentUserId: string) { diff --git a/src/api/gramjs/apiBuilders/users.ts b/src/api/gramjs/apiBuilders/users.ts index bebf58e7d..3aaa35ae2 100644 --- a/src/api/gramjs/apiBuilders/users.ts +++ b/src/api/gramjs/apiBuilders/users.ts @@ -112,7 +112,7 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined { const { id, firstName, lastName, fake, scam, support, closeFriend, storiesUnavailable, storiesMaxId, bot, botActiveUsers, botVerificationIcon, botInlinePlaceholder, botAttachMenu, botCanEdit, - sendPaidMessagesStars, profileColor, botForumView, + sendPaidMessagesStars, profileColor, } = mtpUser; const hasVideoAvatar = mtpUser.photo instanceof GramJs.UserProfilePhoto ? Boolean(mtpUser.photo.hasVideo) : undefined; 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), profileColor: profileColor && buildApiPeerColor(profileColor), paidMessagesStars: toJSNumber(sendPaidMessagesStars), - isBotForum: botForumView, }; } diff --git a/src/api/gramjs/methods/forum.ts b/src/api/gramjs/methods/forum.ts index 5cd3660c6..1efe100db 100644 --- a/src/api/gramjs/methods/forum.ts +++ b/src/api/gramjs/methods/forum.ts @@ -16,14 +16,13 @@ import { processAffectedHistory } from '../updates/updateManager'; import { invokeRequest } from './client'; export async function createTopic({ - chat, title, iconColor, iconEmojiId, sendAs, isTitleMissing, + chat, title, iconColor, iconEmojiId, sendAs, }: { chat: ApiChat; title: string; iconColor?: number; iconEmojiId?: string; sendAs?: ApiPeer; - isTitleMissing?: true; }) { const { id, accessHash } = chat; @@ -34,7 +33,6 @@ export async function createTopic({ iconEmojiId: iconEmojiId ? BigInt(iconEmojiId) : undefined, sendAs: sendAs ? buildInputPeer(sendAs.id, sendAs.accessHash) : undefined, randomId: generateRandomBigInt(), - titleMissing: isTitleMissing, })); if (!(updates instanceof GramJs.Updates) || !updates.updates.length) { @@ -77,10 +75,9 @@ export async function fetchTopics({ if (!result) return undefined; - const { orderByCreateDate } = result; + const { count, orderByCreateDate } = result; 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 draftsById = result.topics.reduce((acc, topic) => { if (topic instanceof GramJs.ForumTopic && topic.draft) { diff --git a/src/api/gramjs/methods/messages.ts b/src/api/gramjs/methods/messages.ts index c6e658678..dd3e1aeb4 100644 --- a/src/api/gramjs/methods/messages.ts +++ b/src/api/gramjs/methods/messages.ts @@ -75,7 +75,6 @@ import { buildLocalMessage, buildPreparedInlineMessage, buildUploadingMedia, - incrementLocalMessageCounter, } from '../apiBuilders/messages'; import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers'; import { buildApiUser, buildApiUserStatuses } from '../apiBuilders/users'; @@ -1256,21 +1255,23 @@ export async function markMessageListRead({ }) { 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) { await invokeRequest(new GramJs.channels.ReadHistory({ channel: buildInputChannel(chat.id, chat.accessHash), - maxId, + maxId: fixedMaxId, })); - } else if (threadId !== MAIN_THREAD_ID) { + } else if (isChannel) { await invokeRequest(new GramJs.messages.ReadDiscussion({ peer: buildInputPeer(chat.id, chat.accessHash), msgId: Number(threadId), - readMaxId: maxId, + readMaxId: fixedMaxId, })); } else { const result = await invokeRequest(new GramJs.messages.ReadHistory({ peer: buildInputPeer(chat.id, chat.accessHash), - maxId, + maxId: fixedMaxId, })); if (result) { @@ -2554,7 +2555,3 @@ export async function fetchPreparedInlineMessage({ return buildPreparedInlineMessage(result); } - -export function incrementLocalMessagesCounter() { - incrementLocalMessageCounter(); -} diff --git a/src/api/gramjs/updates/mtpUpdateHandler.ts b/src/api/gramjs/updates/mtpUpdateHandler.ts index b8d8aeb47..5304af2c5 100644 --- a/src/api/gramjs/updates/mtpUpdateHandler.ts +++ b/src/api/gramjs/updates/mtpUpdateHandler.ts @@ -28,7 +28,6 @@ import { buildChatTypingStatus, } from '../apiBuilders/chats'; import { - buildApiFormattedText, buildApiPhoto, buildApiUsernames, buildPrivacyRules, } from '../apiBuilders/common'; import { omitVirtualClassFields } from '../apiBuilders/helpers'; @@ -497,9 +496,10 @@ export function updater(update: Update) { sendApiUpdate({ '@type': 'updateChatInbox', id: getApiChatIdFromMtpPeer(update.peer), - lastReadInboxMessageId: update.maxId, - unreadCount: update.stillUnreadCount, - threadId: update.topMsgId, + chat: { + lastReadInboxMessageId: update.maxId, + unreadCount: update.stillUnreadCount, + }, }); } else if (update instanceof GramJs.UpdateReadHistoryOutbox) { sendApiUpdate({ @@ -648,33 +648,22 @@ export function updater(update: Update) { update instanceof GramJs.UpdateUserTyping || update instanceof GramJs.UpdateChatUserTyping ) { - const chatId = update instanceof GramJs.UpdateUserTyping + const id = update instanceof GramJs.UpdateUserTyping ? buildApiPeerId(update.userId, 'user') : buildApiPeerId(update.chatId, 'chat'); - const threadId = update instanceof GramJs.UpdateUserTyping ? update.topMsgId : undefined; - if (update.action instanceof GramJs.SendMessageEmojiInteraction) { sendApiUpdate({ '@type': 'updateStartEmojiInteraction', - id: chatId, + id, emoji: update.action.emoticon, messageId: update.action.msgId, 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 { sendApiUpdate({ '@type': 'updateChatTypingStatus', - id: chatId, - threadId, + id, typingStatus: buildChatTypingStatus(update), }); } diff --git a/src/api/types/chats.ts b/src/api/types/chats.ts index ab58d2f43..65be0a1fa 100644 --- a/src/api/types/chats.ts +++ b/src/api/types/chats.ts @@ -57,7 +57,6 @@ export interface ApiChat { isForum?: boolean; isForumAsMessages?: true; isMonoforum?: boolean; - isBotForum?: boolean; withForumTabs?: boolean; linkedMonoforumId?: string; areChannelMessagesAllowed?: boolean; diff --git a/src/api/types/messages.ts b/src/api/types/messages.ts index d4d7a1242..508d3575c 100644 --- a/src/api/types/messages.ts +++ b/src/api/types/messages.ts @@ -489,7 +489,7 @@ export type ApiMessageEntityDefault = { type: Exclude< `${ApiMessageEntityTypes}`, `${ApiMessageEntityTypes.Pre}` | `${ApiMessageEntityTypes.TextUrl}` | `${ApiMessageEntityTypes.MentionName}` | - `${ApiMessageEntityTypes.Blockquote}` | `${ApiMessageEntityTypes.CustomEmoji}` | `${ApiMessageEntityTypes.Timestamp}` + `${ApiMessageEntityTypes.CustomEmoji}` | `${ApiMessageEntityTypes.Blockquote}` | `${ApiMessageEntityTypes.Timestamp}` >; offset: number; length: number; @@ -538,8 +538,15 @@ export type ApiMessageEntityTimestamp = { timestamp: number; }; +export type ApiMessageEntityQuoteFocus = { + type: 'quoteFocus'; + offset: number; + length: number; +}; + export type ApiMessageEntity = ApiMessageEntityDefault | ApiMessageEntityPre | ApiMessageEntityTextUrl | - ApiMessageEntityMentionName | ApiMessageEntityCustomEmoji | ApiMessageEntityBlockquote | ApiMessageEntityTimestamp; + ApiMessageEntityMentionName | ApiMessageEntityCustomEmoji | ApiMessageEntityBlockquote | ApiMessageEntityTimestamp | + ApiMessageEntityQuoteFocus; export enum ApiMessageEntityTypes { Bold = 'MessageEntityBold', @@ -676,8 +683,6 @@ export interface ApiMessage { reportDeliveryUntilDate?: number; paidMessageStars?: number; restrictionReasons?: ApiRestrictionReason[]; - - isTypingDraft?: boolean; // Local field } export interface ApiReactions { @@ -917,18 +922,13 @@ interface ApiKeyboardButtonCopy { copyText: string; } -export interface KeyboardButtonSuggestedMessage { +export interface ApiKeyboardButtonSuggestedMessage { type: 'suggestedMessage'; text: string; buttonType: 'approve' | 'decline' | 'suggestChanges'; disabled?: boolean; } -export interface KeyboardButtonOpenThread { - type: 'openThread'; - text: string; -} - export type ApiKeyboardButton = ( ApiKeyboardButtonSimple | ApiKeyboardButtonReceipt @@ -941,8 +941,7 @@ export type ApiKeyboardButton = ( | ApiKeyboardButtonSimpleWebView | ApiKeyboardButtonUrlAuth | ApiKeyboardButtonCopy - | KeyboardButtonSuggestedMessage - | KeyboardButtonOpenThread + | ApiKeyboardButtonSuggestedMessage ); export type ApiKeyboardButtons = ApiKeyboardButton[][]; diff --git a/src/api/types/misc.ts b/src/api/types/misc.ts index 3f408acf3..26c4a5197 100644 --- a/src/api/types/misc.ts +++ b/src/api/types/misc.ts @@ -274,7 +274,6 @@ export interface ApiAppConfig { verifyAgeBotUsername?: string; verifyAgeCountry?: string; verifyAgeMin?: number; - typingDraftTtl: number; contactNoteLimit?: number; } diff --git a/src/api/types/updates.ts b/src/api/types/updates.ts index c87264adc..980dc2144 100644 --- a/src/api/types/updates.ts +++ b/src/api/types/updates.ts @@ -132,9 +132,7 @@ export type ApiUpdateChatLeave = { export type ApiUpdateChatInbox = { '@type': 'updateChatInbox'; id: string; - threadId?: ThreadId; - lastReadInboxMessageId: number; - unreadCount: number; + chat: Partial; }; export type ApiUpdateChatTypingStatus = { @@ -144,14 +142,6 @@ export type ApiUpdateChatTypingStatus = { typingStatus: ApiTypingStatus | undefined; }; -export type ApiUpdateChatTypingDraft = { - '@type': 'updateChatTypingDraft'; - chatId: string; - id: string; - threadId?: ThreadId; - text: ApiFormattedText; -}; - export type ApiUpdateStartEmojiInteraction = { '@type': 'updateStartEmojiInteraction'; id: string; @@ -888,7 +878,7 @@ export type ApiUpdate = ( ApiUpdateRecentStickers | ApiUpdateSavedGifs | ApiUpdateNewScheduledMessage | ApiUpdateMoveStickerSetToTop | ApiUpdateScheduledMessageSendSucceeded | ApiUpdateScheduledMessage | ApiUpdateStarPaymentStateCompleted | ApiUpdateDeleteScheduledMessages | ApiUpdateResetMessages | ApiUpdateMessageTranslations | - ApiUpdateFailedMessageTranslations | ApiUpdateWebPage | ApiUpdateChatTypingDraft | + ApiUpdateFailedMessageTranslations | ApiUpdateWebPage | ApiUpdateTwoFaError | ApiUpdateTwoFaStateWaitCode | ApiUpdateWebViewResultSent | ApiUpdateDefaultNotifySettings | ApiUpdatePeerNotifySettings | ApiUpdatePeerBlocked | ApiUpdatePrivacy | ApiUpdateServerTimeOffset | ApiUpdateMessageReactions | ApiUpdateSavedReactionTags | diff --git a/src/api/types/users.ts b/src/api/types/users.ts index 797c9f071..268d88dd9 100644 --- a/src/api/types/users.ts +++ b/src/api/types/users.ts @@ -47,7 +47,6 @@ export interface ApiUser { botActiveUsers?: number; botVerificationIconId?: string; paidMessagesStars?: number; - isBotForum?: boolean; } export interface ApiUserFullInfo { diff --git a/src/assets/font-icons/topic-new.svg b/src/assets/font-icons/topic-new.svg deleted file mode 100644 index 00142c166..000000000 --- a/src/assets/font-icons/topic-new.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/assets/localization/fallback.strings b/src/assets/localization/fallback.strings index 58fae90f4..711bee245 100644 --- a/src/assets/localization/fallback.strings +++ b/src/assets/localization/fallback.strings @@ -2301,10 +2301,6 @@ "TitleGiftLocked" = "Gift Locked"; "GiftLockedMessage" = "This gift is currently only available to earlier Telegram users. It will unlock for your account in about **{relativeDate}**."; "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"; "StarGiftReasonDropOriginalDetails" = "Removed Description"; "GiftAnUpgradeButton" = "Gift an Upgrade"; @@ -2314,8 +2310,6 @@ "UserNoteTitle" = "Notes"; "UserNoteHint" = "only visible to you"; "EditUserNoteHint" = "Notes are only visible to you."; -"BotForumAllTopicTitle" = "All Messages"; -"BotForumAllTopicDescription" = "All messages from all topics"; "AriaStoryTogglerOpen" = "Open Story List"; "FileTransferProgress" = "{currentSize} / {totalSize}"; "MediaSizeB_one" = "{size}B"; diff --git a/src/components/common/Composer.tsx b/src/components/common/Composer.tsx index 36e0ac2cb..6a28db9e9 100644 --- a/src/components/common/Composer.tsx +++ b/src/components/common/Composer.tsx @@ -1705,7 +1705,7 @@ const Composer: FC = ({ 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 ? lang('ComposerPlaceholderTopic', { topic: replyToTopic.title }) : lang('ComposerPlaceholderTopicGeneral'); diff --git a/src/components/common/GroupChatInfo.tsx b/src/components/common/GroupChatInfo.tsx index d9deed25b..636c63341 100644 --- a/src/components/common/GroupChatInfo.tsx +++ b/src/components/common/GroupChatInfo.tsx @@ -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 { getActions, getGlobal, withGlobal } from '../../global'; import type { - ApiChat, ApiTopic, ApiTypingStatus, ApiUser, + ApiChat, ApiThreadInfo, ApiTopic, ApiTypingStatus, ApiUser, } from '../../api/types'; import type { IconName } from '../../types/icons'; import { MediaViewerOrigin, type StoryViewerOrigin, type ThreadId } from '../../types'; @@ -19,6 +21,7 @@ import { selectChatOnlineCount, selectIsChatRestricted, selectMonoforumChannel, + selectThreadInfo, selectThreadMessagesCount, selectTopic, selectUser, @@ -65,11 +68,12 @@ type OwnProps = { isSavedDialog?: boolean; withMonoforumStatus?: boolean; onClick?: VoidFunction; - onEmojiStatusClick?: VoidFunction; + onEmojiStatusClick?: NoneToVoidFunction; }; type StateProps = { chat?: ApiChat; + threadInfo?: ApiThreadInfo; topic?: ApiTopic; onlineCount?: number; areMessagesLoaded: boolean; @@ -78,7 +82,7 @@ type StateProps = { monoforumChannel?: ApiChat; }; -const GroupChatInfo = ({ +const GroupChatInfo: FC = ({ typingStatus, className, statusIcon, @@ -91,6 +95,7 @@ const GroupChatInfo = ({ withFullInfo, withUpdatingStatus, withChatType, + threadInfo, noRtl, chat: realChat, onlineCount, @@ -108,7 +113,7 @@ const GroupChatInfo = ({ monoforumChannel, onClick, onEmojiStatusClick, -}: OwnProps & StateProps) => { +}) => { const { loadFullChat, openMediaViewer, @@ -121,7 +126,7 @@ const GroupChatInfo = ({ const lang = useLang(); const isSuperGroup = chat && isChatSuperGroup(chat); - const isTopic = Boolean(chat?.isForum && topic); + const isTopic = Boolean(chat?.isForum && threadInfo && topic); const { id: chatId, isMin } = chat || {}; const isRestricted = selectIsChatRestricted(getGlobal(), chatId!); @@ -199,7 +204,7 @@ const GroupChatInfo = ({ activeKey={messagesCount !== undefined ? 1 : 2} className="message-count-transition" > - {messagesCount !== undefined ? oldLang('messages', messagesCount, 'i') : oldLang('lng_forum_no_messages')} + {messagesCount !== undefined && oldLang('messages', messagesCount, 'i')} ); @@ -285,6 +290,7 @@ const GroupChatInfo = ({ export default memo(withGlobal( (global, { chatId, threadId }): Complete => { const chat = selectChat(global, chatId); + const threadInfo = threadId ? selectThreadInfo(global, chatId, threadId) : undefined; const onlineCount = chat ? selectChatOnlineCount(global, chat) : undefined; const areMessagesLoaded = Boolean(selectChatMessages(global, chatId)); const topic = threadId ? selectTopic(global, chatId, threadId) : undefined; @@ -294,6 +300,7 @@ export default memo(withGlobal( return { chat, + threadInfo, onlineCount, topic, areMessagesLoaded, diff --git a/src/components/common/MessageText.tsx b/src/components/common/MessageText.tsx index 8d31a76fa..ee80dc79e 100644 --- a/src/components/common/MessageText.tsx +++ b/src/components/common/MessageText.tsx @@ -12,12 +12,9 @@ import trimText from '../../util/trimText'; import { insertTextEntity, renderTextWithEntities } from './helpers/renderTextWithEntities'; import useLang from '../../hooks/useLang'; -import useLastCallback from '../../hooks/useLastCallback'; import useSyncEffect from '../../hooks/useSyncEffect'; import useUniqueId from '../../hooks/useUniqueId'; -import TypingWrapper from './TypingWrapper'; - interface OwnProps { messageOrStory: ApiMessage | ApiStory; threadId?: ThreadId; @@ -39,7 +36,6 @@ interface OwnProps { isInSelectMode?: boolean; canBeEmpty?: boolean; maxTimestamp?: number; - shouldAnimateTyping?: boolean; } const MIN_CUSTOM_EMOJIS_FOR_SHARED_CANVAS = 3; @@ -65,7 +61,6 @@ function MessageText({ canBeEmpty, maxTimestamp, threadId, - shouldAnimateTyping, }: OwnProps) { const sharedCanvasRef = useRef(); const sharedCanvasHqRef = useRef(); @@ -112,48 +107,37 @@ function MessageText({ return customEmojisCount >= MIN_CUSTOM_EMOJIS_FOR_SHARED_CANVAS; }, [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) { return {lang('MessageUnsupported')}; } - const textToRender: ApiFormattedText = { - text: trimText(text || '', truncateLength), - entities: entitiesWithFocusedQuote, - }; - return ( <> {[ - withSharedCanvas && , - withSharedCanvas && , - shouldAnimateTyping ? ( - {renderText} - ) : renderText(textToRender), + withSharedCanvas && , + withSharedCanvas && , + renderTextWithEntities({ + text: trimText(text!, truncateLength), + 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)} ); diff --git a/src/components/common/PrivateChatInfo.tsx b/src/components/common/PrivateChatInfo.tsx index f053c35f9..f58b8e565 100644 --- a/src/components/common/PrivateChatInfo.tsx +++ b/src/components/common/PrivateChatInfo.tsx @@ -1,25 +1,19 @@ +import type { FC } from '../../lib/teact/teact'; import { memo, useEffect, useMemo } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import type { - ApiChatMember, ApiTopic, ApiTypingStatus, ApiUser, ApiUserStatus, + ApiChatMember, ApiTypingStatus, ApiUser, ApiUserStatus, } from '../../api/types'; -import type { CustomPeer, StoryViewerOrigin, ThreadId } from '../../types'; +import type { CustomPeer, StoryViewerOrigin } from '../../types'; import type { IconName } from '../../types/icons'; import { MediaViewerOrigin } from '../../types'; import { getMainUsername, getUserStatus, isSystemBot, isUserOnline, } from '../../global/helpers'; -import { - selectChatMessages, - selectThreadMessagesCount, - selectTopic, - selectUser, - selectUserStatus, -} from '../../global/selectors'; +import { selectChatMessages, selectUser, selectUserStatus } from '../../global/selectors'; import buildClassName from '../../util/buildClassName'; -import { REM } from './helpers/mediaDimensions'; import renderText from './helpers/renderText'; import useIntervalForceUpdate from '../../hooks/schedulers/useIntervalForceUpdate'; @@ -28,17 +22,15 @@ import useLastCallback from '../../hooks/useLastCallback'; import useOldLang from '../../hooks/useOldLang'; import RippleEffect from '../ui/RippleEffect'; -import Transition from '../ui/Transition'; import Avatar from './Avatar'; import DotAnimation from './DotAnimation'; import FullNameTitle from './FullNameTitle'; import Icon from './icons/Icon'; -import TopicIcon from './TopicIcon'; import TypingStatus from './TypingStatus'; -const TOPIC_ICON_SIZE = 2.5 * REM; - -type BaseOwnProps = { +type OwnProps = { + userId?: string; + customPeer?: CustomPeer; typingStatus?: ApiTypingStatus; avatarSize?: 'tiny' | 'small' | 'medium' | 'large' | 'jumbo'; forceShowSelf?: boolean; @@ -60,39 +52,25 @@ type BaseOwnProps = { noRtl?: boolean; adminMember?: ApiChatMember; isSavedDialog?: boolean; - noAvatar?: boolean; className?: string; + onEmojiStatusClick?: NoneToVoidFunction; iconElement?: React.ReactNode; rightElement?: React.ReactNode; - onClick?: VoidFunction; - onEmojiStatusClick?: VoidFunction; }; -type OwnProps = BaseOwnProps & ({ - userId: string; - threadId?: ThreadId; - customPeer?: never; -} | { - userId?: never; - threadId?: never; - customPeer: CustomPeer; -}); - -type StateProps = { - user?: ApiUser; - userStatus?: ApiUserStatus; - self?: ApiUser; - isSavedMessages?: boolean; - areMessagesLoaded: boolean; - isSynced?: boolean; - topic?: ApiTopic; - messagesCount?: number; -}; +type StateProps = + { + user?: ApiUser; + userStatus?: ApiUserStatus; + self?: ApiUser; + isSavedMessages?: boolean; + areMessagesLoaded: boolean; + isSynced?: boolean; + }; const UPDATE_INTERVAL = 1000 * 60; // 1 min -const PrivateChatInfo = ({ - userId, +const PrivateChatInfo: FC = ({ customPeer, typingStatus, avatarSize = 'medium', @@ -113,8 +91,6 @@ const PrivateChatInfo = ({ user, userStatus, self, - topic, - messagesCount, isSavedMessages, isSavedDialog, areMessagesLoaded, @@ -122,13 +98,11 @@ const PrivateChatInfo = ({ ripple, className, storyViewerOrigin, - noAvatar, isSynced, + onEmojiStatusClick, iconElement, rightElement, - onClick, - onEmojiStatusClick, -}: OwnProps & StateProps) => { +}) => { const { loadFullUser, openMediaViewer, @@ -138,7 +112,8 @@ const PrivateChatInfo = ({ const oldLang = useOldLang(); const lang = useLang(); - const isTopic = Boolean(user?.isBotForum && topic); + const { id: userId } = user || {}; + const hasAvatarMediaViewer = withMediaViewer && !isSavedMessages; useEffect(() => { @@ -152,11 +127,11 @@ const PrivateChatInfo = ({ const handleAvatarViewerOpen = useLastCallback( (e: React.MouseEvent, hasMedia: boolean) => { - if (hasMedia) { + if (user && hasMedia) { e.stopPropagation(); openMediaViewer({ isAvatarView: true, - chatId: userId, + chatId: user.id, mediaIndex: 0, origin: avatarSize === 'jumbo' ? MediaViewerOrigin.ProfileAvatar : MediaViewerOrigin.MiddleHeaderAvatar, }); @@ -204,21 +179,6 @@ const PrivateChatInfo = ({ return ; } - if (isTopic) { - return ( - - - {messagesCount !== undefined ? oldLang('messages', messagesCount, 'i') : oldLang('lng_forum_no_messages')} - - - ); - } - if (isSystemBot(user.id)) { return undefined; } @@ -238,12 +198,6 @@ const PrivateChatInfo = ({ : undefined; function renderNameTitle() { - if (isTopic) { - return ( -

{renderText(topic!.title)}

- ); - } - if (customTitle) { return (
@@ -276,11 +230,7 @@ const PrivateChatInfo = ({ } return ( -
+
{isSavedDialog && self && ( )} - {!noAvatar && !isTopic && ( - - )} - {isTopic && ( - - )} +
{renderNameTitle()} {(status || (!isSavedMessages && !noStatusOrTyping)) && renderStatusOrTyping()} @@ -322,16 +263,13 @@ const PrivateChatInfo = ({ }; export default memo(withGlobal( - (global, { userId, threadId, forceShowSelf }): Complete => { + (global, { userId, forceShowSelf }): Complete => { const { isSynced } = global; const user = userId ? selectUser(global, userId) : undefined; const userStatus = userId ? selectUserStatus(global, userId) : undefined; const isSavedMessages = !forceShowSelf && user && user.isSelf; const self = isSavedMessages ? user : selectUser(global, global.currentUserId!); - const areMessagesLoaded = Boolean(userId ? selectChatMessages(global, userId) : undefined); - - const topic = threadId ? selectTopic(global, userId, threadId) : undefined; - const messagesCount = topic && userId ? selectThreadMessagesCount(global, userId, threadId!) : undefined; + const areMessagesLoaded = Boolean(userId && selectChatMessages(global, userId)); return { user, @@ -340,8 +278,6 @@ export default memo(withGlobal( areMessagesLoaded, self, isSynced, - topic, - messagesCount, }; }, )(PrivateChatInfo)); diff --git a/src/components/common/TypingWrapper.tsx b/src/components/common/TypingWrapper.tsx deleted file mode 100644 index 074ccfeb6..000000000 --- a/src/components/common/TypingWrapper.tsx +++ /dev/null @@ -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(); - - 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); diff --git a/src/components/common/helpers/renderTextWithEntities.tsx b/src/components/common/helpers/renderTextWithEntities.tsx index 8f2224c3b..177f92c3f 100644 --- a/src/components/common/helpers/renderTextWithEntities.tsx +++ b/src/components/common/helpers/renderTextWithEntities.tsx @@ -1,4 +1,5 @@ import type { ElementRef } from '../../../lib/teact/teact'; +import type React from '../../../lib/teact/teact'; import { getActions } from '../../../global'; import type { ApiFormattedText, ApiMessageEntity } from '../../../api/types'; @@ -9,6 +10,7 @@ import { ApiMessageEntityTypes } from '../../../api/types'; import buildClassName from '../../../util/buildClassName'; import { copyTextToClipboard } from '../../../util/clipboard'; +import { oldTranslate } from '../../../util/oldLangProvider'; import { buildCustomEmojiHtmlFromEntity } from '../../middle/composer/helpers/customEmoji'; import renderText from './renderText'; @@ -299,12 +301,6 @@ function renderMessagePart({ 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) { const resultEntities: ApiMessageEntity[] = []; @@ -765,9 +761,7 @@ function handleHashtagClick(hashtag?: string, username?: string) { function handleCodeClick(e: React.MouseEvent) { copyTextToClipboard(e.currentTarget.innerText); getActions().showNotification({ - message: { - key: 'TextCopied', - }, + message: oldTranslate('TextCopied'), }); } diff --git a/src/components/left/ArchivedChats.tsx b/src/components/left/ArchivedChats.tsx index 0a054cf8c..b047a1061 100644 --- a/src/components/left/ArchivedChats.tsx +++ b/src/components/left/ArchivedChats.tsx @@ -23,7 +23,7 @@ import Button from '../ui/Button'; import DropdownMenu from '../ui/DropdownMenu'; import MenuItem from '../ui/MenuItem'; import ChatList from './main/ChatList'; -import ForumPanel from './main/forum/ForumPanel'; +import ForumPanel from './main/ForumPanel'; import './ArchivedChats.scss'; diff --git a/src/components/left/main/ChatBadge.module.scss b/src/components/left/main/ChatBadge.module.scss index a5a876919..494d104fe 100644 --- a/src/components/left/main/ChatBadge.module.scss +++ b/src/components/left/main/ChatBadge.module.scss @@ -103,7 +103,7 @@ font-size: 0.875rem !important; } -.selected:not(.onAvatar) { +.selected { .badge:not(.pinned) { color: var(--color-chat-active); background: var(--color-white); diff --git a/src/components/left/main/forum/EmptyForum.module.scss b/src/components/left/main/EmptyForum.module.scss similarity index 100% rename from src/components/left/main/forum/EmptyForum.module.scss rename to src/components/left/main/EmptyForum.module.scss diff --git a/src/components/left/main/forum/EmptyForum.tsx b/src/components/left/main/EmptyForum.tsx similarity index 52% rename from src/components/left/main/forum/EmptyForum.tsx rename to src/components/left/main/EmptyForum.tsx index b4680e810..9b43f3872 100644 --- a/src/components/left/main/forum/EmptyForum.tsx +++ b/src/components/left/main/EmptyForum.tsx @@ -1,20 +1,19 @@ -import { memo } from '../../../../lib/teact/teact'; -import { getActions, withGlobal } from '../../../../global'; +import type { FC } from '../../../lib/teact/teact'; +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 { selectAnimatedEmoji, selectChat } from '../../../../global/selectors'; -import buildClassName from '../../../../util/buildClassName'; -import { REM } from '../../../common/helpers/mediaDimensions'; +import { getHasAdminRight } from '../../../global/helpers'; +import { selectAnimatedEmoji, selectChat } from '../../../global/selectors'; +import buildClassName from '../../../util/buildClassName'; +import { REM } from '../../common/helpers/mediaDimensions'; -import useAppLayout from '../../../../hooks/useAppLayout'; -import useLang from '../../../../hooks/useLang'; -import useLastCallback from '../../../../hooks/useLastCallback'; -import useOldLang from '../../../../hooks/useOldLang'; +import useAppLayout from '../../../hooks/useAppLayout'; +import useOldLang from '../../../hooks/useOldLang'; -import AnimatedIconFromSticker from '../../../common/AnimatedIconFromSticker'; -import Button from '../../../ui/Button'; +import AnimatedIconFromSticker from '../../common/AnimatedIconFromSticker'; +import Button from '../../ui/Button'; import styles from './EmptyForum.module.scss'; @@ -29,27 +28,26 @@ type StateProps = { const ICON_SIZE = 7 * REM; -const EmptyForum = ({ +const EmptyForum: FC = ({ chatId, animatedEmoji, canManageTopics, -}: OwnProps & StateProps) => { +}) => { const { openCreateTopicPanel } = getActions(); - const lang = useLang(); - const oldLang = useOldLang(); + const lang = useOldLang(); const { isMobile } = useAppLayout(); - const handleCreateTopic = useLastCallback(() => { + const handleCreateTopic = useCallback(() => { openCreateTopicPanel({ chatId }); - }); + }, [chatId, openCreateTopicPanel]); return (
{animatedEmoji && }
-

{oldLang('ChatList.EmptyTopicsTitle')}

+

{lang('ChatList.EmptyTopicsTitle')}

- {oldLang('ChatList.EmptyTopicsDescription')} + {lang('ChatList.EmptyTopicsDescription')}

{canManageTopics && ( )} diff --git a/src/components/left/main/forum/ForumPanel.module.scss b/src/components/left/main/ForumPanel.module.scss similarity index 100% rename from src/components/left/main/forum/ForumPanel.module.scss rename to src/components/left/main/ForumPanel.module.scss diff --git a/src/components/left/main/forum/ForumPanel.tsx b/src/components/left/main/ForumPanel.tsx similarity index 66% rename from src/components/left/main/forum/ForumPanel.tsx rename to src/components/left/main/ForumPanel.tsx index e3c447938..9a876925d 100644 --- a/src/components/left/main/forum/ForumPanel.tsx +++ b/src/components/left/main/ForumPanel.tsx @@ -1,18 +1,19 @@ +import type { FC } from '../../../lib/teact/teact'; import { beginHeavyAnimation, memo, useEffect, useMemo, useRef, useState, -} from '../../../../lib/teact/teact'; -import { getActions, withGlobal } from '../../../../global'; +} from '../../../lib/teact/teact'; +import { getActions, withGlobal } from '../../../global'; -import type { ApiChat } from '../../../../api/types'; -import type { TopicsInfo } from '../../../../types'; -import { MAIN_THREAD_ID } from '../../../../api/types'; +import type { ApiChat } from '../../../api/types'; +import type { TopicsInfo } from '../../../types'; +import { MAIN_THREAD_ID } from '../../../api/types'; import { GENERAL_TOPIC_ID, TOPIC_HEIGHT_PX, TOPIC_LIST_SENSITIVE_AREA, TOPICS_SLICE, -} from '../../../../config'; -import { requestNextMutation } from '../../../../lib/fasterdom/fasterdom'; -import { getOrderedTopics } from '../../../../global/helpers'; +} from '../../../config'; +import { requestNextMutation } from '../../../lib/fasterdom/fasterdom'; +import { getOrderedTopics } from '../../../global/helpers'; import { selectCanAnimateInterface, selectChat, @@ -20,32 +21,29 @@ import { selectIsForumPanelOpen, selectTabState, selectTopicsInfo, -} from '../../../../global/selectors'; -import { IS_TOUCH_ENV } from '../../../../util/browser/windowEnvironment'; -import buildClassName from '../../../../util/buildClassName'; -import captureEscKeyListener from '../../../../util/captureEscKeyListener'; -import { captureEvents, SwipeDirection } from '../../../../util/captureEvents'; -import { waitForTransitionEnd } from '../../../../util/cssAnimationEndListeners'; -import { isUserId } from '../../../../util/entities/ids'; +} from '../../../global/selectors'; +import { IS_TOUCH_ENV } from '../../../util/browser/windowEnvironment'; +import buildClassName from '../../../util/buildClassName'; +import captureEscKeyListener from '../../../util/captureEscKeyListener'; +import { captureEvents, SwipeDirection } from '../../../util/captureEvents'; +import { waitForTransitionEnd } from '../../../util/cssAnimationEndListeners'; -import useAppLayout from '../../../../hooks/useAppLayout'; -import useHistoryBack from '../../../../hooks/useHistoryBack'; -import useInfiniteScroll from '../../../../hooks/useInfiniteScroll'; -import { useIntersectionObserver, useOnIntersect } from '../../../../hooks/useIntersectionObserver'; -import useLang from '../../../../hooks/useLang'; -import useLastCallback from '../../../../hooks/useLastCallback'; -import usePreviousDeprecated from '../../../../hooks/usePreviousDeprecated'; -import useOrderDiff from '../hooks/useOrderDiff'; +import useAppLayout from '../../../hooks/useAppLayout'; +import useHistoryBack from '../../../hooks/useHistoryBack'; +import useInfiniteScroll from '../../../hooks/useInfiniteScroll'; +import { useIntersectionObserver, useOnIntersect } from '../../../hooks/useIntersectionObserver'; +import useLastCallback from '../../../hooks/useLastCallback'; +import useOldLang from '../../../hooks/useOldLang'; +import usePreviousDeprecated from '../../../hooks/usePreviousDeprecated'; +import useOrderDiff from './hooks/useOrderDiff'; -import GroupCallTopPane from '../../../calls/group/GroupCallTopPane'; -import GroupChatInfo from '../../../common/GroupChatInfo'; -import Icon from '../../../common/icons/Icon'; -import PrivateChatInfo from '../../../common/PrivateChatInfo'; -import HeaderActions from '../../../middle/HeaderActions'; -import Button from '../../../ui/Button'; -import InfiniteScroll from '../../../ui/InfiniteScroll'; -import Loading from '../../../ui/Loading'; -import AllMessagesTopic from './AllMessagesTopic'; +import GroupCallTopPane from '../../calls/group/GroupCallTopPane'; +import GroupChatInfo from '../../common/GroupChatInfo'; +import Icon from '../../common/icons/Icon'; +import HeaderActions from '../../middle/HeaderActions'; +import Button from '../../ui/Button'; +import InfiniteScroll from '../../ui/InfiniteScroll'; +import Loading from '../../ui/Loading'; import EmptyForum from './EmptyForum'; import Topic from './Topic'; @@ -68,17 +66,17 @@ type StateProps = { const INTERSECTION_THROTTLE = 200; -const ForumPanel = ({ +const ForumPanel: FC = ({ chat, currentTopicId, isOpen, isHidden, topicsInfo, - withInterfaceAnimations, onTopicSearch, onCloseAnimationEnd, onOpenAnimationStart, -}: OwnProps & StateProps) => { + withInterfaceAnimations, +}) => { const { closeForumPanel, openChatWithInfo, loadTopics, } = getActions(); @@ -97,7 +95,7 @@ const ForumPanel = ({ }, [topicsInfo, chatId]); const [isScrolled, setIsScrolled] = useState(false); - const lang = useLang(); + const lang = useOldLang(); const handleClose = useLastCallback(() => { closeForumPanel(); @@ -124,17 +122,13 @@ const ForumPanel = ({ }); const orderedIds = useMemo(() => { - const ids = topicsInfo + return topicsInfo ? getOrderedTopics( Object.values(topicsInfo.topicsById), topicsInfo.orderedPinnedTopicIds, ).map(({ id }) => id) : []; - - if (!chat?.isBotForum) return ids; - - return [MAIN_THREAD_ID, ...ids]; - }, [chat?.isBotForum, topicsInfo]); + }, [topicsInfo]); const { orderDiffById, getAnimationType, onReorderAnimationEnd } = useOrderDiff(orderedIds, chat?.id); @@ -203,37 +197,23 @@ const ForumPanel = ({ function renderTopics() { const viewportOffset = orderedIds.indexOf(viewportIds![0]); - return viewportIds?.map((id, i) => { - if (id === MAIN_THREAD_ID) { - return ( - - ); - } - - return ( - - ); - }); + return viewportIds?.map((id, i) => ( + + )); } const isLoading = topicsInfo === undefined; - if (!chat) return undefined; - return (
- {isUserId(chat.id) ? ( - - ) : ( + {chat && ( )} - + {chat + && ( + + )}
- {!isUserId(chat.id) && } + {chat && }
diff --git a/src/components/left/main/LeftMain.tsx b/src/components/left/main/LeftMain.tsx index 84b0caef5..dbbe8c89c 100644 --- a/src/components/left/main/LeftMain.tsx +++ b/src/components/left/main/LeftMain.tsx @@ -25,7 +25,7 @@ import NewChatButton from '../NewChatButton'; import LeftSearch from '../search/LeftSearch.async'; import ChatFolders from './ChatFolders'; import ContactList from './ContactList.async'; -import ForumPanel from './forum/ForumPanel'; +import ForumPanel from './ForumPanel'; import LeftMainHeader from './LeftMainHeader'; import './LeftMain.scss'; diff --git a/src/components/left/main/forum/Topic.module.scss b/src/components/left/main/Topic.module.scss similarity index 100% rename from src/components/left/main/forum/Topic.module.scss rename to src/components/left/main/Topic.module.scss diff --git a/src/components/left/main/forum/Topic.tsx b/src/components/left/main/Topic.tsx similarity index 81% rename from src/components/left/main/forum/Topic.tsx rename to src/components/left/main/Topic.tsx index 0d51030d5..03199afc1 100644 --- a/src/components/left/main/forum/Topic.tsx +++ b/src/components/left/main/Topic.tsx @@ -1,17 +1,17 @@ -import type { FC } from '../../../../lib/teact/teact'; -import { memo } from '../../../../lib/teact/teact'; -import { getActions, withGlobal } from '../../../../global'; +import type { FC } from '../../../lib/teact/teact'; +import { memo } from '../../../lib/teact/teact'; +import { getActions, withGlobal } from '../../../global'; import type { ApiChat, ApiDraft, ApiMessage, ApiMessageOutgoingStatus, ApiPeer, ApiTopic, ApiTypeStory, ApiTypingStatus, -} from '../../../../api/types'; -import type { ObserveFn } from '../../../../hooks/useIntersectionObserver'; -import type { ChatAnimationTypes } from '../hooks'; +} from '../../../api/types'; +import type { ObserveFn } from '../../../hooks/useIntersectionObserver'; +import type { ChatAnimationTypes } from './hooks'; -import { UNMUTE_TIMESTAMP } from '../../../../config'; -import { groupStatefulContent } from '../../../../global/helpers'; -import { getIsChatMuted } from '../../../../global/helpers/notifications'; +import { UNMUTE_TIMESTAMP } from '../../../config'; +import { groupStatefulContent } from '../../../global/helpers'; +import { getIsChatMuted } from '../../../global/helpers/notifications'; import { selectCanAnimateInterface, selectCanDeleteTopic, @@ -27,25 +27,25 @@ import { selectThreadInfo, selectThreadParam, selectTopics, -} 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 renderText from '../../../common/helpers/renderText'; +} 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 renderText from '../../common/helpers/renderText'; -import useFlag from '../../../../hooks/useFlag'; -import useLastCallback from '../../../../hooks/useLastCallback'; -import useOldLang from '../../../../hooks/useOldLang'; -import useChatListEntry from '../hooks/useChatListEntry'; -import useTopicContextActions from '../hooks/useTopicContextActions'; +import useFlag from '../../../hooks/useFlag'; +import useLastCallback from '../../../hooks/useLastCallback'; +import useOldLang from '../../../hooks/useOldLang'; +import useChatListEntry from './hooks/useChatListEntry'; +import useTopicContextActions from './hooks/useTopicContextActions'; -import Icon from '../../../common/icons/Icon'; -import LastMessageMeta from '../../../common/LastMessageMeta'; -import TopicIcon from '../../../common/TopicIcon'; -import ConfirmDialog from '../../../ui/ConfirmDialog'; -import ListItem from '../../../ui/ListItem'; -import MuteChatModal from '../../MuteChatModal.async'; -import ChatBadge from '../ChatBadge'; +import Icon from '../../common/icons/Icon'; +import LastMessageMeta from '../../common/LastMessageMeta'; +import TopicIcon from '../../common/TopicIcon'; +import ConfirmDialog from '../../ui/ConfirmDialog'; +import ListItem from '../../ui/ListItem'; +import MuteChatModal from '../MuteChatModal.async'; +import ChatBadge from './ChatBadge'; import styles from './Topic.module.scss'; @@ -165,7 +165,7 @@ const Topic: FC = ({ } openThread({ chatId, threadId: topic.id, shouldReplaceHistory: true }); - if (!chat.isBotForum && !chat.isMonoforum) setViewForumAsMessages({ chatId, isEnabled: false }); + setViewForumAsMessages({ chatId, isEnabled: false }); if (canScrollDown) { scrollMessageListToBottom(); @@ -263,7 +263,7 @@ export default memo(withGlobal( const typingStatus = selectThreadParam(global, chatId, topic.id, 'typingStatus'); const draft = selectDraft(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 { chatId: currentChatId, threadId: currentThreadId } = selectCurrentMessageList(global) || {}; diff --git a/src/components/left/main/forum/AllMessagesTopic.tsx b/src/components/left/main/forum/AllMessagesTopic.tsx deleted file mode 100644 index 3fc7d3292..000000000 --- a/src/components/left/main/forum/AllMessagesTopic.tsx +++ /dev/null @@ -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 ( - -
-
-
-

{lang('BotForumAllTopicTitle')}

-
-
- {lastMessage && ( - - )} -
-
- - {lang('BotForumAllTopicDescription')} - -
-
- - ); -}; - -export default memo(withGlobal( - (global, { chatId }): Complete => { - const lastMessage = selectChatLastMessage(global, chatId, 'all'); - return { - lastMessage, - }; - }, -)(AllMessagesTopic)); diff --git a/src/components/left/main/hooks/useTopicContextActions.ts b/src/components/left/main/hooks/useTopicContextActions.ts index 012631ee0..114741137 100644 --- a/src/components/left/main/hooks/useTopicContextActions.ts +++ b/src/components/left/main/hooks/useTopicContextActions.ts @@ -5,7 +5,6 @@ import type { ApiChat, ApiTopic } from '../../../../api/types'; import type { MenuItemContextAction } from '../../../ui/ListItem'; 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 { compact } from '../../../../util/iteratees'; @@ -49,11 +48,11 @@ export default function useTopicContextActions({ openQuickPreview, } = getActions(); - const canToggleClosed = getCanManageTopic(chat, topic) && !chat.isBotForum; + const canToggleClosed = getCanManageTopic(chat, topic); const canTogglePinned = chat.isCreator || getHasAdminRight(chat, 'manageTopics'); 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', handler: () => { openChatInNewTab({ chatId: chat.id, threadId: topicId }); diff --git a/src/components/middle/HeaderMenuContainer.tsx b/src/components/middle/HeaderMenuContainer.tsx index 3b8d002fa..0e13326f6 100644 --- a/src/components/middle/HeaderMenuContainer.tsx +++ b/src/components/middle/HeaderMenuContainer.tsx @@ -863,8 +863,7 @@ export default memo(withGlobal( const canGift = selectCanGift(global, chatId); const topic = selectTopic(global, chatId, threadId); - // Disable manual creation for bot forums - const canCreateTopic = chat.isForum && !chat.isBotForum && ( + const canCreateTopic = chat.isForum && ( chat.isCreator || !isUserRightBanned(chat, 'manageTopics') || getHasAdminRight(chat, 'manageTopics') ); const canEditTopic = topic && getCanManageTopic(chat, topic); diff --git a/src/components/middle/MessageList.scss b/src/components/middle/MessageList.scss index e86ec9faa..c28d090f5 100644 --- a/src/components/middle/MessageList.scss +++ b/src/components/middle/MessageList.scss @@ -1,8 +1,6 @@ .MessageList { --action-message-bg: var(--pattern-color); - scroll-snap-type: y proximity; - overflow-x: hidden; overflow-y: scroll; flex: 1; @@ -38,10 +36,6 @@ display: none; } - &.no-bottom-snap { - scroll-snap-type: none; - } - .messages-container { display: flex; flex-direction: column; @@ -57,10 +51,6 @@ margin-top: 100vh !important; } - .fab-trigger { - scroll-snap-align: end; - } - @media (max-width: 600px) { width: 100vw; // Patch for an issue on Android when rotating device diff --git a/src/components/middle/MessageList.tsx b/src/components/middle/MessageList.tsx index 86fa09b40..2486fb36a 100644 --- a/src/components/middle/MessageList.tsx +++ b/src/components/middle/MessageList.tsx @@ -14,7 +14,7 @@ import { MESSAGE_LIST_SLICE, SERVICE_NOTIFICATIONS_USER_ID, } from '../../config'; -import { forceMeasure, requestForcedReflow, requestMeasure, requestMutation } from '../../lib/fasterdom/fasterdom'; +import { forceMeasure, requestForcedReflow, requestMeasure } from '../../lib/fasterdom/fasterdom'; import { getIsSavedDialog, getMessageHtmlId, @@ -54,7 +54,6 @@ import { import { selectIsChatRestricted } from '../../global/selectors/chats'; import { selectActiveRestrictionReasons, selectCurrentMessageList } from '../../global/selectors/messages'; import animateScroll, { isAnimatingScroll, restartCurrentScrollAnimation } from '../../util/animateScroll'; -import { IS_FIREFOX } from '../../util/browser/windowEnvironment'; import buildClassName from '../../util/buildClassName'; import { isUserId } from '../../util/entities/ids'; import { orderBy } from '../../util/iteratees'; @@ -146,7 +145,6 @@ type StateProps = { translationLanguage?: string; shouldAutoTranslate?: boolean; isActive?: boolean; - isBotForum?: boolean; shouldScrollToBottom?: boolean; }; @@ -168,19 +166,13 @@ const MESSAGE_REACTIONS_POLLING_INTERVAL = 20 * 1000; const MESSAGE_COMMENTS_POLLING_INTERVAL = 20 * 1000; const MESSAGE_FACT_CHECK_UPDATE_INTERVAL = 5 * 1000; const MESSAGE_STORY_POLLING_INTERVAL = 5 * 60 * 1000; - const BOTTOM_THRESHOLD = 50; -const BOTTOM_SNAP_THRESHOLD = 10; - const UNREAD_DIVIDER_TOP = 10; const SCROLL_DEBOUNCE = 200; const MESSAGE_ANIMATION_DURATION = 500; const BOTTOM_FOCUS_MARGIN = 0.5 * REM; const SELECT_MODE_ANIMATION_DURATION = 200; - 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); @@ -196,7 +188,6 @@ const MessageList: FC = ({ canPost, isSynced, isActive, - isBotForum, shouldScrollToBottom, // eslint-disable-next-line @typescript-eslint/no-shadow isChatMonoforum, @@ -267,7 +258,6 @@ const MessageList: FC = ({ const memoFocusingIdRef = useRef(); const isScrollTopJustUpdatedRef = useRef(false); const shouldAnimateAppearanceRef = useRef(Boolean(lastMessage)); - const scrollSnapDisabledTimerRef = useRef(); const isSavedDialog = getIsSavedDialog(chatId, threadId, currentUserId); const hasOpenChatButton = isSavedDialog && threadId !== ANONYMOUS_USER_ID; @@ -508,33 +498,6 @@ const MessageList: FC = ({ const [getContainerHeight, prevContainerHeightRef] = useContainerHeight(containerRef, canPost && !isSelectModeActive); - const handleWheel = useLastCallback((e: React.WheelEvent) => { - // 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 useEffect(() => { if (!loadMoreAround || !isChatLoaded || isRestricted || focusingId) { @@ -628,30 +591,21 @@ const MessageList: FC = ({ isViewportNewest && wasMessageAdded && (messageIds && messageIds.length < MESSAGE_LIST_SLICE / 2) - && !container.parentElement!.classList.contains(FORCE_MESSAGES_SCROLL_CLASS) + && !container.parentElement!.classList.contains('force-messages-scroll') && forceMeasure(() => ( (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(() => { if (container.parentElement) { - removeExtraClass(container.parentElement, FORCE_MESSAGES_SCROLL_CLASS); + removeExtraClass(container.parentElement, 'force-messages-scroll'); } }, 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(() => { const { scrollTop, scrollHeight, offsetHeight } = container; const scrollOffset = scrollOffsetRef.current; @@ -768,7 +722,6 @@ const MessageList: FC = ({ !isReady && 'is-animating', hasOpenChatButton && 'saved-dialog', isChatProtected && 'hide-on-print', - IS_FIREFOX && NO_BOTTOM_SNAP_CLASS, ); const hasMessages = Boolean((messageIds && messageGroups) || lastMessage); @@ -851,7 +804,6 @@ const MessageList: FC = ({ noAppearanceAnimation={!messageGroups || !shouldAnimateAppearanceRef.current} isQuickPreview={isQuickPreview} canPost={canPost} - isBotForum={isBotForum} shouldScrollToBottom={shouldScrollToBottom} onScrollDownToggle={onScrollDownToggle} onNotchToggle={onNotchToggle} @@ -870,7 +822,6 @@ const MessageList: FC = ({ activeKey={activeKey} shouldCleanup onScroll={handleScroll} - onWheel={handleWheel} onMouseDown={preventMessageInputBlur} > {renderContent()} @@ -986,7 +937,6 @@ export default memo(withGlobal( canTranslate, translationLanguage, shouldAutoTranslate, - isBotForum: chat.isBotForum, shouldScrollToBottom, }; }, diff --git a/src/components/middle/MessageListContent.tsx b/src/components/middle/MessageListContent.tsx index 3e4383a7c..9db6e3ada 100644 --- a/src/components/middle/MessageListContent.tsx +++ b/src/components/middle/MessageListContent.tsx @@ -37,7 +37,6 @@ import usePreviousDeprecated from '../../hooks/usePreviousDeprecated'; import useMessageObservers from './hooks/useMessageObservers'; import useScrollHooks from './hooks/useScrollHooks'; -import Icon from '../common/icons/Icon'; import MiniTable, { type TableEntry } from '../common/MiniTable'; import ActionMessage from './message/ActionMessage'; import Message from './message/Message'; @@ -60,7 +59,6 @@ interface OwnProps { withUsers: boolean; isChannelChat: boolean | undefined; isChatMonoforum?: boolean; - isBotForum?: boolean; isEmptyThread?: boolean; isComments?: boolean; noAvatars: boolean; @@ -101,7 +99,6 @@ const MessageListContent = ({ withUsers, isChannelChat, isChatMonoforum, - isBotForum, noAvatars, containerRef, anchorIdRef, @@ -246,20 +243,6 @@ const MessageListContent = ({ return undefined; }; - const renderBotForumTopicAction = () => { - if (!isBotForum || threadId !== MAIN_THREAD_ID) return undefined; - return ( -
-
- -

{lang('BotForumActionNew')}

- {lang('BotForumActionNewDescription')} - -
-
- ); - }; - const messageCountToAnimate = noAppearanceAnimation ? 0 : messageGroups.reduce((acc, messageGroup) => { return acc + messageGroup.senderGroups.flat().length; }, 0); @@ -465,7 +448,6 @@ const MessageListContent = ({ {shouldRenderAccountInfo && } {dateGroups.flat()} - {isViewportNewest && renderBotForumTopicAction()} {withHistoryTriggers && (
= ({ = ({ }) => { const { clickBotInlineButton } = getActions(); - const lang = useLang(); + const lang = useOldLang(); const [handleMouseEnter, handleMouseLeave] = useMouseInside(isOpen, onClose); const { isKeyboardSingleUse } = message || {}; diff --git a/src/components/middle/composer/helpers/renderKeyboardButtonText.tsx b/src/components/middle/composer/helpers/renderKeyboardButtonText.tsx index 6656137cc..f950f454a 100644 --- a/src/components/middle/composer/helpers/renderKeyboardButtonText.tsx +++ b/src/components/middle/composer/helpers/renderKeyboardButtonText.tsx @@ -7,9 +7,11 @@ import { STARS_ICON_PLACEHOLDER } from '../../../../config'; import { replaceWithTeact } from '../../../../util/replaceWithTeact'; import renderText from '../../../common/helpers/renderText'; +import { type OldLangFn } from '../../../../hooks/useOldLang'; + 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') { return lang('PaymentReceipt'); } diff --git a/src/components/middle/hooks/useMessageObservers.ts b/src/components/middle/hooks/useMessageObservers.ts index ea6330b4c..47c368e90 100644 --- a/src/components/middle/hooks/useMessageObservers.ts +++ b/src/components/middle/hooks/useMessageObservers.ts @@ -83,7 +83,7 @@ export default function useMessageObservers( }); if (!isQuickPreview) { - if (memoFirstUnreadIdRef.current && maxId && maxId >= memoFirstUnreadIdRef.current) { + if (memoFirstUnreadIdRef.current && maxId >= memoFirstUnreadIdRef.current) { markMessageListRead({ maxId }); } diff --git a/src/components/middle/message/ActionMessage.module.scss b/src/components/middle/message/ActionMessage.module.scss index 117b1cc25..d7391ae69 100644 --- a/src/components/middle/message/ActionMessage.module.scss +++ b/src/components/middle/message/ActionMessage.module.scss @@ -283,23 +283,3 @@ font-size: 1rem; 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; -} diff --git a/src/components/middle/message/ActionMessage.tsx b/src/components/middle/message/ActionMessage.tsx index 1a6997ee3..32366f584 100644 --- a/src/components/middle/message/ActionMessage.tsx +++ b/src/components/middle/message/ActionMessage.tsx @@ -40,6 +40,7 @@ import useEnsureMessage from '../../../hooks/useEnsureMessage'; import useFlag from '../../../hooks/useFlag'; import { type ObserveFn, useOnIntersect } from '../../../hooks/useIntersectionObserver'; import useLastCallback from '../../../hooks/useLastCallback'; +import useMessageResizeObserver from '../../../hooks/useResizeMessageObserver'; import useShowTransition from '../../../hooks/useShowTransition'; import { type OnIntersectPinnedMessage } from '../hooks/usePinnedMessage'; import useFluidBackgroundFilter from './hooks/useFluidBackgroundFilter'; @@ -124,11 +125,11 @@ const ActionMessage = ({ hasUnreadReaction, isResizingContainer, scrollTargetPosition, - isAccountFrozen, onIntersectPinnedMessage, observeIntersectionForBottom, observeIntersectionForLoading, observeIntersectionForPlaying, + isAccountFrozen, }: OwnProps & StateProps) => { const { requestConfetti, @@ -167,6 +168,8 @@ const ActionMessage = ({ useOnIntersect(ref, !shouldSkipRender ? observeIntersectionForBottom : undefined); + useMessageResizeObserver(ref, !shouldSkipRender && isLastInList && action.type !== 'channelJoined'); + useEnsureMessage( replyToPeerId || chatId, replyToMsgId, diff --git a/src/components/middle/message/ActionMessageText.tsx b/src/components/middle/message/ActionMessageText.tsx index df6e86d32..f8770963a 100644 --- a/src/components/middle/message/ActionMessageText.tsx +++ b/src/components/middle/message/ActionMessageText.tsx @@ -40,7 +40,6 @@ import { getPinnedMediaValue, renderMessageLink, renderPeerLink, - renderTopicLink, translateWithYou, } from './helpers/messageActions'; @@ -82,6 +81,7 @@ const ActionMessageText = ({ asPreview, }: OwnProps & StateProps) => { const { + openThread, openTelegramLink, openUrl, } = getActions(); @@ -231,15 +231,18 @@ const ActionMessageText = ({ const topicId = selectThreadIdFromMessage(global, message); - const topicLinkContent = ( - <> + const topicLink = ( + openThread({ chatId, threadId: topicId })} + > {iconEmojiId ? : } {NBSP} {renderText(title)} - + ); - const topicLink = renderTopicLink(chatId, Number(topicId), topicLinkContent, asPreview); return lang('ActionTopicCreated', { topic: topicLink }, { withNodes: true }); } @@ -250,8 +253,12 @@ const ActionMessageText = ({ const topicId = selectThreadIdFromMessage(global, message); const currentTopic = selectTopic(global, chatId, topicId); - const topicLinkContent = ( - <> + const topicLink = ( + openThread({ chatId, threadId: topicId })} + > {iconEmojiId && iconEmojiId !== DEFAULT_TOPIC_ICON_ID ? : ( @@ -263,12 +270,17 @@ const ActionMessageText = ({ )} {topicId !== GENERAL_TOPIC_ID && NBSP} {renderText(title || currentTopic?.title || lang('ActionTopicPlaceholder'))} - + ); - const topicLink = renderTopicLink(chatId, Number(topicId), topicLinkContent, asPreview); - const topicPlaceholderLink = renderTopicLink( - chatId, Number(topicId), lang('ActionTopicPlaceholder'), asPreview, + const topicPlaceholderLink = ( + openThread({ chatId, threadId: topicId })} + > + {lang('ActionTopicPlaceholder')} + ); if (isClosed !== undefined) { diff --git a/src/components/middle/message/InlineButtons.tsx b/src/components/middle/message/InlineButtons.tsx index 9a739a82a..a9949165f 100644 --- a/src/components/middle/message/InlineButtons.tsx +++ b/src/components/middle/message/InlineButtons.tsx @@ -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 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 renderKeyboardButtonText from '../composer/helpers/renderKeyboardButtonText'; -import useLang from '../../../hooks/useLang'; +import useOldLang from '../../../hooks/useOldLang'; import Icon from '../../common/icons/Icon'; import Button from '../../ui/Button'; @@ -14,12 +15,12 @@ import Button from '../../ui/Button'; import './InlineButtons.scss'; type OwnProps = { - inlineButtons: ApiKeyboardButton[][]; - onClick: (payload: ApiKeyboardButton) => void; + message: ApiMessage; + onClick: (payload: ActionPayloads['clickBotInlineButton']) => void; }; -const InlineButtons = ({ inlineButtons, onClick }: OwnProps) => { - const lang = useLang(); +const InlineButtons: FC = ({ message, onClick }) => { + const lang = useOldLang(); const renderIcon = (button: ApiKeyboardButton) => { const { type } = button; @@ -65,15 +66,15 @@ const InlineButtons = ({ inlineButtons, onClick }: OwnProps) => { const buttonTexts = useMemo(() => { const texts: TeactNode[][] = []; - inlineButtons.forEach((row) => { + message.inlineButtons!.forEach((row) => { texts.push(row.map((button) => renderKeyboardButtonText(lang, button))); }); return texts; - }, [lang, inlineButtons]); + }, [lang, message.inlineButtons]); return (
- {inlineButtons.map((row, i) => ( + {message.inlineButtons!.map((row, i) => (
{row.map((button, j) => (