diff --git a/src/api/gramjs/apiBuilders/messages.ts b/src/api/gramjs/apiBuilders/messages.ts index 7792ab860..4cee6f120 100644 --- a/src/api/gramjs/apiBuilders/messages.ts +++ b/src/api/gramjs/apiBuilders/messages.ts @@ -235,7 +235,7 @@ export function buildApiMessageWithChatId( }), ...(shouldHideKeyboardButtons && { shouldHideKeyboardButtons, isHideKeyboardSelective }), ...(mtpMessage.viaBotId && { viaBotId: buildApiPeerId(mtpMessage.viaBotId, 'user') }), - ...(replies?.comments && { repliesThreadInfo: buildThreadInfo(replies, mtpMessage.id, chatId) }), + ...(replies && { repliesThreadInfo: buildThreadInfo(replies, mtpMessage.id, chatId) }), ...(postAuthor && { postAuthorTitle: postAuthor }), isProtected, isForwardingAllowed, @@ -1591,20 +1591,18 @@ function buildThreadInfo( messageReplies: GramJs.TypeMessageReplies, messageId: number, chatId: string, ): ApiThreadInfo | undefined { const { - channelId, replies, maxId, readMaxId, recentRepliers, + channelId, replies, maxId, readMaxId, recentRepliers, comments, } = messageReplies; - if (!channelId) { - return undefined; - } - const apiChannelId = buildApiPeerId(channelId, 'channel'); + const apiChannelId = channelId ? buildApiPeerId(channelId, 'channel') : undefined; if (apiChannelId === DELETED_COMMENTS_CHANNEL_ID) { return undefined; } - const isPostThread = chatId !== apiChannelId; + const isPostThread = apiChannelId && chatId !== apiChannelId; return { + isComments: comments, threadId: messageId, ...(isPostThread ? { chatId: apiChannelId, diff --git a/src/api/gramjs/methods/messages.ts b/src/api/gramjs/methods/messages.ts index af95379c6..d4d270db0 100644 --- a/src/api/gramjs/methods/messages.ts +++ b/src/api/gramjs/methods/messages.ts @@ -850,9 +850,9 @@ export async function markMessagesRead({ } export async function requestThreadInfoUpdate({ - chat, threadId, + chat, threadId, originChannelId, }: { - chat: ApiChat; threadId: number; + chat: ApiChat; threadId: number; originChannelId?: string; }) { if (threadId === MAIN_THREAD_ID) { return undefined; @@ -881,15 +881,18 @@ export async function requestThreadInfoUpdate({ return undefined; } + const topMessageId = topMessageResult.messages[topMessageResult.messages.length - 1].id; + onUpdate({ '@type': 'updateThreadInfo', chatId: discussionChatId, - threadId, + threadId: topMessageId, threadInfo: { - threadId, - topMessageId: topMessageResult.messages[topMessageResult.messages.length - 1].id, + threadId: topMessageId, + topMessageId, lastReadInboxMessageId: topMessageResult.readInboxMaxId, messagesCount: (repliesResult instanceof GramJs.messages.ChannelMessages) ? repliesResult.count : undefined, + ...(originChannelId ? { originChannelId } : undefined), }, firstMessageId: repliesResult && 'messages' in repliesResult && repliesResult.messages.length ? repliesResult.messages[0].id @@ -920,6 +923,7 @@ export async function requestThreadInfoUpdate({ const users = topMessageResult.users.map(buildApiUser).filter(Boolean); return { + topMessageId, discussionChatId, users, }; diff --git a/src/api/gramjs/updater.ts b/src/api/gramjs/updater.ts index a21fa3419..f0f1d98a7 100644 --- a/src/api/gramjs/updater.ts +++ b/src/api/gramjs/updater.ts @@ -603,6 +603,14 @@ export function updater(update: Update, originRequest?: GramJs.AnyRequest) { lastReadInboxMessageId: update.readMaxId, }, }); + } else if (update instanceof GramJs.UpdateReadChannelDiscussionOutbox) { + onUpdate({ + '@type': 'updateChat', + id: buildApiPeerId(update.channelId, 'channel'), + chat: { + lastReadOutboxMessageId: update.readMaxId, + }, + }); } else if ( update instanceof GramJs.UpdateDialogPinned && update.peer instanceof GramJs.DialogPeer diff --git a/src/api/types/messages.ts b/src/api/types/messages.ts index 6c02515aa..f9aa08ed3 100644 --- a/src/api/types/messages.ts +++ b/src/api/types/messages.ts @@ -491,6 +491,7 @@ export type ApiReactionCustomEmoji = { export type ApiReaction = ApiReactionEmoji | ApiReactionCustomEmoji; export interface ApiThreadInfo { + isComments?: boolean; threadId: number; chatId: string; topMessageId?: number; diff --git a/src/assets/fonts/icomoon.woff b/src/assets/fonts/icomoon.woff index a4f7b575c..257f0970f 100644 Binary files a/src/assets/fonts/icomoon.woff and b/src/assets/fonts/icomoon.woff differ diff --git a/src/assets/fonts/icomoon.woff2 b/src/assets/fonts/icomoon.woff2 index ec40bd44d..f2b777ee8 100644 Binary files a/src/assets/fonts/icomoon.woff2 and b/src/assets/fonts/icomoon.woff2 differ diff --git a/src/components/common/ChatForumLastMessage.module.scss b/src/components/common/ChatForumLastMessage.module.scss index 462caacde..a9f249f46 100644 --- a/src/components/common/ChatForumLastMessage.module.scss +++ b/src/components/common/ChatForumLastMessage.module.scss @@ -6,6 +6,7 @@ min-width: 0; overflow: hidden; margin-inline-end: 0.5rem; + flex-grow: 1; flex-direction: column; align-items: flex-start; diff --git a/src/components/common/StickerSetModal.tsx b/src/components/common/StickerSetModal.tsx index e5820eaf8..4d56cc6e8 100644 --- a/src/components/common/StickerSetModal.tsx +++ b/src/components/common/StickerSetModal.tsx @@ -13,7 +13,7 @@ import { selectCurrentMessageList, selectIsChatWithSelf, selectIsCurrentUserPremium, selectShouldSchedule, - selectStickerSet, + selectStickerSet, selectThreadInfo, } from '../../global/selectors'; import renderText from './helpers/renderText'; import { copyTextToClipboard } from '../../util/clipboard'; @@ -236,8 +236,10 @@ export default memo(withGlobal( const { chatId, threadId } = currentMessageList || {}; const chat = chatId && selectChat(global, chatId); const sendOptions = chat ? getAllowedAttachmentOptions(chat) : undefined; + const threadInfo = chatId && threadId ? selectThreadInfo(global, chatId, threadId) : undefined; + const isComments = Boolean(threadInfo?.originChannelId); const canSendStickers = Boolean( - chat && threadId && getCanPostInChat(chat, threadId) && sendOptions?.canSendStickers, + chat && threadId && getCanPostInChat(chat, threadId, isComments) && sendOptions?.canSendStickers, ); const isSavedMessages = Boolean(chatId) && selectIsChatWithSelf(global, chatId); diff --git a/src/components/middle/MessageList.tsx b/src/components/middle/MessageList.tsx index 041b7f247..cf9d3f1f5 100644 --- a/src/components/middle/MessageList.tsx +++ b/src/components/middle/MessageList.tsx @@ -28,7 +28,7 @@ import { selectFirstMessageId, selectChatScheduledMessages, selectCurrentMessageIds, - selectIsCurrentUserPremium, selectLastScrollOffset, + selectIsCurrentUserPremium, selectLastScrollOffset, selectThreadInfo, } from '../../global/selectors'; import { isChatChannel, @@ -96,6 +96,7 @@ type StateProps = { messageIds?: number[]; messagesById?: Record; firstUnreadId?: number; + isComments?: boolean; isViewportNewest?: boolean; isRestricted?: boolean; restrictionReason?: ApiRestrictionReason; @@ -144,6 +145,7 @@ const MessageList: FC = ({ messageIds, messagesById, firstUnreadId, + isComments, isViewportNewest, threadFirstMessageId, isRestricted, @@ -604,6 +606,8 @@ const MessageList: FC = ({ ( ? selectChatScheduledMessages(global, chatId) : selectChatMessages(global, chatId); const threadTopMessageId = selectThreadTopMessageId(global, chatId, threadId); + const threadInfo = selectThreadInfo(global, chatId, threadId); if ( threadId !== MAIN_THREAD_ID && !chat?.isForum @@ -687,6 +692,7 @@ export default memo(withGlobal( isBot: Boolean(chatBot), messageIds, messagesById, + isComments: Boolean(threadInfo?.originChannelId), firstUnreadId: selectFirstUnreadId(global, chatId, threadId), isViewportNewest: type !== 'thread' || selectIsViewportNewest(global, chatId, threadId), threadFirstMessageId: selectFirstMessageId(global, chatId, threadId), diff --git a/src/components/middle/MessageListContent.tsx b/src/components/middle/MessageListContent.tsx index d11e40039..2c9622656 100644 --- a/src/components/middle/MessageListContent.tsx +++ b/src/components/middle/MessageListContent.tsx @@ -1,10 +1,12 @@ import type { RefObject } from 'react'; import type { FC } from '../../lib/teact/teact'; import React, { memo } from '../../lib/teact/teact'; +import { getActions } from '../../global'; import type { MessageListType } from '../../global/types'; import { SCHEDULED_WHEN_ONLINE } from '../../config'; +import { MAIN_THREAD_ID } from '../../api/types'; import buildClassName from '../../util/buildClassName'; import { compact } from '../../util/iteratees'; import { formatHumanDate } from '../../util/dateFormat'; @@ -21,8 +23,6 @@ import useMessageObservers from './hooks/useMessageObservers'; import Message from './message/Message'; import SponsoredMessage from './message/SponsoredMessage'; import ActionMessage from './ActionMessage'; -import { getActions } from '../../global'; -import { MAIN_THREAD_ID } from '../../api/types'; interface OwnProps { isCurrentUserPremium?: boolean; @@ -33,6 +33,8 @@ interface OwnProps { isViewportNewest: boolean; isUnread: boolean; withUsers: boolean; + isChannelChat: boolean | undefined; + isComments?: boolean; noAvatars: boolean; containerRef: RefObject; anchorIdRef: { current: string | undefined }; @@ -60,7 +62,9 @@ const MessageListContent: FC = ({ messageGroups, isViewportNewest, isUnread, + isComments, withUsers, + isChannelChat, noAvatars, containerRef, anchorIdRef, @@ -190,6 +194,10 @@ const MessageListContent: FC = ({ // Service notifications saved in cache in previous versions may share the same `previousLocalId` const key = isServiceNotificationMessage(message) ? `${message.date}_${originalId}` : originalId; + const noComments = hasLinkedChat === false || !isChannelChat; + + const isTopicTopMessage = message.id === threadTopMessageId; + return compact([ message.id === memoUnreadDividerBeforeIdRef.current && unreadDivider, = ({ observeIntersectionForPlaying={observeIntersectionForPlaying} album={album} noAvatars={noAvatars} - withAvatar={position.isLastInGroup && withUsers && !isOwn && !(message.id === threadTopMessageId)} + withAvatar={position.isLastInGroup && withUsers && !isOwn && (!isTopicTopMessage || !isComments)} withSenderName={position.isFirstInGroup && withUsers && !isOwn} threadId={threadId} messageListType={type} - noComments={hasLinkedChat === false} + noComments={noComments} + noReplies={!noComments || threadId !== MAIN_THREAD_ID} appearanceOrder={messageCountToAnimate - ++appearanceIndex} isFirstInGroup={position.isFirstInGroup} isLastInGroup={position.isLastInGroup} diff --git a/src/components/middle/MiddleColumn.tsx b/src/components/middle/MiddleColumn.tsx index 0d94c859a..5b0d8ec0a 100644 --- a/src/components/middle/MiddleColumn.tsx +++ b/src/components/middle/MiddleColumn.tsx @@ -40,7 +40,7 @@ import { selectIsUserBlocked, selectPinnedIds, selectReplyingToId, - selectTheme, + selectTheme, selectThreadInfo, } from '../../global/selectors'; import { getCanPostInChat, @@ -630,7 +630,9 @@ export default memo(withGlobal( const pinnedIds = selectPinnedIds(global, chatId, threadId); const { chatId: audioChatId, messageId: audioMessageId } = audioPlayer; - const canPost = chat && getCanPostInChat(chat, threadId); + const threadInfo = selectThreadInfo(global, chatId, threadId); + const isComments = Boolean(threadInfo?.originChannelId); + const canPost = chat && getCanPostInChat(chat, threadId, isComments); const isBotNotStarted = selectIsChatBotNotStarted(global, chatId); const isPinnedMessageList = messageListType === 'pinned'; const isScheduledMessageList = messageListType === 'scheduled'; diff --git a/src/components/middle/MiddleHeader.tsx b/src/components/middle/MiddleHeader.tsx index 784f4b65b..4f124f379 100644 --- a/src/components/middle/MiddleHeader.tsx +++ b/src/components/middle/MiddleHeader.tsx @@ -89,6 +89,7 @@ type StateProps = { isRightColumnShown?: boolean; audioMessage?: ApiMessage; messagesCount?: number; + isComments?: boolean; isChatWithSelf?: boolean; lastSyncTime?: number; hasButtonInHeader?: boolean; @@ -116,6 +117,7 @@ const MiddleHeader: FC = ({ audioMessage, chat, messagesCount, + isComments, isChatWithSelf, lastSyncTime, hasButtonInHeader, @@ -340,7 +342,8 @@ const MiddleHeader: FC = ({ {renderBackButton()}

{messagesCount !== undefined ? ( - messageListType === 'thread' ? (lang('CommentsCount', messagesCount, 'i')) + messageListType === 'thread' ? ( + lang(isComments ? 'CommentsCount' : 'Replies', messagesCount, 'i')) : messageListType === 'pinned' ? (lang('PinnedMessagesCount', messagesCount, 'i')) : messageListType === 'scheduled' ? ( isChatWithSelf ? lang('Reminders') : lang('messages', messagesCount, 'i') @@ -422,13 +425,15 @@ const MiddleHeader: FC = ({ {renderInfo()} - + } + chatId={chatId} + /> + )} {shouldRenderPinnedMessage && renderingPinnedMessage && ( ( const pinnedMessageId = selectThreadTopMessageId(global, chatId, threadId); const message = pinnedMessageId ? selectChatMessage(global, chatId, pinnedMessageId) : undefined; const topMessageSender = message ? selectForwardedSender(global, message) : undefined; + const threadInfo = selectThreadInfo(global, chatId, threadId); return { ...state, pinnedMessageIds: pinnedMessageId, canUnpin: false, topMessageSender, + isComments: Boolean(threadInfo?.originChannelId), }; } diff --git a/src/components/middle/message/CommentButton.tsx b/src/components/middle/message/CommentButton.tsx index 5b395557c..e465c8ef8 100644 --- a/src/components/middle/message/CommentButton.tsx +++ b/src/components/middle/message/CommentButton.tsx @@ -25,16 +25,16 @@ const CommentButton: FC = ({ threadInfo, disabled, }) => { - const { openChat } = getActions(); + const { openComments } = getActions(); const lang = useLang(); const { - threadId, chatId, messagesCount, lastMessageId, lastReadInboxMessageId, recentReplierIds, + threadId, chatId, messagesCount, lastMessageId, lastReadInboxMessageId, recentReplierIds, originChannelId, } = threadInfo; const handleClick = useCallback(() => { - openChat({ id: chatId, threadId }); - }, [openChat, chatId, threadId]); + openComments({ id: chatId, threadId, originChannelId }); + }, [openComments, chatId, threadId, originChannelId]); const recentRepliers = useMemo(() => { if (!recentReplierIds?.length) { diff --git a/src/components/middle/message/ContextMenuContainer.tsx b/src/components/middle/message/ContextMenuContainer.tsx index b9ba2718e..3c69f9c94 100644 --- a/src/components/middle/message/ContextMenuContainer.tsx +++ b/src/components/middle/message/ContextMenuContainer.tsx @@ -6,7 +6,7 @@ import { getActions, getGlobal, withGlobal } from '../../../global'; import type { MessageListType } from '../../../global/types'; import type { - ApiAvailableReaction, ApiStickerSetInfo, ApiMessage, ApiStickerSet, ApiChatReactions, ApiReaction, + ApiAvailableReaction, ApiStickerSetInfo, ApiMessage, ApiStickerSet, ApiChatReactions, ApiReaction, ApiThreadInfo, } from '../../../api/types'; import type { IAlbum, IAnchorPosition } from '../../../types'; @@ -48,8 +48,10 @@ export type OwnProps = { album?: IAlbum; anchor: IAnchorPosition; messageListType: MessageListType; + noReplies?: boolean; onClose: () => void; onCloseAnimationEnd: () => void; + repliesThreadInfo?: ApiThreadInfo; }; type StateProps = { @@ -107,6 +109,7 @@ const ContextMenuContainer: FC = ({ canReschedule, canReply, canPin, + repliesThreadInfo, canUnpin, canDelete, canReport, @@ -129,11 +132,13 @@ const ContextMenuContainer: FC = ({ canRevote, canClosePoll, activeDownloads, + noReplies, canShowSeenBy, canScheduleUntilOnline, threadId, }) => { const { + openChat, setReplyingToId, setEditingId, pinMessage, @@ -254,6 +259,14 @@ const ContextMenuContainer: FC = ({ closeMenu(); }, [setReplyingToId, message.id, closeMenu]); + const handleOpenThread = useCallback(() => { + openChat({ + id: message.chatId, + threadId: message.id, + }); + closeMenu(); + }, [closeMenu, message.chatId, message.id, openChat]); + const handleEdit = useCallback(() => { setEditingId({ messageId: message.id }); closeMenu(); @@ -411,6 +424,7 @@ const ContextMenuContainer: FC = ({ canDelete={canDelete} canReport={canReport} canPin={canPin} + repliesThreadInfo={repliesThreadInfo} canUnpin={canUnpin} canEdit={canEdit} canForward={canForward} @@ -428,6 +442,8 @@ const ContextMenuContainer: FC = ({ customEmojiSets={customEmojiSets} isDownloading={isDownloading} seenByRecentUsers={seenByRecentUsers} + noReplies={noReplies} + onOpenThread={handleOpenThread} onReply={handleReply} onEdit={handleEdit} onPin={handlePin} diff --git a/src/components/middle/message/Message.tsx b/src/components/middle/message/Message.tsx index aa404d5a9..c2b6540d5 100644 --- a/src/components/middle/message/Message.tsx +++ b/src/components/middle/message/Message.tsx @@ -170,6 +170,7 @@ type OwnProps = threadId: number; messageListType: MessageListType; noComments: boolean; + noReplies: boolean; appearanceOrder: number; memoFirstUnreadIdRef: { current: number | undefined }; } @@ -265,6 +266,7 @@ const Message: FC = ({ withAvatar, withSenderName, noComments, + noReplies, appearanceOrder, isFirstInGroup, isPremium, @@ -466,6 +468,7 @@ const Message: FC = ({ handleAudioPlay, handleAlbumMediaClick, handleMetaClick, + handleOpenThread, handleReadMedia, handleCancelUpload, handleVoteSend, @@ -523,7 +526,7 @@ const Message: FC = ({ message.hasUnreadMention && 'has-unread-mention', isSelected && 'is-selected', isInSelectMode && 'is-in-selection-mode', - isThreadTop && 'is-thread-top', + isThreadTop && !withAvatar && 'is-thread-top', Boolean(message.inlineButtons) && 'has-inline-buttons', isSwiped && 'is-swiped', transitionClassNames, @@ -546,7 +549,7 @@ const Message: FC = ({ isCustomShape, isLastInGroup, asForwarded, - hasThread, + hasThread: hasThread && !noComments, forceSenderName, hasComments: repliesThreadInfo && repliesThreadInfo.messagesCount > 0, hasActionButton: canForward || canFocus, @@ -708,11 +711,14 @@ const Message: FC = ({ const meta = ( ); @@ -1200,6 +1206,8 @@ const Message: FC = ({ messageListType={messageListType} onClose={handleContextMenuClose} onCloseAnimationEnd={handleContextMenuHide} + repliesThreadInfo={repliesThreadInfo} + noReplies={noReplies} /> )} diff --git a/src/components/middle/message/MessageContextMenu.tsx b/src/components/middle/message/MessageContextMenu.tsx index fe47f91a2..3c190ac36 100644 --- a/src/components/middle/message/MessageContextMenu.tsx +++ b/src/components/middle/message/MessageContextMenu.tsx @@ -5,7 +5,14 @@ import { getActions } from '../../../global'; import type { FC } from '../../../lib/teact/teact'; import type { - ApiAvailableReaction, ApiChatReactions, ApiMessage, ApiReaction, ApiSponsoredMessage, ApiStickerSet, ApiUser, + ApiAvailableReaction, + ApiChatReactions, + ApiMessage, + ApiReaction, + ApiSponsoredMessage, + ApiStickerSet, + ApiThreadInfo, + ApiUser, } from '../../../api/types'; import type { IAnchorPosition } from '../../../types'; @@ -39,6 +46,7 @@ type OwnProps = { maxUniqueReactions?: number; canReschedule?: boolean; canReply?: boolean; + repliesThreadInfo?: ApiThreadInfo; canPin?: boolean; canUnpin?: boolean; canDelete?: boolean; @@ -62,9 +70,11 @@ type OwnProps = { isDownloading?: boolean; canShowSeenBy?: boolean; seenByRecentUsers?: ApiUser[]; + noReplies?: boolean; hasCustomEmoji?: boolean; customEmojiSets?: ApiStickerSet[]; onReply?: () => void; + onOpenThread?: VoidFunction; onEdit?: () => void; onPin?: () => void; onUnpin?: () => void; @@ -110,6 +120,7 @@ const MessageContextMenu: FC = ({ canBuyPremium, canReply, canEdit, + noReplies, canPin, canUnpin, canDelete, @@ -125,6 +136,7 @@ const MessageContextMenu: FC = ({ canRevote, canClosePoll, isDownloading, + repliesThreadInfo, canShowSeenBy, canShowReactionsCount, canShowReactionList, @@ -132,6 +144,7 @@ const MessageContextMenu: FC = ({ hasCustomEmoji, customEmojiSets, onReply, + onOpenThread, onEdit, onPin, onUnpin, @@ -294,6 +307,11 @@ const MessageContextMenu: FC = ({ {lang('MessageScheduleEditTime')} )} {canReply && {lang('Reply')}} + {!noReplies && Boolean(repliesThreadInfo?.messagesCount) && ( + + {lang('Conversation.ContextViewReplies', repliesThreadInfo!.messagesCount, 'i')} + + )} {canEdit && {lang('Edit')}} {canFaveSticker && ( {lang('AddToFavorites')} diff --git a/src/components/middle/message/MessageMeta.scss b/src/components/middle/message/MessageMeta.scss index 53727a123..7259f3536 100644 --- a/src/components/middle/message/MessageMeta.scss +++ b/src/components/middle/message/MessageMeta.scss @@ -16,7 +16,8 @@ .message-time, .message-imported, .message-signature, - .message-views { + .message-views, + .message-replies { font-size: 0.75rem; white-space: nowrap; } @@ -55,6 +56,12 @@ top: -0.0625rem; } + .icon-reply-filled { + margin-left: 0.125rem; + margin-right: 0.375rem; + font-size: 0.75rem; + } + .has-solid-background & { color: rgba(var(--color-text-meta-rgb), 0.75); background: none; diff --git a/src/components/middle/message/MessageMeta.tsx b/src/components/middle/message/MessageMeta.tsx index dead41eae..c8cc003d3 100644 --- a/src/components/middle/message/MessageMeta.tsx +++ b/src/components/middle/message/MessageMeta.tsx @@ -2,7 +2,9 @@ import type { FC } from '../../../lib/teact/teact'; import React, { memo, useMemo } from '../../../lib/teact/teact'; import { getActions } from '../../../global'; -import type { ApiAvailableReaction, ApiMessage, ApiMessageOutgoingStatus } from '../../../api/types'; +import type { + ApiAvailableReaction, ApiMessage, ApiMessageOutgoingStatus, ApiThreadInfo, +} from '../../../api/types'; import { formatDateTimeToString, formatTime } from '../../../util/dateFormat'; import { formatIntegerCompact } from '../../../util/textFormat'; @@ -13,6 +15,7 @@ import useFlag from '../../../hooks/useFlag'; import buildClassName from '../../../util/buildClassName'; import MessageOutgoingStatus from '../../common/MessageOutgoingStatus'; +import AnimatedCounter from '../../common/AnimatedCounter'; import './MessageMeta.scss'; @@ -22,7 +25,10 @@ type OwnProps = { outgoingStatus?: ApiMessageOutgoingStatus; signature?: string; availableReactions?: ApiAvailableReaction[]; + noReplies?: boolean; + repliesThreadInfo?: ApiThreadInfo; onClick: (e: React.MouseEvent) => void; + onOpenThread: () => void; }; const MessageMeta: FC = ({ @@ -30,7 +36,10 @@ const MessageMeta: FC = ({ outgoingStatus, signature, withReactionOffset, + repliesThreadInfo, + noReplies, onClick, + onOpenThread, }) => { const { showNotification } = getActions(); const lang = useLang(); @@ -44,6 +53,11 @@ const MessageMeta: FC = ({ }); }; + function handleOpenThread(e: React.MouseEvent) { + e.stopPropagation(); + onOpenThread(); + } + const title = useMemo(() => { if (!isActivated) return undefined; const createDateTime = formatDateTimeToString(message.date * 1000, lang.code); @@ -84,6 +98,14 @@ const MessageMeta: FC = ({ )} + {!noReplies && Boolean(repliesThreadInfo?.messagesCount) && ( + + + + + + + )} {signature && ( {renderText(signature)} )} diff --git a/src/components/middle/message/hooks/useInnerHandlers.ts b/src/components/middle/message/hooks/useInnerHandlers.ts index 79c8185dd..a7f904050 100644 --- a/src/components/middle/message/hooks/useInnerHandlers.ts +++ b/src/components/middle/message/hooks/useInnerHandlers.ts @@ -160,6 +160,13 @@ export default function useInnerHandlers( selectMessage(e, groupedId); }, [selectMessage, groupedId]); + const handleOpenThread = useCallback(() => { + openChat({ + id: message.chatId, + threadId: message.id, + }); + }, [message.chatId, message.id, openChat]); + const handleTopicChipClick = useCallback(() => { if (!messageTopic) return; focusMessage({ @@ -178,6 +185,7 @@ export default function useInnerHandlers( handleAudioPlay, handleAlbumMediaClick, handleMetaClick: selectWithGroupedId, + handleOpenThread, handleReadMedia, handleCancelUpload, handleVoteSend, diff --git a/src/components/right/GifSearch.tsx b/src/components/right/GifSearch.tsx index 0efedb079..6d470485d 100644 --- a/src/components/right/GifSearch.tsx +++ b/src/components/right/GifSearch.tsx @@ -11,7 +11,7 @@ import { selectIsChatWithBot, selectCurrentMessageList, selectCanScheduleUntilOnline, - selectIsChatWithSelf, + selectIsChatWithSelf, selectThreadInfo, } from '../../global/selectors'; import { getAllowedAttachmentOptions, getCanPostInChat } from '../../global/helpers'; import buildClassName from '../../util/buildClassName'; @@ -155,7 +155,9 @@ export default memo(withGlobal( const chat = chatId ? selectChat(global, chatId) : undefined; const isChatWithBot = chat ? selectIsChatWithBot(global, chat) : undefined; const isSavedMessages = Boolean(chatId) && selectIsChatWithSelf(global, chatId); - const canPostInChat = Boolean(chat) && Boolean(threadId) && getCanPostInChat(chat, threadId); + const threadInfo = chatId && threadId ? selectThreadInfo(global, chatId, threadId) : undefined; + const isComments = Boolean(threadInfo?.originChannelId); + const canPostInChat = Boolean(chat) && Boolean(threadId) && getCanPostInChat(chat, threadId, isComments); return { query, diff --git a/src/components/right/management/ManageUser.tsx b/src/components/right/management/ManageUser.tsx index a5a76db72..d3bb561a4 100644 --- a/src/components/right/management/ManageUser.tsx +++ b/src/components/right/management/ManageUser.tsx @@ -276,9 +276,9 @@ const ManageUser: FC = ({ export default memo(withGlobal( (global, { userId }): StateProps => { const user = selectUser(global, userId); - const chat = selectChat(global, userId)!; + const chat = selectChat(global, userId); const { progress } = selectTabState(global).management; - const isMuted = selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)); + const isMuted = chat && selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)); return { user, progress, isMuted, diff --git a/src/global/actions/api/chats.ts b/src/global/actions/api/chats.ts index 51775d6b1..36c5a69b8 100644 --- a/src/global/actions/api/chats.ts +++ b/src/global/actions/api/chats.ts @@ -55,7 +55,7 @@ import { selectChatFolder, selectSupportChat, selectChatByUsername, selectCurrentMessageList, selectThreadInfo, selectCurrentChat, selectLastServiceNotification, selectVisibleUsers, selectUserByPhoneNumber, selectDraft, selectThreadTopMessageId, - selectTabState, selectThread, + selectTabState, selectThread, selectThreadOriginChat, } from '../../selectors'; import { buildCollectionByKey, omit } from '../../../util/iteratees'; import { debounce, pause, throttle } from '../../../util/schedulers'; @@ -142,10 +142,38 @@ addActionHandler('openChat', (global, actions, payload): ActionReturnType => { actions.requestChatUpdate({ chatId: id }); } + if (threadId !== MAIN_THREAD_ID) { + actions.requestThreadInfoUpdate({ chatId: id, threadId }); + } +}); + +addActionHandler('openComments', async (global, actions, payload): Promise => { + const { + id, threadId, originChannelId, tabId = getCurrentTabId(), + } = payload; + if (threadId !== MAIN_THREAD_ID) { const topMessageId = selectThreadTopMessageId(global, id, threadId); if (!topMessageId) { - actions.requestThreadInfoUpdate({ chatId: id, threadId }); + const chat = selectThreadOriginChat(global, id, threadId); + if (!chat) { + return; + } + + actions.openChat({ id: TMP_CHAT_ID, tabId }); + + const result = await callApi('requestThreadInfoUpdate', { chat, threadId, originChannelId }); + if (!result) { + actions.openPreviousChat({ tabId }); + return; + } + global = getGlobal(); + global = addUsers(global, buildCollectionByKey(result.users, 'id')); + setGlobal(global); + + actions.openChat({ id, threadId: result.topMessageId, tabId }); + } else { + actions.openChat({ id, threadId: topMessageId, tabId }); } } }); diff --git a/src/global/actions/api/messages.ts b/src/global/actions/api/messages.ts index 24b67c8d6..4165c3635 100644 --- a/src/global/actions/api/messages.ts +++ b/src/global/actions/api/messages.ts @@ -68,7 +68,6 @@ import { selectReplyingToId, selectEditingId, selectDraft, - selectThreadOriginChat, selectThreadTopMessageId, selectEditingScheduledId, selectEditingMessage, @@ -239,12 +238,17 @@ addActionHandler('sendMessage', (global, actions, payload): ActionReturnType => } const chat = selectChat(global, chatId)!; - const replyingToTopId = chat.isForum ? selectThreadTopMessageId(global, chatId, threadId) : undefined; + const replyingToId = selectReplyingToId(global, chatId, threadId); + const replyingToMessage = replyingToId ? selectChatMessage(global, chatId, replyingToId) : undefined; + + const replyingToTopId = chat.isForum + ? selectThreadTopMessageId(global, chatId, threadId) + : replyingToMessage?.replyToTopMessageId || replyingToMessage?.replyToMessageId; const params = { ...payload, chat, - replyingTo: selectReplyingToId(global, chatId, threadId), + replyingTo: replyingToId, replyingToTopId, noWebPage: selectNoWebPage(global, chatId, threadId), sendAs: selectSendAs(global, chatId), @@ -549,7 +553,7 @@ addActionHandler('markMessageListRead', (global, actions, payload): ActionReturn } const { chatId, threadId } = currentMessageList; - const chat = selectThreadOriginChat(global, chatId, threadId); + const chat = selectChat(global, chatId); if (!chat) { return undefined; } @@ -853,7 +857,7 @@ addActionHandler('rescheduleMessage', (global, actions, payload): ActionReturnTy addActionHandler('requestThreadInfoUpdate', async (global, actions, payload): Promise => { const { chatId, threadId } = payload; - const chat = selectThreadOriginChat(global, chatId, threadId); + const chat = selectChat(global, chatId); if (!chat) { return; } @@ -939,7 +943,7 @@ async function loadViewportMessages( global = getGlobal(); const result = await callApi('fetchMessages', { - chat: selectThreadOriginChat(global, chatId, threadId)!, + chat: selectChat(global, chatId)!, offsetId, addOffset, limit: MESSAGE_LIST_SLICE, diff --git a/src/global/actions/api/sync.ts b/src/global/actions/api/sync.ts index 2eee3ff48..752da5b48 100644 --- a/src/global/actions/api/sync.ts +++ b/src/global/actions/api/sync.ts @@ -105,8 +105,6 @@ async function loadAndReplaceMessages(global: T, actions: const activeThreadId = currentThreadId || MAIN_THREAD_ID; const threadInfo = currentThreadId && currentChatId ? selectThreadInfo(global, currentChatId, currentThreadId) : undefined; - // TODO Fix comments chat id, or refetch chat thread here - const activeCurrentChatId = threadInfo?.originChannelId || currentChatId; // Memoize drafts const draftChatIds = Object.keys(global.messages.byChatId); // eslint-disable-next-line @typescript-eslint/no-loop-func @@ -119,14 +117,14 @@ async function loadAndReplaceMessages(global: T, actions: return acc; }, {}); - const currentChat = activeCurrentChatId ? global.chats.byId[activeCurrentChatId] : undefined; - if (activeCurrentChatId && currentChat) { + const currentChat = currentChatId ? global.chats.byId[currentChatId] : undefined; + if (currentChatId && currentChat) { const result = await loadTopMessages(currentChat, activeThreadId, threadInfo?.lastReadInboxMessageId); global = getGlobal(); const { chatId: newCurrentChatId } = selectCurrentMessageList(global, tabId) || {}; if (result && newCurrentChatId === currentChatId) { - const currentChatMessages = selectChatMessages(global, activeCurrentChatId); + const currentChatMessages = selectChatMessages(global, currentChatId); const localMessages = currentChatId === SERVICE_NOTIFICATIONS_USER_ID ? global.serviceNotifications.filter(({ isDeleted }) => !isDeleted).map(({ message }) => message) : []; @@ -158,18 +156,20 @@ async function loadAndReplaceMessages(global: T, actions: wasReset = true; } - global = addChatMessagesById(global, activeCurrentChatId, byId); - global = updateListedIds(global, activeCurrentChatId, activeThreadId, listedIds); + global = addChatMessagesById(global, currentChatId, byId); + global = updateListedIds(global, currentChatId, activeThreadId, listedIds); // eslint-disable-next-line @typescript-eslint/no-loop-func Object.values(global.byTabId).forEach(({ id: otherTabId }) => { const { chatId: otherChatId, threadId: otherThreadId } = selectCurrentMessageList(global, otherTabId) || {}; - if (otherChatId === activeCurrentChatId && otherThreadId === activeThreadId) { - global = safeReplaceViewportIds(global, activeCurrentChatId, activeThreadId, listedIds, otherTabId); + if (otherChatId === currentChatId && otherThreadId === activeThreadId) { + global = safeReplaceViewportIds(global, currentChatId, activeThreadId, listedIds, otherTabId); } }); global = updateChats(global, buildCollectionByKey(result.chats, 'id')); global = updateUsers(global, buildCollectionByKey(result.users, 'id')); - global = updateThreadInfos(global, activeCurrentChatId, result.repliesThreadInfos); + if (result.repliesThreadInfos.length) { + global = updateThreadInfos(global, currentChatId, result.repliesThreadInfos); + } areMessagesLoaded = true; } @@ -184,10 +184,10 @@ async function loadAndReplaceMessages(global: T, actions: setGlobal(global); if (currentChat?.isForum) { - actions.loadTopics({ chatId: activeCurrentChatId!, force: true }); + actions.loadTopics({ chatId: currentChatId!, force: true }); if (currentThreadId && currentThreadId !== MAIN_THREAD_ID) { actions.loadTopicById({ - chatId: activeCurrentChatId!, topicId: currentThreadId, shouldCloseChatOnError: true, + chatId: currentChatId!, topicId: currentThreadId, shouldCloseChatOnError: true, }); } } diff --git a/src/global/actions/apiUpdaters/chats.ts b/src/global/actions/apiUpdaters/chats.ts index c16abdde4..d44a5b943 100644 --- a/src/global/actions/apiUpdaters/chats.ts +++ b/src/global/actions/apiUpdaters/chats.ts @@ -1,9 +1,10 @@ import { addActionHandler, getGlobal, setGlobal } from '../../index'; +import type { ApiUpdateChat } from '../../../api/types'; import { MAIN_THREAD_ID } from '../../../api/types'; import { ARCHIVED_FOLDER_ID, MAX_ACTIVE_PINNED_CHATS } from '../../../config'; -import { buildCollectionByKey, pick } from '../../../util/iteratees'; +import { buildCollectionByKey, omit, pick } from '../../../util/iteratees'; import { closeMessageNotifications, notifyAboutMessage } from '../../../util/notifications'; import { updateChat, @@ -28,7 +29,15 @@ const TYPING_STATUS_CLEAR_DELAY = 6000; // 6 seconds addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { switch (update['@type']) { case 'updateChat': { - const { isForum: prevIsForum } = selectChat(global, update.id) || {}; + const { isForum: prevIsForum, lastReadOutboxMessageId } = selectChat(global, update.id) || {}; + + if (update.chat.lastReadOutboxMessageId && lastReadOutboxMessageId + && update.chat.lastReadOutboxMessageId < lastReadOutboxMessageId) { + update = { + ...update, + chat: omit(update.chat, ['lastReadInboxMessageId']), + }; + } global = updateChat(global, update.id, update.chat, update.newProfilePhoto); setGlobal(global); @@ -47,8 +56,10 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { Object.values(global.byTabId).forEach(({ id: tabId }) => { const { chatId: currentChatId } = selectCurrentMessageList(global, tabId) || {}; + const chatUpdate = update as ApiUpdateChat; // The property `isForum` was changed in another client - if (currentChatId === update.id && 'isForum' in update.chat && prevIsForum !== update.chat.isForum) { + if (currentChatId === chatUpdate.id + && 'isForum' in chatUpdate.chat && prevIsForum !== chatUpdate.chat.isForum) { if (prevIsForum) { actions.closeForumPanel({ tabId }); } diff --git a/src/global/actions/apiUpdaters/messages.ts b/src/global/actions/apiUpdaters/messages.ts index b940dc36a..eecff8ce2 100644 --- a/src/global/actions/apiUpdaters/messages.ts +++ b/src/global/actions/apiUpdaters/messages.ts @@ -108,7 +108,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { } const { threadInfo } = selectThreadByMessage(global, message as ApiMessage) || {}; - if (threadInfo) { + if (threadInfo && !isLocal) { actions.requestThreadInfoUpdate({ chatId, threadId: threadInfo.threadId }); } @@ -362,6 +362,17 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => { actions.loadTopicById({ chatId, topicId: threadId }); } + // Update reply thread last read message id if already read in main thread + if (threadInfo.topMessageId === threadId && !chat?.isForum) { + const lastReadInboxMessageId = chat?.lastReadInboxMessageId; + const lastReadInboxMessageIdInThread = newThreadInfo.lastReadInboxMessageId || lastReadInboxMessageId; + if (lastReadInboxMessageId && lastReadInboxMessageIdInThread) { + global = updateThreadInfo(global, chatId, threadId, { + lastReadInboxMessageId: Math.max(lastReadInboxMessageIdInThread, lastReadInboxMessageId), + }); + } + } + setGlobal(global); break; @@ -758,8 +769,13 @@ function updateListedAndViewportIds( global = replaceThreadParam(global, chatId, threadInfo.threadId, 'threadInfo', { ...threadInfo, lastMessageId: message.id, - messagesCount: (threadInfo.messagesCount || 0) + 1, }); + + if (!isMessageLocal(message)) { + global = updateThreadInfo(global, chatId, threadInfo.threadId, { + messagesCount: (threadInfo.messagesCount || 0) + 1, + }); + } } if (isUnreadChatNotLoaded) { diff --git a/src/global/cache.ts b/src/global/cache.ts index 7025cf7b9..db29b384c 100644 --- a/src/global/cache.ts +++ b/src/global/cache.ts @@ -3,7 +3,7 @@ import { addCallback, removeCallback } from '../lib/teact/teactn'; import { addActionHandler, getGlobal } from './index'; -import type { ActionReturnType, GlobalState } from './types'; +import type { ActionReturnType, GlobalState, MessageList } from './types'; import type { ApiChat, ApiUser } from '../api/types'; import { MAIN_THREAD_ID } from '../api/types'; @@ -27,7 +27,7 @@ import { } from '../util/iteratees'; import { selectChat, - selectCurrentMessageList, + selectCurrentMessageList, selectThreadOriginChat, selectVisibleUsers, } from './selectors'; import { hasStoredSession } from '../util/sessions'; @@ -459,8 +459,19 @@ function reduceChats(global: T): GlobalState['chats'] { const { chats: { byId }, currentUserId } = global; const currentChatIds = compact( Object.values(global.byTabId) - .map(({ id: tabId }) => selectCurrentMessageList(global, tabId)), - ).map(({ chatId }) => chatId).filter((chatId) => isUserId(chatId)); + .flatMap(({ id: tabId }): MessageList[] | undefined => { + const messageList = selectCurrentMessageList(global, tabId); + if (!messageList) return undefined; + + const { chatId, threadId } = messageList; + const origin = selectThreadOriginChat(global, chatId, threadId); + return origin ? [{ + chatId: origin.id, + threadId: MAIN_THREAD_ID, + type: 'thread', + }, messageList] : [messageList]; + }), + ).map(({ chatId }) => chatId); const idsToSave = unique([ ...currentUserId ? [currentUserId] : [], diff --git a/src/global/helpers/chats.ts b/src/global/helpers/chats.ts index ca5982b63..dc55a7b8f 100644 --- a/src/global/helpers/chats.ts +++ b/src/global/helpers/chats.ts @@ -177,7 +177,7 @@ export function isUserRightBanned(chat: ApiChat, key: keyof ApiChatBannedRights) ); } -export function getCanPostInChat(chat: ApiChat, threadId: number) { +export function getCanPostInChat(chat: ApiChat, threadId: number, isComments?: boolean) { if (threadId !== MAIN_THREAD_ID) { if (chat.isForum) { if (chat.isNotJoined) { @@ -189,10 +189,10 @@ export function getCanPostInChat(chat: ApiChat, threadId: number) { return false; } } - return true; // TODO[forums] legacy value, check that again } - if (chat.isRestricted || chat.isForbidden || chat.migratedTo || chat.isNotJoined || isChatWithRepliesBot(chat.id)) { + if (chat.isRestricted || chat.isForbidden || chat.migratedTo + || (!isComments && chat.isNotJoined) || isChatWithRepliesBot(chat.id)) { return false; } diff --git a/src/global/init.ts b/src/global/init.ts index 94621cef1..4c3b2e33a 100644 --- a/src/global/init.ts +++ b/src/global/init.ts @@ -10,7 +10,6 @@ import { cloneDeep } from '../util/iteratees'; import { replaceTabThreadParam, replaceThreadParam, updatePasscodeSettings } from './reducers'; import { clearStoredSession } from '../util/sessions'; import { parseLocationHash } from '../util/routing'; -import { MAIN_THREAD_ID } from '../api/types'; import { selectTabState, selectThreadParam } from './selectors'; import { Bundles, loadBundle } from '../util/moduleLoader'; import { getCurrentTabId, reestablishMasterToSelf } from '../util/establishMultitabRole'; @@ -68,20 +67,24 @@ addActionHandler('init', (global, actions, payload): ActionReturnType => { } Object.keys(global.messages.byChatId).forEach((chatId) => { - const lastViewportIds = selectThreadParam(global, chatId, MAIN_THREAD_ID, 'lastViewportIds'); - // Check if migration from previous version is faulty - if (!lastViewportIds?.every((id) => isLocalMessageId(id) || global.messages.byChatId[chatId]?.byId[id])) { - global = replaceThreadParam(global, chatId, MAIN_THREAD_ID, 'lastViewportIds', undefined); - return; - } - global = replaceTabThreadParam( - global, - chatId, - MAIN_THREAD_ID, - 'viewportIds', - lastViewportIds, - tabId, - ); + const threadsById = global.messages.byChatId[chatId].threadsById; + Object.keys(threadsById).forEach((thread) => { + const threadId = Number(thread); + const lastViewportIds = selectThreadParam(global, chatId, threadId, 'lastViewportIds'); + // Check if migration from previous version is faulty + if (!lastViewportIds?.every((id) => isLocalMessageId(id) || global.messages.byChatId[chatId]?.byId[id])) { + global = replaceThreadParam(global, chatId, threadId, 'lastViewportIds', undefined); + return; + } + global = replaceTabThreadParam( + global, + chatId, + threadId, + 'viewportIds', + lastViewportIds, + tabId, + ); + }); }); // Temporary state fix diff --git a/src/global/reducers/messages.ts b/src/global/reducers/messages.ts index ff39697ff..b9be302be 100644 --- a/src/global/reducers/messages.ts +++ b/src/global/reducers/messages.ts @@ -30,6 +30,7 @@ import { } from '../../util/iteratees'; import { updateTabState } from './tabs'; import { getCurrentTabId } from '../../util/establishMultitabRole'; +import { isLocalMessageId } from '../helpers'; type MessageStoreSections = { byId: Record; @@ -254,7 +255,7 @@ export function deleteChatMessages( messageIds.forEach((messageId) => { if (listedIds?.includes(messageId)) { listedIds = listedIds.filter((id) => id !== messageId); - if (newMessageCount !== undefined) newMessageCount -= 1; + if (newMessageCount !== undefined && !isLocalMessageId(messageId)) newMessageCount -= 1; } if (pinnedIds?.includes(messageId)) { diff --git a/src/global/selectors/messages.ts b/src/global/selectors/messages.ts index a6b66a41d..55a9497f5 100644 --- a/src/global/selectors/messages.ts +++ b/src/global/selectors/messages.ts @@ -482,8 +482,14 @@ export function selectThreadIdFromMessage(global: T, mess return message.id; } - // TODO ignore only basic group if reply threads are added - if (!chat?.isForum) return MAIN_THREAD_ID; + if (!chat?.isForum) { + if (chat && isChatBasicGroup(chat)) return MAIN_THREAD_ID; + + if (chat && isChatSuperGroup(chat)) { + return replyToTopMessageId || replyToMessageId || MAIN_THREAD_ID; + } + return MAIN_THREAD_ID; + } if (!isTopicReply) return GENERAL_TOPIC_ID; return replyToTopMessageId || replyToMessageId || GENERAL_TOPIC_ID; } @@ -531,7 +537,10 @@ export function selectAllowedMessageActions(global: T, me && !chat.isForbidden ); - const canReply = !isLocal && !isServiceNotification && !chat.isForbidden && getCanPostInChat(chat, threadId) + const threadInfo = selectThreadInfo(global, message.chatId, threadId); + const isComments = Boolean(threadInfo?.originChannelId); + const canReply = !isLocal && !isServiceNotification && !chat.isForbidden + && getCanPostInChat(chat, threadId, isComments) && (!messageTopic || !messageTopic.isClosed || messageTopic.isOwner || getHasAdminRight(chat, 'manageTopics')); const hasPinPermission = isPrivate || ( diff --git a/src/global/types.ts b/src/global/types.ts index cbb45f05b..6b3e8a428 100644 --- a/src/global/types.ts +++ b/src/global/types.ts @@ -1572,6 +1572,11 @@ export interface ActionPayloads { shouldReplaceHistory?: boolean; noForumTopicPanel?: boolean; } & WithTabId; + openComments: { + id: string; + threadId: number; + originChannelId?: string; + } & WithTabId; loadFullChat: { chatId: string; force?: boolean; diff --git a/src/styles/Telegram T.json b/src/styles/Telegram T.json index 54afe7f3a..96bd27303 100644 --- a/src/styles/Telegram T.json +++ b/src/styles/Telegram T.json @@ -2,7 +2,7 @@ "metadata": { "name": "Telegram T", "lastOpened": 0, - "created": 1674067291539 + "created": 1675856733901 }, "iconSets": [ { @@ -157,13 +157,21 @@ }, { "selection": [ + { + "order": 754, + "id": 85, + "name": "replies", + "prevSize": 32, + "code": 59833, + "tempChar": "" + }, { "order": 746, "id": 84, "name": "forums", "prevSize": 32, "code": 59828, - "tempChar": "" + "tempChar": "" }, { "order": 743, @@ -171,7 +179,7 @@ "name": "hashtag", "prevSize": 32, "code": 59825, - "tempChar": "" + "tempChar": "" }, { "order": 744, @@ -179,7 +187,7 @@ "name": "reopen-topic", "prevSize": 32, "code": 59826, - "tempChar": "" + "tempChar": "" }, { "order": 745, @@ -187,7 +195,7 @@ "name": "close-topic", "prevSize": 32, "code": 59827, - "tempChar": "" + "tempChar": "" }, { "order": 739, @@ -195,7 +203,7 @@ "name": "open-in-new-tab", "prevSize": 32, "code": 59823, - "tempChar": "" + "tempChar": "" }, { "order": 738, @@ -203,7 +211,7 @@ "name": "pip", "prevSize": 32, "code": 59822, - "tempChar": "" + "tempChar": "" }, { "order": 737, @@ -211,7 +219,7 @@ "name": "gift", "prevSize": 32, "code": 59821, - "tempChar": "" + "tempChar": "" }, { "order": 734, @@ -219,7 +227,7 @@ "name": "sort", "prevSize": 32, "code": 59820, - "tempChar": "" + "tempChar": "" }, { "order": 732, @@ -227,7 +235,7 @@ "name": "web", "prevSize": 32, "code": 59819, - "tempChar": "" + "tempChar": "" }, { "order": 731, @@ -235,7 +243,7 @@ "name": "transcribe", "prevSize": 32, "code": 59818, - "tempChar": "" + "tempChar": "" }, { "order": 719, @@ -243,7 +251,7 @@ "name": "add-one-badge", "prevSize": 32, "code": 59803, - "tempChar": "" + "tempChar": "" }, { "order": 720, @@ -251,7 +259,7 @@ "name": "chat-badge", "prevSize": 32, "code": 59808, - "tempChar": "" + "tempChar": "" }, { "order": 721, @@ -259,7 +267,7 @@ "name": "chats-badge", "prevSize": 32, "code": 59809, - "tempChar": "" + "tempChar": "" }, { "order": 722, @@ -267,7 +275,7 @@ "name": "double-badge", "prevSize": 32, "code": 59810, - "tempChar": "" + "tempChar": "" }, { "order": 723, @@ -275,7 +283,7 @@ "name": "file-badge", "prevSize": 32, "code": 59811, - "tempChar": "" + "tempChar": "" }, { "order": 724, @@ -283,7 +291,7 @@ "name": "folder-badge", "prevSize": 32, "code": 59812, - "tempChar": "" + "tempChar": "" }, { "order": 726, @@ -291,7 +299,7 @@ "name": "link-badge", "prevSize": 32, "code": 59813, - "tempChar": "" + "tempChar": "" }, { "order": 725, @@ -299,7 +307,7 @@ "name": "pin-badge", "prevSize": 32, "code": 59814, - "tempChar": "" + "tempChar": "" }, { "order": 727, @@ -307,7 +315,7 @@ "name": "premium", "prevSize": 32, "code": 59815, - "tempChar": "" + "tempChar": "" }, { "order": 728, @@ -315,7 +323,7 @@ "name": "unlock-badge", "prevSize": 32, "code": 59816, - "tempChar": "" + "tempChar": "" }, { "order": 729, @@ -323,7 +331,7 @@ "name": "lock-badge", "prevSize": 32, "code": 59817, - "tempChar": "" + "tempChar": "" }, { "order": 715, @@ -331,7 +339,7 @@ "name": "key", "prevSize": 32, "code": 59802, - "tempChar": "" + "tempChar": "" }, { "order": 714, @@ -339,7 +347,7 @@ "name": "heart-outline", "prevSize": 32, "code": 59806, - "tempChar": "" + "tempChar": "" }, { "order": 713, @@ -347,7 +355,7 @@ "name": "heart", "prevSize": 32, "code": 59807, - "tempChar": "" + "tempChar": "" }, { "order": 712, @@ -355,7 +363,7 @@ "name": "word-wrap", "prevSize": 32, "code": 59805, - "tempChar": "" + "tempChar": "" }, { "order": 708, @@ -363,7 +371,7 @@ "name": "webapp", "prevSize": 32, "code": 59795, - "tempChar": "" + "tempChar": "" }, { "order": 707, @@ -371,7 +379,7 @@ "name": "reload", "prevSize": 32, "code": 59796, - "tempChar": "" + "tempChar": "" }, { "order": 706, @@ -379,7 +387,7 @@ "name": "install", "prevSize": 32, "code": 59801, - "tempChar": "" + "tempChar": "" }, { "order": 705, @@ -387,7 +395,7 @@ "name": "favorite-filled", "prevSize": 32, "code": 59800, - "tempChar": "" + "tempChar": "" }, { "order": 702, @@ -395,7 +403,7 @@ "name": "share-screen", "prevSize": 32, "code": 59770, - "tempChar": "" + "tempChar": "" }, { "order": 701, @@ -403,7 +411,7 @@ "name": "video-outlined", "prevSize": 32, "code": 59799, - "tempChar": "" + "tempChar": "" }, { "order": 700, @@ -411,7 +419,7 @@ "name": "stats", "prevSize": 32, "code": 59798, - "tempChar": "" + "tempChar": "" }, { "order": 699, @@ -419,7 +427,7 @@ "name": "copy-media", "prevSize": 32, "code": 59797, - "tempChar": "" + "tempChar": "" }, { "order": 704, @@ -427,7 +435,7 @@ "name": "sidebar", "prevSize": 32, "code": 59794, - "tempChar": "" + "tempChar": "" }, { "order": 690, @@ -435,7 +443,7 @@ "name": "video-stop", "prevSize": 32, "code": 59787, - "tempChar": "" + "tempChar": "" }, { "order": 678, @@ -443,7 +451,7 @@ "name": "speaker", "prevSize": 32, "code": 59777, - "tempChar": "" + "tempChar": "" }, { "order": 679, @@ -451,7 +459,7 @@ "name": "speaker-outline", "prevSize": 32, "code": 59778, - "tempChar": "" + "tempChar": "" }, { "order": 680, @@ -459,7 +467,7 @@ "name": "phone-discard-outline", "prevSize": 32, "code": 59779, - "tempChar": "" + "tempChar": "" }, { "order": 681, @@ -467,7 +475,7 @@ "name": "allow-speak", "prevSize": 32, "code": 59780, - "tempChar": "" + "tempChar": "" }, { "order": 682, @@ -475,7 +483,7 @@ "name": "stop-raising-hand", "prevSize": 32, "code": 59781, - "tempChar": "" + "tempChar": "" }, { "order": 683, @@ -483,7 +491,7 @@ "name": "share-screen-outlined", "prevSize": 32, "code": 59782, - "tempChar": "" + "tempChar": "" }, { "order": 684, @@ -491,7 +499,7 @@ "name": "voice-chat", "prevSize": 32, "code": 59783, - "tempChar": "" + "tempChar": "" }, { "order": 689, @@ -499,7 +507,7 @@ "name": "video", "prevSize": 32, "code": 59784, - "tempChar": "" + "tempChar": "" }, { "order": 686, @@ -507,7 +515,7 @@ "name": "noise-suppression", "prevSize": 32, "code": 59785, - "tempChar": "" + "tempChar": "" }, { "order": 703, @@ -515,7 +523,7 @@ "name": "phone-discard", "prevSize": 32, "code": 59786, - "tempChar": "" + "tempChar": "" }, { "order": 667, @@ -523,7 +531,7 @@ "name": "bot-commands-filled", "prevSize": 32, "code": 59775, - "tempChar": "" + "tempChar": "" }, { "order": 664, @@ -531,7 +539,7 @@ "name": "reply-filled", "prevSize": 32, "code": 59776, - "tempChar": "" + "tempChar": "" }, { "order": 656, @@ -539,7 +547,7 @@ "name": "bug", "prevSize": 32, "code": 59774, - "tempChar": "" + "tempChar": "" }, { "order": 619, @@ -547,7 +555,7 @@ "name": "data", "prevSize": 32, "code": 59773, - "tempChar": "" + "tempChar": "" }, { "order": 622, @@ -555,7 +563,7 @@ "name": "darkmode", "prevSize": 32, "code": 59769, - "tempChar": "" + "tempChar": "" }, { "order": 711, @@ -563,7 +571,7 @@ "name": "animations", "prevSize": 32, "code": 59804, - "tempChar": "" + "tempChar": "" }, { "order": 626, @@ -571,7 +579,7 @@ "name": "enter", "prevSize": 32, "code": 59771, - "tempChar": "" + "tempChar": "" }, { "order": 627, @@ -579,7 +587,7 @@ "name": "fontsize", "prevSize": 32, "code": 59772, - "tempChar": "" + "tempChar": "" }, { "order": 630, @@ -587,7 +595,7 @@ "name": "permissions", "prevSize": 32, "code": 59766, - "tempChar": "" + "tempChar": "" }, { "order": 631, @@ -595,7 +603,7 @@ "name": "card", "prevSize": 32, "code": 59767, - "tempChar": "" + "tempChar": "" }, { "order": 634, @@ -603,7 +611,7 @@ "name": "truck", "prevSize": 32, "code": 59768, - "tempChar": "" + "tempChar": "" }, { "order": 663, @@ -611,7 +619,7 @@ "name": "share-filled", "prevSize": 32, "code": 59738, - "tempChar": "" + "tempChar": "" }, { "order": 638, @@ -619,7 +627,7 @@ "name": "bold", "prevSize": 32, "code": 59745, - "tempChar": "" + "tempChar": "" }, { "order": 639, @@ -627,7 +635,7 @@ "name": "bot-command", "prevSize": 32, "code": 59746, - "tempChar": "" + "tempChar": "" }, { "order": 642, @@ -635,7 +643,7 @@ "name": "calendar-filter", "prevSize": 32, "code": 59747, - "tempChar": "" + "tempChar": "" }, { "order": 643, @@ -643,7 +651,7 @@ "name": "comments", "prevSize": 32, "code": 59748, - "tempChar": "" + "tempChar": "" }, { "order": 645, @@ -651,7 +659,7 @@ "name": "comments-sticker", "prevSize": 32, "code": 59749, - "tempChar": "" + "tempChar": "" }, { "order": 646, @@ -659,7 +667,7 @@ "name": "arrow-down", "prevSize": 32, "code": 59750, - "tempChar": "" + "tempChar": "" }, { "order": 668, @@ -667,7 +675,7 @@ "name": "email", "prevSize": 32, "code": 59751, - "tempChar": "" + "tempChar": "" }, { "order": 648, @@ -675,7 +683,7 @@ "name": "italic", "prevSize": 32, "code": 59752, - "tempChar": "" + "tempChar": "" }, { "order": 620, @@ -683,7 +691,7 @@ "name": "link", "prevSize": 32, "code": 59753, - "tempChar": "" + "tempChar": "" }, { "order": 742, @@ -691,7 +699,7 @@ "name": "link-broken", "prevSize": 32, "code": 59824, - "tempChar": "" + "tempChar": "" }, { "order": 621, @@ -699,7 +707,7 @@ "name": "mention", "prevSize": 32, "code": 59754, - "tempChar": "" + "tempChar": "" }, { "order": 624, @@ -707,7 +715,7 @@ "name": "monospace", "prevSize": 32, "code": 59755, - "tempChar": "" + "tempChar": "" }, { "order": 625, @@ -715,7 +723,7 @@ "name": "next", "prevSize": 32, "code": 59756, - "tempChar": "" + "tempChar": "" }, { "order": 628, @@ -723,7 +731,7 @@ "name": "password-off", "prevSize": 32, "code": 59757, - "tempChar": "" + "tempChar": "" }, { "order": 629, @@ -731,7 +739,7 @@ "name": "pin-list", "prevSize": 32, "code": 59758, - "tempChar": "" + "tempChar": "" }, { "order": 632, @@ -739,7 +747,7 @@ "name": "previous", "prevSize": 32, "code": 59759, - "tempChar": "" + "tempChar": "" }, { "order": 633, @@ -747,7 +755,7 @@ "name": "replace", "prevSize": 32, "code": 59760, - "tempChar": "" + "tempChar": "" }, { "order": 636, @@ -755,7 +763,7 @@ "name": "schedule", "prevSize": 32, "code": 59761, - "tempChar": "" + "tempChar": "" }, { "order": 691, @@ -763,7 +771,7 @@ "name": "strikethrough", "prevSize": 32, "code": 59762, - "tempChar": "" + "tempChar": "" }, { "order": 692, @@ -771,7 +779,7 @@ "name": "underlined", "prevSize": 32, "code": 59763, - "tempChar": "" + "tempChar": "" }, { "order": 641, @@ -779,7 +787,7 @@ "name": "zoom-in", "prevSize": 32, "code": 59764, - "tempChar": "" + "tempChar": "" }, { "order": 649, @@ -787,20 +795,37 @@ "name": "zoom-out", "prevSize": 32, "code": 59765, - "tempChar": "" + "tempChar": "" } ], "id": 2, "metadata": { "name": "Untitled Set", "importSize": { - "width": 24, - "height": 24 + "width": 768, + "height": 768 } }, "height": 1024, "prevSize": 32, "icons": [ + { + "id": 85, + "paths": [ + "M563.2 576c-0.933 0-2 0-2.933 0-0.533 0-1.067 0-1.6-0.133l-4.267-0.4c-0.667 0-1.333-0.133-2-0.133-5.2-0.667-9.733-1.733-14.267-3.6-4.667-1.867-9.2-4.4-14.4-8-0.4-0.267-0.8-0.533-1.2-0.933l-5.067-3.733c-0.4-0.267-0.667-0.533-1.067-0.8l-1.2-0.933c-0.4-0.4-0.933-0.8-1.467-1.2-0.133-0.133-0.4-0.267-0.533-0.533l-6.533-5.6c-0.267-0.267-0.533-0.533-0.8-0.667l-12.267-10.933c-0.133-0.133-0.267-0.267-0.4-0.4l-178.8-165.333c-0.133-0.133-0.4-0.267-0.533-0.533l-9.2-8.8c-0.267-0.267-0.533-0.4-0.667-0.667-0.8-0.8-1.6-1.6-2.4-2.4-0.667-0.667-1.2-1.333-1.733-1.867-0.4-0.533-0.933-1.067-1.333-1.6l-2.533-3.067c-0.533-0.667-1.2-1.467-1.733-2.133-0.933-1.333-1.733-2.533-2.667-3.867-1.067-1.6-2-3.333-3.067-5.2-0.8-1.467-1.6-3.067-2.267-4.667s-1.333-3.2-1.867-4.8c-2.667-7.733-4-15.733-4-23.867s1.333-16.133 4-23.867c0.533-1.6 1.2-3.2 1.867-4.8s1.467-3.2 2.267-4.667c1.067-2 2-3.733 3.067-5.2 0.933-1.467 1.733-2.667 2.667-4 0.533-0.8 1.067-1.467 1.733-2.267l2.533-3.067c0.4-0.533 0.8-0.933 1.333-1.467 0.533-0.667 1.2-1.333 1.867-2 0.8-0.8 1.6-1.6 2.4-2.4 0.133-0.133 0.4-0.4 0.533-0.533l5.6-5.333c0.267-0.267 0.4-0.4 0.667-0.667l182.267-168.533c0.133-0.133 0.267-0.267 0.4-0.4l12.4-11.067c0.133-0.133 0.267-0.267 0.4-0.4 0.533-0.533 1.2-1.067 1.733-1.6l1.6-1.333c0.133-0.133 0.4-0.267 0.533-0.533l5.867-4.8c0.533-0.4 0.933-0.8 1.467-1.2l4.8-3.6c0.4-0.267 0.8-0.667 1.2-0.933 5.2-3.6 9.733-6.133 14.4-8 4.533-1.733 9.067-2.933 14.267-3.6 0.667-0.133 1.333-0.133 2-0.267l4.267-0.267c0.533 0 1.067-0.133 1.6-0.133 0.933 0 2 0 2.933 0 10.267 0 20.133 2.133 29.467 6.133 10.133 4.4 19.333 11.2 26.533 19.733 0.4 0.4 0.667 0.8 0.933 1.2l2.533 3.2c0.4 0.533 0.8 0.933 1.067 1.467 2.933 4.133 5.2 8 6.933 12.267 1.733 4.4 3.067 9.2 4 14.933 0.133 0.533 0.133 1.067 0.267 1.6l0.667 5.6c0 0.4 0.133 0.8 0.133 1.333l0.133 1.333c0 0.667 0.133 1.2 0.133 1.733 0 0.267 0 0.533 0 0.8l0.4 7.6c0 0.4 0 0.8 0 1.2l0.267 14.533c0 0.267 0 0.533 0 0.8v11.6c0 0.667 0 1.2 0 1.867v13.333c76.133 7.467 139.333 35.467 188.667 83.467 49.467 48.133 83.467 115.867 100.933 201.333 1.067 2.667 1.867 5.333 2.4 8.267l5.067 28.267c0 0.267 0.133 0.533 0.133 0.933l1.733 11.067c0.133 0.533 0.133 0.933 0.267 1.467l0.667 4.933c0.133 0.667 0.133 1.333 0.267 2.133l0.267 3.733c0 0.667 0.133 1.467 0.133 2.133 0 0.533 0 1.067 0 1.467 0 6.933-1.733 13.733-4.8 19.733l-0.133 0.267c-0.4 0.8-0.8 1.6-1.333 2.267l-2.4 3.867c-0.133 0.133-0.267 0.4-0.4 0.533-5.2 8.4-12.133 15.067-20.267 19.733-8.267 4.8-17.6 7.467-27.6 7.733l-3.067 0.133c-0.4 0-0.8 0-1.333 0h-1.467c-0.133 0-0.133 0-0.267 0-7.333 0-14.667-1.867-21.067-5.6l-0.4-0.267c-0.933-0.533-1.733-1.067-2.533-1.6l-2.4-1.6c-0.533-0.4-1.2-0.8-1.733-1.2l-3.6-2.667c-0.4-0.267-0.8-0.667-1.2-0.933l-7.867-6.267c-0.267-0.267-0.533-0.533-0.933-0.667l-30.667-25.6c-0.933-0.667-1.733-1.467-2.533-2.267-24.4-19.467-52.267-34.667-82.933-45.2-24.4-8.4-51.067-14-79.333-16.8v10.533c0 0.133 0 0.267 0 0.4v9.867c0 0.133 0 0.133 0 0.267v0.533c0 0.533 0 0.933 0 1.467v7.6c0 0.4 0 0.667 0 1.067l-0.4 10.267c0 0.267 0 0.533 0 0.8-0.533 11.333-1.733 19.2-3.733 26.133-2.133 7.2-5.333 13.6-10.133 19.867-0.267 0.267-0.4 0.533-0.667 0.933l-1.467 1.867c-0.267 0.267-0.533 0.667-0.8 0.933-7.2 8.533-16.4 15.333-26.667 19.867-9.6 4.133-19.467 6.267-29.733 6.267zM550.933 474.933l0.4 0.4v-61.467c0-11.6 4.533-22.667 12.8-30.8 8.133-8.133 19.067-12.8 30.8-12.8 53.733 0 103.867 7.867 148.933 23.333 33.6 11.6 64.667 27.467 92.667 47.467-14.8-56.133-38.667-100.533-70.933-131.867-39.867-38.8-94.267-59.333-161.6-60.933l-9.467-0.133c-11.467-0.133-22.267-4.667-30.4-12.667-8.267-8.267-12.8-19.2-12.8-30.933v-61.467l-0.4 0.4-179.467 165.733 1.067 1.067 178.4 164.667zM636.8 413.867v0c0 0 0 0 0 0z", + "M272.933 980c-28.667 0-43.467-4.8-52.133-8.4-25.733-10.8-44.267-32-50.8-58.4-4.8-19.333-5.067-49.067 20.667-80.4 0.133-0.267 0.4-0.4 0.533-0.667l0.933-1.2c0.133-0.267 0.4-0.4 0.533-0.667l7.467-8.4c0.133-0.133 0.267-0.267 0.4-0.4l6-6.533 5.733-6.533c1.333-1.6 2.667-3.2 3.733-4.667-36.4-31.333-65.733-67.333-87.2-107.333-28-52.133-42.267-109.6-42.267-170.8 0-53.733 11.6-105.867 34.4-154.8 10-21.333 35.333-30.533 56.667-20.667 21.333 10 30.533 35.333 20.667 56.667-17.6 37.6-26.4 77.6-26.4 118.8 0 47.067 10.8 90.933 32 130.4 19.6 36.533 48 69.067 84.4 96.667 8.133 6.133 13.733 14.933 15.867 24.8 1.867 8.4 6.4 38-12.667 67.067-4.133 6.4-8.933 12.667-14.4 19.2-0.133 0.267-0.4 0.4-0.533 0.667l-6.4 7.2c-0.267 0.267-0.4 0.533-0.667 0.8l-6.133 6.667-7.467 8.4c-0.933 1.2-2.8 3.467-3.333 5.067 0.133 0.133 0.4 0.267 0.667 0.4l0.4 0.133c0 0 5.067 1.733 18.8 1.733 10 0 44.533-1.2 72.133-16.667 10.933-6.133 20.933-12.667 30.533-19.867l0.267-0.267 9.6-7.6 8-6.8 9.333-8.533c9.867-9.067 23.467-12.933 36.667-10.4 22.8 4.4 46.267 6.533 69.733 6.533 90.133 0 174.933-31.867 238.667-89.6 30.933-28 55.2-60.667 72.133-97.067 10-21.333 35.333-30.533 56.667-20.667 21.333 10 30.533 35.333 20.667 56.667-21.733 46.667-52.8 88.533-92.133 124.267-79.467 72.133-184.533 111.733-296 111.733-21.6 0-43.2-1.467-64.533-4.533l-4 3.333c-0.267 0.133-0.4 0.4-0.667 0.533l-1.2 0.933c-0.267 0.133-0.4 0.4-0.667 0.533l-9.6 7.6c-0.133 0.133-0.267 0.133-0.4 0.267l-0.667 0.533c-0.133 0.133-0.267 0.267-0.4 0.267-12.8 9.733-26.133 18.267-40.533 26.4-43.6 24.933-93.6 27.6-113.067 27.6zM220.933 794.933c0 0 0 0 0 0s0 0 0 0z" + ], + "attrs": [ + {}, + {} + ], + "isMulticolor": false, + "isMulticolor2": false, + "grid": 24, + "tags": [ + "replies" + ] + }, { "id": 84, "paths": [ @@ -3680,7 +3705,7 @@ "name": "spoiler-disable", "prevSize": 32, "code": 59829, - "tempChar": "" + "tempChar": "" }, { "order": 752, @@ -3688,7 +3713,7 @@ "name": "grouped", "prevSize": 32, "code": 59830, - "tempChar": "" + "tempChar": "" }, { "order": 751, @@ -3696,7 +3721,7 @@ "name": "grouped-disable", "prevSize": 32, "code": 59831, - "tempChar": "" + "tempChar": "" }, { "order": 749, @@ -3704,7 +3729,7 @@ "name": "spoiler", "prevSize": 32, "code": 59832, - "tempChar": "" + "tempChar": "" }, { "order": 576, @@ -3712,7 +3737,7 @@ "name": "select", "prevSize": 32, "code": 59744, - "tempChar": "" + "tempChar": "" }, { "order": 480, @@ -3720,7 +3745,7 @@ "name": "folder", "prevSize": 32, "code": 59667, - "tempChar": "" + "tempChar": "" }, { "order": 481, @@ -3728,7 +3753,7 @@ "name": "bots", "prevSize": 32, "code": 59669, - "tempChar": "" + "tempChar": "" }, { "order": 482, @@ -3736,7 +3761,7 @@ "name": "calendar", "prevSize": 32, "code": 59670, - "tempChar": "" + "tempChar": "" }, { "order": 483, @@ -3744,7 +3769,7 @@ "name": "cloud-download", "prevSize": 32, "code": 59671, - "tempChar": "" + "tempChar": "" }, { "order": 484, @@ -3752,7 +3777,7 @@ "name": "colorize", "prevSize": 32, "code": 59672, - "tempChar": "" + "tempChar": "" }, { "order": 651, @@ -3760,7 +3785,7 @@ "name": "forward", "prevSize": 32, "code": 59687, - "tempChar": "" + "tempChar": "" }, { "order": 650, @@ -3768,7 +3793,7 @@ "name": "reply", "prevSize": 32, "code": 59719, - "tempChar": "" + "tempChar": "" }, { "order": 487, @@ -3776,7 +3801,7 @@ "name": "help", "prevSize": 32, "code": 59690, - "tempChar": "" + "tempChar": "" }, { "order": 488, @@ -3784,7 +3809,7 @@ "name": "info", "prevSize": 32, "code": 59691, - "tempChar": "" + "tempChar": "" }, { "order": 489, @@ -3792,7 +3817,7 @@ "name": "info-filled", "prevSize": 32, "code": 59675, - "tempChar": "" + "tempChar": "" }, { "order": 490, @@ -3800,7 +3825,7 @@ "name": "delete-filled", "prevSize": 32, "code": 59676, - "tempChar": "" + "tempChar": "" }, { "order": 491, @@ -3808,7 +3833,7 @@ "name": "delete", "prevSize": 32, "code": 59677, - "tempChar": "" + "tempChar": "" }, { "order": 492, @@ -3816,7 +3841,7 @@ "name": "edit", "prevSize": 32, "code": 59683, - "tempChar": "" + "tempChar": "" }, { "order": 493, @@ -3824,7 +3849,7 @@ "name": "new-chat-filled", "prevSize": 32, "code": 59705, - "tempChar": "" + "tempChar": "" }, { "order": 494, @@ -3832,7 +3857,7 @@ "name": "send", "prevSize": 32, "code": 59722, - "tempChar": "" + "tempChar": "" }, { "order": 495, @@ -3840,7 +3865,7 @@ "name": "send-outline", "prevSize": 32, "code": 59723, - "tempChar": "" + "tempChar": "" }, { "order": 496, @@ -3848,7 +3873,7 @@ "name": "add-user-filled", "prevSize": 32, "code": 59652, - "tempChar": "" + "tempChar": "" }, { "order": 497, @@ -3856,7 +3881,7 @@ "name": "add-user", "prevSize": 32, "code": 59653, - "tempChar": "" + "tempChar": "" }, { "order": 498, @@ -3864,7 +3889,7 @@ "name": "delete-user", "prevSize": 32, "code": 59678, - "tempChar": "" + "tempChar": "" }, { "order": 499, @@ -3872,7 +3897,7 @@ "name": "microphone", "prevSize": 32, "code": 59701, - "tempChar": "" + "tempChar": "" }, { "order": 500, @@ -3880,7 +3905,7 @@ "name": "microphone-alt", "prevSize": 32, "code": 59707, - "tempChar": "" + "tempChar": "" }, { "order": 501, @@ -3888,7 +3913,7 @@ "name": "poll", "prevSize": 32, "code": 59704, - "tempChar": "" + "tempChar": "" }, { "order": 502, @@ -3896,7 +3921,7 @@ "name": "revote", "prevSize": 32, "code": 59706, - "tempChar": "" + "tempChar": "" }, { "order": 503, @@ -3904,7 +3929,7 @@ "name": "photo", "prevSize": 32, "code": 59712, - "tempChar": "" + "tempChar": "" }, { "order": 748, @@ -3912,7 +3937,7 @@ "name": "document", "prevSize": 32, "code": 59679, - "tempChar": "" + "tempChar": "" }, { "order": 505, @@ -3920,7 +3945,7 @@ "name": "camera", "prevSize": 32, "code": 59662, - "tempChar": "" + "tempChar": "" }, { "order": 506, @@ -3928,7 +3953,7 @@ "name": "camera-add", "prevSize": 32, "code": 59663, - "tempChar": "" + "tempChar": "" }, { "order": 507, @@ -3936,7 +3961,7 @@ "name": "logout", "prevSize": 32, "code": 59698, - "tempChar": "" + "tempChar": "" }, { "order": 508, @@ -3944,7 +3969,7 @@ "name": "saved-messages", "prevSize": 32, "code": 59720, - "tempChar": "" + "tempChar": "" }, { "order": 509, @@ -3952,7 +3977,7 @@ "name": "settings", "prevSize": 32, "code": 59726, - "tempChar": "" + "tempChar": "" }, { "order": 652, @@ -3960,7 +3985,7 @@ "name": "phone", "prevSize": 32, "code": 59711, - "tempChar": "" + "tempChar": "" }, { "order": 653, @@ -3968,7 +3993,7 @@ "name": "attach", "prevSize": 32, "code": 59657, - "tempChar": "" + "tempChar": "" }, { "order": 512, @@ -3976,7 +4001,7 @@ "name": "copy", "prevSize": 32, "code": 59674, - "tempChar": "" + "tempChar": "" }, { "order": 513, @@ -3984,7 +4009,7 @@ "name": "channel", "prevSize": 32, "code": 59665, - "tempChar": "" + "tempChar": "" }, { "order": 514, @@ -3992,7 +4017,7 @@ "name": "group", "prevSize": 32, "code": 59689, - "tempChar": "" + "tempChar": "" }, { "order": 515, @@ -4000,7 +4025,7 @@ "name": "user", "prevSize": 32, "code": 59737, - "tempChar": "" + "tempChar": "" }, { "order": 516, @@ -4008,7 +4033,7 @@ "name": "non-contacts", "prevSize": 32, "code": 59688, - "tempChar": "" + "tempChar": "" }, { "order": 517, @@ -4016,7 +4041,7 @@ "name": "active-sessions", "prevSize": 32, "code": 59650, - "tempChar": "" + "tempChar": "" }, { "order": 518, @@ -4024,7 +4049,7 @@ "name": "admin", "prevSize": 32, "code": 59654, - "tempChar": "" + "tempChar": "" }, { "order": 519, @@ -4032,7 +4057,7 @@ "name": "download", "prevSize": 32, "code": 59681, - "tempChar": "" + "tempChar": "" }, { "order": 520, @@ -4040,7 +4065,7 @@ "name": "location", "prevSize": 32, "code": 59696, - "tempChar": "" + "tempChar": "" }, { "order": 521, @@ -4048,7 +4073,7 @@ "name": "stop", "prevSize": 32, "code": 59730, - "tempChar": "" + "tempChar": "" }, { "order": 523, @@ -4056,7 +4081,7 @@ "name": "archive", "prevSize": 32, "code": 59656, - "tempChar": "" + "tempChar": "" }, { "order": 524, @@ -4064,7 +4089,7 @@ "name": "unarchive", "prevSize": 32, "code": 59731, - "tempChar": "" + "tempChar": "" }, { "order": 525, @@ -4072,7 +4097,7 @@ "name": "readchats", "prevSize": 32, "code": 59699, - "tempChar": "" + "tempChar": "" }, { "order": 526, @@ -4080,7 +4105,7 @@ "name": "unread", "prevSize": 32, "code": 59735, - "tempChar": "" + "tempChar": "" }, { "order": 654, @@ -4088,7 +4113,7 @@ "name": "message", "prevSize": 32, "code": 59700, - "tempChar": "" + "tempChar": "" }, { "order": 659, @@ -4096,7 +4121,7 @@ "name": "lock", "prevSize": 32, "code": 59697, - "tempChar": "" + "tempChar": "" }, { "order": 529, @@ -4104,7 +4129,7 @@ "name": "unlock", "prevSize": 32, "code": 59732, - "tempChar": "" + "tempChar": "" }, { "order": 530, @@ -4112,7 +4137,7 @@ "name": "mute", "prevSize": 32, "code": 59703, - "tempChar": "" + "tempChar": "" }, { "order": 531, @@ -4120,7 +4145,7 @@ "name": "unmute", "prevSize": 32, "code": 59733, - "tempChar": "" + "tempChar": "" }, { "order": 532, @@ -4128,7 +4153,7 @@ "name": "pin", "prevSize": 32, "code": 59713, - "tempChar": "" + "tempChar": "" }, { "order": 533, @@ -4136,7 +4161,7 @@ "name": "unpin", "prevSize": 32, "code": 59734, - "tempChar": "" + "tempChar": "" }, { "order": 534, @@ -4144,7 +4169,7 @@ "name": "smallscreen", "prevSize": 32, "code": 59742, - "tempChar": "" + "tempChar": "" }, { "order": 535, @@ -4152,7 +4177,7 @@ "name": "fullscreen", "prevSize": 32, "code": 59743, - "tempChar": "" + "tempChar": "" }, { "order": 536, @@ -4160,7 +4185,7 @@ "name": "large-pause", "prevSize": 32, "code": 59694, - "tempChar": "" + "tempChar": "" }, { "order": 537, @@ -4168,7 +4193,7 @@ "name": "large-play", "prevSize": 32, "code": 59695, - "tempChar": "" + "tempChar": "" }, { "order": 538, @@ -4176,7 +4201,7 @@ "name": "pause", "prevSize": 32, "code": 59709, - "tempChar": "" + "tempChar": "" }, { "order": 539, @@ -4184,7 +4209,7 @@ "name": "play", "prevSize": 32, "code": 59715, - "tempChar": "" + "tempChar": "" }, { "order": 540, @@ -4192,7 +4217,7 @@ "name": "channelviews", "prevSize": 32, "code": 59666, - "tempChar": "" + "tempChar": "" }, { "order": 541, @@ -4200,7 +4225,7 @@ "name": "message-succeeded", "prevSize": 32, "code": 59648, - "tempChar": "" + "tempChar": "" }, { "order": 657, @@ -4208,7 +4233,7 @@ "name": "message-read", "prevSize": 32, "code": 59649, - "tempChar": "" + "tempChar": "" }, { "order": 543, @@ -4216,7 +4241,7 @@ "name": "message-pending", "prevSize": 32, "code": 59724, - "tempChar": "" + "tempChar": "" }, { "order": 544, @@ -4224,7 +4249,7 @@ "name": "message-failed", "prevSize": 32, "code": 59725, - "tempChar": "" + "tempChar": "" }, { "order": 545, @@ -4232,7 +4257,7 @@ "name": "favorite", "prevSize": 32, "code": 59710, - "tempChar": "" + "tempChar": "" }, { "order": 546, @@ -4240,7 +4265,7 @@ "name": "keyboard", "prevSize": 32, "code": 59716, - "tempChar": "" + "tempChar": "" }, { "order": 547, @@ -4248,7 +4273,7 @@ "name": "delete-left", "prevSize": 32, "code": 59717, - "tempChar": "" + "tempChar": "" }, { "order": 548, @@ -4256,7 +4281,7 @@ "name": "recent", "prevSize": 32, "code": 59718, - "tempChar": "" + "tempChar": "" }, { "order": 549, @@ -4264,7 +4289,7 @@ "name": "gifs", "prevSize": 32, "code": 59727, - "tempChar": "" + "tempChar": "" }, { "order": 550, @@ -4272,7 +4297,7 @@ "name": "stickers", "prevSize": 32, "code": 59739, - "tempChar": "" + "tempChar": "" }, { "order": 551, @@ -4280,7 +4305,7 @@ "name": "smile", "prevSize": 32, "code": 59728, - "tempChar": "" + "tempChar": "" }, { "order": 552, @@ -4288,7 +4313,7 @@ "name": "animals", "prevSize": 32, "code": 59655, - "tempChar": "" + "tempChar": "" }, { "order": 553, @@ -4296,7 +4321,7 @@ "name": "eats", "prevSize": 32, "code": 59682, - "tempChar": "" + "tempChar": "" }, { "order": 554, @@ -4304,7 +4329,7 @@ "name": "sport", "prevSize": 32, "code": 59729, - "tempChar": "" + "tempChar": "" }, { "order": 555, @@ -4312,7 +4337,7 @@ "name": "car", "prevSize": 32, "code": 59664, - "tempChar": "" + "tempChar": "" }, { "order": 556, @@ -4320,7 +4345,7 @@ "name": "lamp", "prevSize": 32, "code": 59692, - "tempChar": "" + "tempChar": "" }, { "order": 557, @@ -4328,7 +4353,7 @@ "name": "language", "prevSize": 32, "code": 59693, - "tempChar": "" + "tempChar": "" }, { "order": 558, @@ -4336,7 +4361,7 @@ "name": "flag", "prevSize": 32, "code": 59686, - "tempChar": "" + "tempChar": "" }, { "order": 559, @@ -4344,7 +4369,7 @@ "name": "more", "prevSize": 32, "code": 59702, - "tempChar": "" + "tempChar": "" }, { "order": 560, @@ -4352,7 +4377,7 @@ "name": "search", "prevSize": 32, "code": 59721, - "tempChar": "" + "tempChar": "" }, { "order": 561, @@ -4360,7 +4385,7 @@ "name": "remove", "prevSize": 32, "code": 59740, - "tempChar": "" + "tempChar": "" }, { "order": 562, @@ -4368,7 +4393,7 @@ "name": "add", "prevSize": 32, "code": 59651, - "tempChar": "" + "tempChar": "" }, { "order": 563, @@ -4376,7 +4401,7 @@ "name": "check", "prevSize": 32, "code": 59668, - "tempChar": "" + "tempChar": "" }, { "order": 564, @@ -4384,7 +4409,7 @@ "name": "close", "prevSize": 32, "code": 59673, - "tempChar": "" + "tempChar": "" }, { "order": 610, @@ -4392,7 +4417,7 @@ "name": "arrow-left", "prevSize": 32, "code": 59661, - "tempChar": "" + "tempChar": "" }, { "order": 566, @@ -4400,7 +4425,7 @@ "name": "arrow-right", "prevSize": 32, "code": 59708, - "tempChar": "" + "tempChar": "" }, { "order": 730, @@ -4408,7 +4433,7 @@ "name": "down", "prevSize": 32, "code": 59680, - "tempChar": "" + "tempChar": "" }, { "order": 568, @@ -4416,7 +4441,7 @@ "name": "up", "prevSize": 32, "code": 59736, - "tempChar": "" + "tempChar": "" }, { "order": 569, @@ -4424,7 +4449,7 @@ "name": "eye-closed", "prevSize": 32, "code": 59685, - "tempChar": "" + "tempChar": "" }, { "order": 570, @@ -4432,7 +4457,7 @@ "name": "eye", "prevSize": 32, "code": 59684, - "tempChar": "" + "tempChar": "" }, { "order": 571, @@ -4440,7 +4465,7 @@ "name": "muted", "prevSize": 32, "code": 59741, - "tempChar": "" + "tempChar": "" }, { "order": 572, @@ -4448,7 +4473,7 @@ "name": "avatar-archived-chats", "prevSize": 32, "code": 59658, - "tempChar": "" + "tempChar": "" }, { "order": 573, @@ -4456,7 +4481,7 @@ "name": "avatar-deleted-account", "prevSize": 32, "code": 59659, - "tempChar": "" + "tempChar": "" }, { "order": 747, @@ -4464,7 +4489,7 @@ "name": "avatar-saved-messages", "prevSize": 32, "code": 59660, - "tempChar": "" + "tempChar": "" }, { "order": 575, @@ -4472,7 +4497,7 @@ "name": "pinned-chat", "prevSize": 32, "code": 59714, - "tempChar": "" + "tempChar": "" } ], "prevSize": 32, @@ -4524,4 +4549,4 @@ "showLiga": false }, "uid": -1 -} +} \ No newline at end of file diff --git a/src/styles/icons.scss b/src/styles/icons.scss index 44cdb0978..f9560f509 100644 --- a/src/styles/icons.scss +++ b/src/styles/icons.scss @@ -51,6 +51,9 @@ .icon-volume-3:before { content: "\e991"; } +.icon-replies:before { + content: "\e9b9"; +} .icon-forums:before { content: "\e9b4"; }