Chat: Support unread poll votes (#6847)

This commit is contained in:
zubiden 2026-04-17 13:37:59 +02:00 committed by Alexander Zinchuk
parent 741c6f532f
commit 86684cd1c1
31 changed files with 1329 additions and 827 deletions

View File

@ -710,7 +710,7 @@ export function buildThreadReadState(
const forumTopic = input instanceof GramJs.ForumTopic ? input : undefined; const forumTopic = input instanceof GramJs.ForumTopic ? input : undefined;
const { unreadReactionsCount } = dialog || monoForumDialog || forumTopic || {}; const { unreadReactionsCount } = dialog || monoForumDialog || forumTopic || {};
const { unreadMentionsCount } = dialog || forumTopic || {}; const { unreadMentionsCount, unreadPollVotesCount } = dialog || forumTopic || {};
const { unreadMark } = dialog || monoForumDialog || {}; const { unreadMark } = dialog || monoForumDialog || {};
return omitUndefined<ThreadReadState>({ return omitUndefined<ThreadReadState>({
@ -718,6 +718,7 @@ export function buildThreadReadState(
lastReadInboxMessageId: readInboxMaxId, lastReadInboxMessageId: readInboxMaxId,
lastReadOutboxMessageId: readOutboxMaxId, lastReadOutboxMessageId: readOutboxMaxId,
unreadReactionsCount, unreadReactionsCount,
unreadPollVotesCount,
unreadMentionsCount, unreadMentionsCount,
hasUnreadMark: unreadMark, hasUnreadMark: unreadMark,
}); });

View File

@ -45,6 +45,7 @@ import {
MAX_INT_32, MAX_INT_32,
MENTION_UNREAD_SLICE, MENTION_UNREAD_SLICE,
MESSAGE_ID_REQUIRED_ERROR, MESSAGE_ID_REQUIRED_ERROR,
POLL_UNREAD_SLICE,
REACTION_UNREAD_SLICE, REACTION_UNREAD_SLICE,
SUPPORTED_PHOTO_CONTENT_TYPES, SUPPORTED_PHOTO_CONTENT_TYPES,
SUPPORTED_VIDEO_CONTENT_TYPES, SUPPORTED_VIDEO_CONTENT_TYPES,
@ -2298,6 +2299,27 @@ export async function readAllReactions({
} }
} }
export async function readAllPollVotes({
chat,
threadId,
}: {
chat: ApiChat;
threadId?: ThreadId;
}) {
const result = await invokeRequest(new GramJs.messages.ReadPollVotes({
peer: buildInputPeer(chat.id, chat.accessHash),
topMsgId: threadId ? Number(threadId) : undefined,
}));
if (!result) return;
processAffectedHistory(chat, result);
if (result.offset) {
await readAllPollVotes({ chat, threadId });
}
}
export async function fetchUnreadMentions({ export async function fetchUnreadMentions({
chat, threadId, offsetId, addOffset, maxId, minId, chat, threadId, offsetId, addOffset, maxId, minId,
}: { }: {
@ -2376,6 +2398,45 @@ export async function fetchUnreadReactions({
}; };
} }
export async function fetchUnreadPollVotes({
chat, threadId, offsetId, addOffset, maxId, minId,
}: {
chat: ApiChat;
threadId?: ThreadId;
offsetId?: number;
addOffset?: number;
maxId?: number;
minId?: number;
}) {
const result = await invokeRequest(new GramJs.messages.GetUnreadPollVotes({
peer: buildInputPeer(chat.id, chat.accessHash),
topMsgId: threadId ? Number(threadId) : undefined,
limit: POLL_UNREAD_SLICE,
offsetId: offsetId ?? DEFAULT_PRIMITIVES.INT,
addOffset: addOffset ?? DEFAULT_PRIMITIVES.INT,
maxId: maxId ?? DEFAULT_PRIMITIVES.INT,
minId: minId ?? DEFAULT_PRIMITIVES.INT,
}));
if (
!result
|| result instanceof GramJs.messages.MessagesNotModified
|| !result.messages
) {
return undefined;
}
const totalCount = 'count' in result ? result.count : result.messages.length;
const messages = result.messages.map(buildApiMessage).filter(Boolean);
const topics = result.topics.map(buildApiTopicWithState).filter(Boolean);
return {
totalCount,
messages,
topics,
};
}
export async function transcribeAudio({ export async function transcribeAudio({
chat, messageId, chat, messageId,
}: { }: {

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 32 32"><path d="M16 28a3 3 0 0 1-3-3V7a3 3 0 1 1 6 0v18a3 3 0 0 1-3 3M25.325 28a3 3 0 0 1-3-3v-7.318a3 3 0 0 1 6 0V25a3 3 0 0 1-3 3M6.675 28a3 3 0 0 1-3-3V13.713a3 3 0 1 1 6 0V25a3 3 0 0 1-3 3"/></svg>

After

Width:  |  Height:  |  Size: 276 B

View File

@ -109,6 +109,7 @@
"AnonymousPoll" = "Anonymous Poll"; "AnonymousPoll" = "Anonymous Poll";
"AccDescrReactionMentionDown" = "Go to next unread reactions"; "AccDescrReactionMentionDown" = "Go to next unread reactions";
"AccDescrMentionDown" = "Go to next mention"; "AccDescrMentionDown" = "Go to next mention";
"AccDescrPollVoteDown" = "Go to next unread poll vote";
"AccDescrPageDown" = "Go to bottom"; "AccDescrPageDown" = "Go to bottom";
"ChannelPrivate" = "Private Channel"; "ChannelPrivate" = "Private Channel";
"ChannelPrivateInfo" = "Private channels can only be joined via invite link."; "ChannelPrivateInfo" = "Private channels can only be joined via invite link.";

View File

@ -90,6 +90,10 @@
background-color: #ed504f; background-color: #ed504f;
} }
.poll {
background-color: var(--color-primary);
}
.round { .round {
display: grid; display: grid;
place-content: center; place-content: center;

View File

@ -69,6 +69,7 @@ const ChatBadge = ({
const { const {
unreadMentionsCount: stateUnreadMentionsCount = 0, unreadMentionsCount: stateUnreadMentionsCount = 0,
unreadPollVotesCount: stateUnreadPollVotesCount = 0,
unreadReactionsCount: stateUnreadReactionsCount = 0, unreadReactionsCount: stateUnreadReactionsCount = 0,
unreadCount: stateUnreadCount = 0, unreadCount: stateUnreadCount = 0,
hasUnreadMark, hasUnreadMark,
@ -95,12 +96,29 @@ const ChatBadge = ({
const topicsWithUnreadMentionsIds = useMemo(() => ( const topicsWithUnreadMentionsIds = useMemo(() => (
isForum && listedTopicIds ? listedTopicIds.filter((tId) => topicsReadStates[tId]?.unreadMentionsCount) : undefined isForum && listedTopicIds ? listedTopicIds.filter((tId) => topicsReadStates[tId]?.unreadMentionsCount) : undefined
), [listedTopicIds, isForum, topicsReadStates]); ), [listedTopicIds, isForum, topicsReadStates]);
const topicsWithUnreadPollVotesIds = useMemo(() => (
isForum && listedTopicIds ? listedTopicIds.filter((tId) => topicsReadStates[tId]?.unreadPollVotesCount) : undefined
), [listedTopicIds, isForum, topicsReadStates]);
const topicsWithUnreadReactionsIds = useMemo(() => ( const topicsWithUnreadReactionsIds = useMemo(() => (
isForum && listedTopicIds ? listedTopicIds.filter((tId) => topicsReadStates[tId]?.unreadReactionsCount) : undefined isForum && listedTopicIds ? listedTopicIds.filter((tId) => topicsReadStates[tId]?.unreadReactionsCount) : undefined
), [listedTopicIds, isForum, topicsReadStates]); ), [listedTopicIds, isForum, topicsReadStates]);
const topicsWithStatefulUnreadIds = useMemo(() => {
if (!isForum) {
return undefined;
}
const allTopicIds = [
...(topicsWithUnreadIds || []),
...(topicsWithUnreadPollVotesIds || []),
...(topicsWithUnreadReactionsIds || []),
];
return allTopicIds.length ? [...new Set(allTopicIds)] : [];
}, [isForum, topicsWithUnreadIds, topicsWithUnreadPollVotesIds, topicsWithUnreadReactionsIds]);
const unreadCount = isForum ? topicsWithUnreadIds?.length : stateUnreadCount; const unreadCount = isForum ? topicsWithUnreadIds?.length : stateUnreadCount;
const unreadMentionsCount = isForum ? topicsWithUnreadMentionsIds?.length : stateUnreadMentionsCount; const unreadMentionsCount = isForum ? topicsWithUnreadMentionsIds?.length : stateUnreadMentionsCount;
const unreadPollVotesCount = isForum ? topicsWithUnreadPollVotesIds?.length : stateUnreadPollVotesCount;
const unreadReactionsCount = isForum ? topicsWithUnreadReactionsIds?.length : stateUnreadReactionsCount; const unreadReactionsCount = isForum ? topicsWithUnreadReactionsIds?.length : stateUnreadReactionsCount;
const shouldBeUnMuted = useMemo(() => { const shouldBeUnMuted = useMemo(() => {
@ -108,17 +126,21 @@ const ChatBadge = ({
return !isMuted || topic?.notifySettings.mutedUntil === 0; return !isMuted || topic?.notifySettings.mutedUntil === 0;
} }
if (isMuted) { if (!topicsWithStatefulUnreadIds?.length) {
return topicsWithUnreadIds?.some((tId) => topicsById?.[tId]?.notifySettings.mutedUntil === 0); return !isMuted;
} }
const isEveryUnreadMuted = topicsWithUnreadIds?.every((tId) => { if (isMuted) {
return topicsWithStatefulUnreadIds.some((tId) => topicsById?.[tId]?.notifySettings.mutedUntil === 0);
}
const isEveryUnreadMuted = topicsWithStatefulUnreadIds.every((tId) => {
const mutedUntil = topicsById?.[tId]?.notifySettings.mutedUntil; const mutedUntil = topicsById?.[tId]?.notifySettings.mutedUntil;
return mutedUntil && mutedUntil > getServerTime(); return mutedUntil && mutedUntil > getServerTime();
}); });
return !isEveryUnreadMuted; return !isEveryUnreadMuted;
}, [isForum, isMuted, topicsWithUnreadIds, topicsById, topic?.notifySettings.mutedUntil]); }, [isForum, isMuted, topicsById, topic?.notifySettings.mutedUntil, topicsWithStatefulUnreadIds]);
const isUnread = Boolean((unreadCount || hasUnreadMark) && !isSavedDialog); const isUnread = Boolean((unreadCount || hasUnreadMark) && !isSavedDialog);
@ -127,7 +149,7 @@ const ChatBadge = ({
[forceHidden], [forceHidden],
); );
const isShown = !resolvedForceHidden && Boolean( const isShown = !resolvedForceHidden && Boolean(
unreadCount || unreadMentionsCount || hasUnreadMark || isPinned || unreadReactionsCount unreadCount || unreadMentionsCount || unreadPollVotesCount || hasUnreadMark || isPinned || unreadReactionsCount
|| isTopicUnopened || hasMiniApp, || isTopicUnopened || hasMiniApp,
); );
@ -152,6 +174,12 @@ const ChatBadge = ({
</div> </div>
); );
const unreadPollVotesElement = unreadPollVotesCount && (
<div className={buildClassName(statefulClassName, styles.poll, styles.round)}>
<Icon name="poll-badge" />
</div>
);
const unreadMentionsElement = unreadMentionsCount && ( const unreadMentionsElement = unreadMentionsCount && (
<div className={buildClassName(baseClassName, styles.mention, styles.round)}> <div className={buildClassName(baseClassName, styles.mention, styles.round)}>
<Icon name="mention" /> <Icon name="mention" />
@ -187,10 +215,16 @@ const ChatBadge = ({
); );
const visiblePinnedElement = !unreadCountElement && !unreadMentionsElement && !unreadReactionsElement const visiblePinnedElement = !unreadCountElement && !unreadMentionsElement && !unreadReactionsElement
&& !unreadPollVotesElement
&& pinnedElement; && pinnedElement;
const elements = [ const elements = [
unopenedTopicElement, unreadReactionsElement, unreadMentionsElement, unreadCountElement, visiblePinnedElement, unopenedTopicElement,
unreadPollVotesElement,
unreadReactionsElement,
unreadMentionsElement,
unreadCountElement,
visiblePinnedElement,
].filter(Boolean); ].filter(Boolean);
if (isSavedDialog) return pinnedElement; if (isSavedDialog) return pinnedElement;
@ -204,7 +238,11 @@ const ChatBadge = ({
if (shouldShowOnlyMostImportant) { if (shouldShowOnlyMostImportant) {
const importanceOrderedElements = [ const importanceOrderedElements = [
unreadMentionsElement, unreadCountElement, unreadReactionsElement, pinnedElement, unreadPollVotesElement,
unreadReactionsElement,
unreadMentionsElement,
unreadCountElement,
pinnedElement,
].filter(Boolean); ].filter(Boolean);
return importanceOrderedElements[0]; return importanceOrderedElements[0];
} }

View File

@ -1,6 +1,7 @@
.root { .root {
--base-bottom-pos: 5.3125rem; --base-bottom-pos: 5.3125rem;
--translate-y: 4.5rem; --translate-y: 4.5rem;
--unread-count-button-offset: 3.5rem;
pointer-events: none; pointer-events: none;
@ -14,23 +15,6 @@
transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.2s ease; transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1), opacity 0.2s ease;
.hidden {
pointer-events: none;
opacity: 0;
}
.reactions {
transition: 0.2s ease opacity, 0.25s cubic-bezier(0.34, 1.56, 0.64, 1) transform;
}
.transform-down {
transform: translateY(3.5rem);
}
.unread {
pointer-events: none;
}
:global(body.no-page-transitions) & { :global(body.no-page-transitions) & {
--translate-y: 0 !important; --translate-y: 0 !important;
@ -52,7 +36,7 @@
} }
&.hide-scroll-down { &.hide-scroll-down {
--translate-y: 3.5rem; --translate-y: var(--unread-count-button-offset);
.unread { .unread {
pointer-events: none; pointer-events: none;
@ -75,3 +59,24 @@
} }
} }
} }
.hidden {
pointer-events: none;
opacity: 0;
}
.unreadCountButton {
transition: opacity 0.2s ease, transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.transformDown {
transform: translateY(var(--unread-count-button-offset));
}
.transformDownDouble {
transform: translateY(calc(var(--unread-count-button-offset) * 2));
}
.unread {
pointer-events: none;
}

View File

@ -2,6 +2,7 @@ import { memo, useEffect, useRef } from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global'; import { getActions, withGlobal } from '../../global';
import type { MessageListType, ThreadId, ThreadReadState } from '../../types'; import type { MessageListType, ThreadId, ThreadReadState } from '../../types';
import type { IconName } from '../../types/icons';
import { selectChat, selectCurrentMessageList, selectCurrentMiddleSearch } from '../../global/selectors'; import { selectChat, selectCurrentMessageList, selectCurrentMiddleSearch } from '../../global/selectors';
import { selectThreadReadState } from '../../global/selectors/threads'; import { selectThreadReadState } from '../../global/selectors/threads';
@ -27,6 +28,16 @@ type StateProps = {
shouldShowCount?: boolean; shouldShowCount?: boolean;
}; };
type UnreadCountButton = {
icon: IconName;
ariaLabelLang: string;
unreadCount?: number;
isHidden: boolean;
hiddenUnreadCountButtonsBelow: number;
onClick: VoidFunction;
onReadAll: VoidFunction;
};
const FloatingActionButtons = ({ const FloatingActionButtons = ({
withScrollDown, withScrollDown,
canPost, canPost,
@ -38,19 +49,40 @@ const FloatingActionButtons = ({
shouldShowCount, shouldShowCount,
}: OwnProps & StateProps) => { }: OwnProps & StateProps) => {
const { const {
focusNextReply, focusNextReaction, focusNextMention, loadUnreadReactions, focusNextPollVote,
readAllMentions, readAllReactions, loadUnreadMentions, scrollMessageListToBottom, focusNextReply,
focusNextReaction,
focusNextMention,
loadUnreadPollVotes,
loadUnreadReactions,
readAllMentions,
readAllPollVotes,
readAllReactions,
loadUnreadMentions,
scrollMessageListToBottom,
} = getActions(); } = getActions();
const elementRef = useRef<HTMLDivElement>(); const elementRef = useRef<HTMLDivElement>();
const { const {
unreadReactionsCount, unreadMentionsCount, unreadCount, unreadReactions, unreadMentions, unreadPollVotesCount,
unreadReactionsCount,
unreadMentionsCount,
unreadCount,
unreadPollVotes,
unreadReactions,
unreadMentions,
} = (shouldShowCount && threadReadState) || {}; } = (shouldShowCount && threadReadState) || {};
const hasUnreadPollVotes = Boolean(unreadPollVotesCount);
const hasUnreadReactions = Boolean(unreadReactionsCount); const hasUnreadReactions = Boolean(unreadReactionsCount);
const hasUnreadMentions = Boolean(unreadMentionsCount); const hasUnreadMentions = Boolean(unreadMentionsCount);
const handleReadAllPollVotes = useLastCallback(() => {
if (!chatId) return;
readAllPollVotes({ chatId, threadId });
});
const handleReadAllReactions = useLastCallback(() => { const handleReadAllReactions = useLastCallback(() => {
if (!chatId) return; if (!chatId) return;
readAllReactions({ chatId, threadId }); readAllReactions({ chatId, threadId });
@ -61,6 +93,12 @@ const FloatingActionButtons = ({
readAllMentions({ chatId, threadId }); readAllMentions({ chatId, threadId });
}); });
useEffect(() => {
if (hasUnreadPollVotes && chatId && !unreadPollVotes?.length) {
loadUnreadPollVotes({ chatId, threadId });
}
}, [chatId, threadId, hasUnreadPollVotes, unreadPollVotes?.length]);
useEffect(() => { useEffect(() => {
if (hasUnreadReactions && chatId && !unreadReactions?.length) { if (hasUnreadReactions && chatId && !unreadReactions?.length) {
loadUnreadReactions({ chatId, threadId }); loadUnreadReactions({ chatId, threadId });
@ -90,42 +128,87 @@ const FloatingActionButtons = ({
focusNextReaction({ chatId, threadId }); focusNextReaction({ chatId, threadId });
}); });
const handleFocusNextPollVote = useLastCallback(() => {
if (!chatId) return;
focusNextPollVote({ chatId, threadId });
});
const handleFocusNextMention = useLastCallback(() => { const handleFocusNextMention = useLastCallback(() => {
if (!chatId) return; if (!chatId) return;
focusNextMention({ chatId, threadId }); focusNextMention({ chatId, threadId });
}); });
const unreadCountButtonsConfig = [
{
icon: 'poll',
ariaLabelLang: 'AccDescrPollVoteDown',
unreadCount: unreadPollVotesCount,
isHidden: !hasUnreadPollVotes,
onClick: handleFocusNextPollVote,
onReadAll: handleReadAllPollVotes,
},
{
icon: 'heart-outline',
ariaLabelLang: 'AccDescrReactionMentionDown',
unreadCount: unreadReactionsCount,
isHidden: !hasUnreadReactions,
onClick: handleFocusNextReaction,
onReadAll: handleReadAllReactions,
},
{
icon: 'mention',
ariaLabelLang: 'AccDescrMentionDown',
unreadCount: unreadMentionsCount,
isHidden: !hasUnreadMentions,
onClick: handleFocusNextMention,
onReadAll: handleReadAllMentions,
},
] satisfies Omit<UnreadCountButton, 'hiddenUnreadCountButtonsBelow'>[];
let hiddenUnreadCountButtonsBelow = 0;
const unreadCountButtons = unreadCountButtonsConfig.reduceRight<UnreadCountButton[]>((result, button) => {
result.unshift({
...button,
hiddenUnreadCountButtonsBelow,
});
if (button.isHidden) {
hiddenUnreadCountButtonsBelow++;
}
return result;
}, []);
const hasUnreadCountButtons = unreadCountButtons.some((button) => !button.isHidden);
const buildUnreadCountButtonClassName = (button: UnreadCountButton) => buildClassName(
styles.unreadCountButton,
button.isHidden && styles.hidden,
button.hiddenUnreadCountButtonsBelow === 1 && styles.transformDown,
button.hiddenUnreadCountButtonsBelow === 2 && styles.transformDownDouble,
);
const fabClassName = buildClassName( const fabClassName = buildClassName(
styles.root, styles.root,
(withScrollDown || hasUnreadReactions || hasUnreadMentions) && styles.revealed, (withScrollDown || hasUnreadCountButtons) && styles.revealed,
(hasUnreadReactions || hasUnreadMentions) && !withScrollDown && styles.hideScrollDown, hasUnreadCountButtons && !withScrollDown && styles.hideScrollDown,
!canPost && styles.noComposer, !canPost && styles.noComposer,
!withExtraShift && styles.noExtraShift, !withExtraShift && styles.noExtraShift,
); );
return ( return (
<div ref={elementRef} className={fabClassName}> <div ref={elementRef} className={fabClassName}>
<ScrollDownButton {unreadCountButtons.map((button) => (
icon="heart-outline" <ScrollDownButton
ariaLabelLang="AccDescrReactionMentionDown" key={button.icon}
onClick={handleFocusNextReaction} icon={button.icon}
onReadAll={handleReadAllReactions} ariaLabelLang={button.ariaLabelLang}
unreadCount={unreadReactionsCount} onClick={button.onClick}
className={buildClassName( onReadAll={button.onReadAll}
styles.reactions, unreadCount={button.unreadCount}
!hasUnreadReactions && styles.hidden, className={buildUnreadCountButtonClassName(button)}
!hasUnreadMentions && styles.transformDown, />
)} ))}
/>
<ScrollDownButton
icon="mention"
ariaLabelLang="AccDescrMentionDown"
onClick={handleFocusNextMention}
onReadAll={handleReadAllMentions}
unreadCount={unreadMentionsCount}
className={!hasUnreadMentions && styles.hidden}
/>
<ScrollDownButton <ScrollDownButton
icon="arrow-down" icon="arrow-down"
@ -142,7 +225,13 @@ export default memo(withGlobal<OwnProps>(
(global): Complete<StateProps> => { (global): Complete<StateProps> => {
const currentMessageList = selectCurrentMessageList(global); const currentMessageList = selectCurrentMessageList(global);
if (!currentMessageList) { if (!currentMessageList) {
return {} as Complete<StateProps>; return {
chatId: undefined,
messageListType: undefined,
shouldShowCount: undefined,
threadId: undefined,
threadReadState: undefined,
};
} }
const { chatId, threadId, type: messageListType } = currentMessageList; const { chatId, threadId, type: messageListType } = currentMessageList;

View File

@ -33,7 +33,7 @@ export default function useMessageObservers({
onIntersectPinnedMessage: OnIntersectPinnedMessage | undefined; onIntersectPinnedMessage: OnIntersectPinnedMessage | undefined;
}) { }) {
const { const {
markMessageListRead, markMentionsRead, animateUnreadReaction, markMessageListRead, markMentionsRead, markPollVotesRead, animateUnreadReaction,
scheduleForViewsIncrement, scheduleForViewsIncrement,
} = getActions(); } = getActions();
@ -61,6 +61,7 @@ export default function useMessageObservers({
let maxId = 0; let maxId = 0;
const mentionIds: number[] = []; const mentionIds: number[] = [];
const pollVoteIds: number[] = [];
const reactionIds: number[] = []; const reactionIds: number[] = [];
const scheduledToUpdateViews: number[] = []; const scheduledToUpdateViews: number[] = [];
@ -91,6 +92,10 @@ export default function useMessageObservers({
mentionIds.push(messageId); mentionIds.push(messageId);
} }
if (dataset.hasUnreadPollVote) {
pollVoteIds.push(messageId);
}
if (dataset.hasUnreadReaction) { if (dataset.hasUnreadReaction) {
reactionIds.push(messageId); reactionIds.push(messageId);
} }
@ -113,6 +118,10 @@ export default function useMessageObservers({
markMentionsRead({ chatId, messageIds: mentionIds }); markMentionsRead({ chatId, messageIds: mentionIds });
} }
if (pollVoteIds.length) {
markPollVotesRead({ chatId, messageIds: pollVoteIds });
}
if (scheduledToUpdateViews.length) { if (scheduledToUpdateViews.length) {
scheduleForViewsIncrement({ chatId, ids: scheduledToUpdateViews }); scheduleForViewsIncrement({ chatId, ids: scheduledToUpdateViews });
} }

View File

@ -102,6 +102,7 @@ type StateProps = {
isCurrentUserPremium?: boolean; isCurrentUserPremium?: boolean;
isInSelectMode?: boolean; isInSelectMode?: boolean;
hasUnreadReaction?: boolean; hasUnreadReaction?: boolean;
hasUnreadPollVote?: boolean;
isResizingContainer?: boolean; isResizingContainer?: boolean;
scrollTargetPosition?: ScrollTargetPosition; scrollTargetPosition?: ScrollTargetPosition;
isAccountFrozen?: boolean; isAccountFrozen?: boolean;
@ -140,6 +141,7 @@ const ActionMessage = ({
isCurrentUserPremium, isCurrentUserPremium,
isInSelectMode, isInSelectMode,
hasUnreadReaction, hasUnreadReaction,
hasUnreadPollVote,
isResizingContainer, isResizingContainer,
scrollTargetPosition, scrollTargetPosition,
isAccountFrozen, isAccountFrozen,
@ -161,6 +163,7 @@ const ActionMessage = ({
toggleChannelRecommendations, toggleChannelRecommendations,
animateUnreadReaction, animateUnreadReaction,
markMentionsRead, markMentionsRead,
markPollVotesRead,
focusMessage, focusMessage,
openGiftOfferAcceptModal, openGiftOfferAcceptModal,
declineStarGiftOffer, declineStarGiftOffer,
@ -335,10 +338,22 @@ const ActionMessage = ({
animateUnreadReaction({ chatId, messageIds: [id] }); animateUnreadReaction({ chatId, messageIds: [id] });
} }
if (hasUnreadPollVote) {
markPollVotesRead({ chatId, messageIds: [id] });
}
if (message.hasUnreadMention) { if (message.hasUnreadMention) {
markMentionsRead({ chatId, messageIds: [id] }); markMentionsRead({ chatId, messageIds: [id] });
} }
}, [hasUnreadReaction, chatId, id, animateUnreadReaction, message.hasUnreadMention]); }, [
hasUnreadReaction,
hasUnreadPollVote,
chatId,
id,
animateUnreadReaction,
markPollVotesRead,
message.hasUnreadMention,
]);
useEffect(() => { useEffect(() => {
if (action.type !== 'giftPremium') return; if (action.type !== 'giftPremium') return;
@ -619,6 +634,7 @@ const ActionMessage = ({
data-message-id={message.id} data-message-id={message.id}
data-is-pinned={message.isPinned || undefined} data-is-pinned={message.isPinned || undefined}
data-has-unread-mention={message.hasUnreadMention || undefined} data-has-unread-mention={message.hasUnreadMention || undefined}
data-has-unread-poll-vote={hasUnreadPollVote || undefined}
data-has-unread-reaction={hasUnreadReaction || undefined} data-has-unread-reaction={hasUnreadReaction || undefined}
onMouseDown={handleMouseDown} onMouseDown={handleMouseDown}
onContextMenu={handleContextMenu} onContextMenu={handleContextMenu}
@ -737,6 +753,7 @@ export default memo(withGlobal<OwnProps>(
const readState = selectThreadReadState(global, message.chatId, threadId); const readState = selectThreadReadState(global, message.chatId, threadId);
const hasUnreadReaction = readState?.unreadReactions?.includes(message.id); const hasUnreadReaction = readState?.unreadReactions?.includes(message.id);
const hasUnreadPollVote = readState?.unreadPollVotes?.includes(message.id);
const isAccountFrozen = selectIsCurrentUserFrozen(global); const isAccountFrozen = selectIsCurrentUserFrozen(global);
return { return {
@ -751,6 +768,7 @@ export default memo(withGlobal<OwnProps>(
isInSelectMode: selectIsInSelectMode(global), isInSelectMode: selectIsInSelectMode(global),
actionMessageBg: selectActionMessageBg(global), actionMessageBg: selectActionMessageBg(global),
hasUnreadReaction, hasUnreadReaction,
hasUnreadPollVote,
isResizingContainer, isResizingContainer,
scrollTargetPosition, scrollTargetPosition,
isAccountFrozen, isAccountFrozen,

View File

@ -301,6 +301,7 @@ type StateProps = {
defaultReaction?: ApiReaction; defaultReaction?: ApiReaction;
activeEmojiInteractions?: ActiveEmojiInteraction[]; activeEmojiInteractions?: ActiveEmojiInteraction[];
hasUnreadReaction?: boolean; hasUnreadReaction?: boolean;
hasUnreadPollVote?: boolean;
isTranscribing?: boolean; isTranscribing?: boolean;
transcribedText?: string; transcribedText?: string;
isPremium: boolean; isPremium: boolean;
@ -431,6 +432,7 @@ const Message = ({
autoLoadFileMaxSizeMb, autoLoadFileMaxSizeMb,
repliesThreadInfo, repliesThreadInfo,
hasUnreadReaction, hasUnreadReaction,
hasUnreadPollVote,
memoFirstUnreadIdRef, memoFirstUnreadIdRef,
senderChatMember, senderChatMember,
messageTopic, messageTopic,
@ -478,6 +480,7 @@ const Message = ({
animateUnreadReaction, animateUnreadReaction,
focusMessage, focusMessage,
markMentionsRead, markMentionsRead,
markPollVotesRead,
openThread, openThread,
summarizeMessage, summarizeMessage,
} = getActions(); } = getActions();
@ -963,6 +966,10 @@ const Message = ({
animateUnreadReaction({ chatId, messageIds: [messageId] }); animateUnreadReaction({ chatId, messageIds: [messageId] });
} }
if (hasUnreadPollVote) {
markPollVotesRead({ chatId, messageIds: [messageId] });
}
let unreadMentionIds: number[] = []; let unreadMentionIds: number[] = [];
if (message.hasUnreadMention) { if (message.hasUnreadMention) {
unreadMentionIds = [messageId]; unreadMentionIds = [messageId];
@ -975,7 +982,16 @@ const Message = ({
if (unreadMentionIds.length) { if (unreadMentionIds.length) {
markMentionsRead({ chatId, messageIds: unreadMentionIds }); markMentionsRead({ chatId, messageIds: unreadMentionIds });
} }
}, [hasUnreadReaction, album, chatId, messageId, animateUnreadReaction, message.hasUnreadMention]); }, [
hasUnreadReaction,
hasUnreadPollVote,
album,
chatId,
messageId,
animateUnreadReaction,
markPollVotesRead,
message.hasUnreadMention,
]);
const albumLayout = useMemo(() => { const albumLayout = useMemo(() => {
return isAlbum return isAlbum
@ -1848,6 +1864,7 @@ const Message = ({
data-last-message-id={album ? album.messages[album.messages.length - 1].id : undefined} data-last-message-id={album ? album.messages[album.messages.length - 1].id : undefined}
data-album-main-id={album ? album.mainMessage.id : undefined} data-album-main-id={album ? album.mainMessage.id : undefined}
data-has-unread-mention={message.hasUnreadMention || undefined} data-has-unread-mention={message.hasUnreadMention || undefined}
data-has-unread-poll-vote={hasUnreadPollVote || undefined}
data-has-unread-reaction={hasUnreadReaction || undefined} data-has-unread-reaction={hasUnreadReaction || undefined}
data-is-pinned={isPinned || undefined} data-is-pinned={isPinned || undefined}
data-should-update-views={message.viewsCount !== undefined} data-should-update-views={message.viewsCount !== undefined}
@ -2123,6 +2140,7 @@ export default memo(withGlobal<OwnProps>(
const readState = selectThreadReadState(global, chatId, threadId); const readState = selectThreadReadState(global, chatId, threadId);
const hasUnreadReaction = readState?.unreadReactions?.includes(message.id); const hasUnreadReaction = readState?.unreadReactions?.includes(message.id);
const hasUnreadPollVote = readState?.unreadPollVotes?.includes(message.id);
const hasTopicChip = threadId === MAIN_THREAD_ID && chat?.isForum && !chat.isBotForum && isFirstInGroup; const hasTopicChip = threadId === MAIN_THREAD_ID && chat?.isForum && !chat.isBotForum && isFirstInGroup;
const messageTopic = selectTopicFromMessage(global, message); const messageTopic = selectTopicFromMessage(global, message);
@ -2220,6 +2238,7 @@ export default memo(withGlobal<OwnProps>(
hasActiveReactions, hasActiveReactions,
activeEmojiInteractions, activeEmojiInteractions,
hasUnreadReaction, hasUnreadReaction,
hasUnreadPollVote,
isTranscribing: transcriptionId !== undefined && global.transcriptions[transcriptionId]?.isPending, isTranscribing: transcriptionId !== undefined && global.transcriptions[transcriptionId]?.isPending,
transcribedText: transcriptionId !== undefined ? global.transcriptions[transcriptionId]?.text : undefined, transcribedText: transcriptionId !== undefined ? global.transcriptions[transcriptionId]?.text : undefined,
isPremium, isPremium,

View File

@ -116,6 +116,7 @@ export const GLOBAL_SUGGESTED_CHANNELS_ID = 'global';
// https://github.com/DrKLO/Telegram/blob/51e9947527/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java#L7781 // https://github.com/DrKLO/Telegram/blob/51e9947527/TMessagesProj/src/main/java/org/telegram/messenger/MediaDataController.java#L7781
export const REACTION_UNREAD_SLICE = 100; export const REACTION_UNREAD_SLICE = 100;
export const MENTION_UNREAD_SLICE = 100; export const MENTION_UNREAD_SLICE = 100;
export const POLL_UNREAD_SLICE = 100;
export const TOPICS_SLICE = 20; export const TOPICS_SLICE = 20;
export const TOPICS_SLICE_SECOND_LOAD = 500; export const TOPICS_SLICE_SECOND_LOAD = 500;

View File

@ -1461,6 +1461,7 @@ addActionHandler('markChatMessagesRead', async (global, actions, payload): Promi
await callApi('markMessageListRead', { chat, threadId: MAIN_THREAD_ID }); await callApi('markMessageListRead', { chat, threadId: MAIN_THREAD_ID });
actions.readAllMentions({ chatId: id }); actions.readAllMentions({ chatId: id });
actions.readAllReactions({ chatId: id }); actions.readAllReactions({ chatId: id });
actions.readAllPollVotes({ chatId: id });
if (chatReadState?.hasUnreadMark) { if (chatReadState?.hasUnreadMark) {
actions.markChatRead({ id }); actions.markChatRead({ id });
} }
@ -1487,7 +1488,13 @@ addActionHandler('markChatMessagesRead', async (global, actions, payload): Promi
global = updateTopicWithState(global, id, topicWithState); global = updateTopicWithState(global, id, topicWithState);
const { readState } = topicWithState; const { readState } = topicWithState;
if (readState && !readState.unreadCount && !readState.unreadMentionsCount && !readState.unreadReactionsCount) { if (
readState
&& !readState.unreadCount
&& !readState.unreadMentionsCount
&& !readState.unreadReactionsCount
&& !readState.unreadPollVotesCount
) {
return; return;
} }
@ -1530,6 +1537,7 @@ addActionHandler('markTopicRead', (global, actions, payload): ActionReturnType =
}); });
actions.readAllMentions({ chatId, threadId: topicId }); actions.readAllMentions({ chatId, threadId: topicId });
actions.readAllReactions({ chatId, threadId: topicId }); actions.readAllReactions({ chatId, threadId: topicId });
actions.readAllPollVotes({ chatId, threadId: topicId });
global = getGlobal(); global = getGlobal();
global = replaceThreadReadStateParam(global, chatId, topicId, 'lastReadInboxMessageId', lastTopicMessageId); global = replaceThreadReadStateParam(global, chatId, topicId, 'lastReadInboxMessageId', lastTopicMessageId);

View File

@ -81,12 +81,12 @@ import {
} from '../../index'; } from '../../index';
import { import {
addChatMessagesById, addChatMessagesById,
addUnreadMentions,
clearMessageSummary, clearMessageSummary,
deleteSponsoredMessage, deleteSponsoredMessage,
removeOutlyingList, removeOutlyingList,
removeRequestedMessageTranslation, removeRequestedMessageTranslation,
removeUnreadMentions, removeUnreadMentions,
removeUnreadPollVotes,
replaceSettings, replaceSettings,
replaceUserStatuses, replaceUserStatuses,
safeReplacePinnedIds, safeReplacePinnedIds,
@ -106,6 +106,7 @@ import {
updateScheduledMessages, updateScheduledMessages,
updateSponsoredMessage, updateSponsoredMessage,
updateTopicWithState, updateTopicWithState,
updateUnreadCounters,
updateUploadByMessageKey, updateUploadByMessageKey,
updateUserFullInfo, updateUserFullInfo,
} from '../../reducers'; } from '../../reducers';
@ -172,7 +173,6 @@ import {
selectThreadReadState, selectThreadReadState,
} from '../../selectors/threads'; } from '../../selectors/threads';
import { deleteMessages, updateWithLocalMedia } from '../apiUpdaters/messages'; import { deleteMessages, updateWithLocalMedia } from '../apiUpdaters/messages';
const AUTOLOGIN_TOKEN_KEY = 'autologin_token'; const AUTOLOGIN_TOKEN_KEY = 'autologin_token';
const uploadProgressCallbacks = new Map<MessageKey, ApiOnProgress>(); const uploadProgressCallbacks = new Map<MessageKey, ApiOnProgress>();
@ -2287,19 +2287,45 @@ addActionHandler('loadUnreadMentions', async (global, actions, payload): Promise
const { messages, topics, totalCount } = result; const { messages, topics, totalCount } = result;
const byId = buildCollectionByKey(messages, 'id');
const ids = Object.keys(byId).map(Number);
global = getGlobal(); global = getGlobal();
global = addChatMessagesById(global, chat.id, byId); global = updateUnreadCounters({
topics.forEach((topicState) => {
global = updateTopicWithState(global, chat.id, topicState);
});
global = addUnreadMentions({
global, global,
chatId, chatId,
ids, threadId,
messages,
topics,
totalCount, totalCount,
unreadCountKey: 'unreadMentionsCount',
});
setGlobal(global);
});
addActionHandler('loadUnreadPollVotes', async (global, actions, payload): Promise<void> => {
const { chatId, threadId = MAIN_THREAD_ID, offsetId } = payload;
const chat = selectChat(global, chatId);
if (!chat) return;
const result = await callApi('fetchUnreadPollVotes', {
chat,
threadId: threadId !== MAIN_THREAD_ID ? threadId : undefined,
offsetId,
});
if (!result) return;
const { messages, topics, totalCount } = result;
global = getGlobal();
global = updateUnreadCounters({
global,
chatId,
threadId,
messages,
topics,
totalCount,
unreadCountKey: 'unreadPollVotesCount',
}); });
setGlobal(global); setGlobal(global);
@ -2391,6 +2417,21 @@ addActionHandler('markMentionsRead', (global, actions, payload): ActionReturnTyp
actions.markMessagesRead({ chatId, messageIds }); actions.markMessagesRead({ chatId, messageIds });
}); });
addActionHandler('markPollVotesRead', (global, actions, payload): ActionReturnType => {
const { chatId, messageIds } = payload;
const chat = selectChat(global, chatId);
if (!chat) return;
global = removeUnreadPollVotes({
global,
chatId,
ids: messageIds,
});
setGlobal(global);
actions.markMessagesRead({ chatId, messageIds });
});
addActionHandler('focusNextMention', async (global, actions, payload): Promise<void> => { addActionHandler('focusNextMention', async (global, actions, payload): Promise<void> => {
const { chatId, threadId = MAIN_THREAD_ID, tabId = getCurrentTabId() } = payload; const { chatId, threadId = MAIN_THREAD_ID, tabId = getCurrentTabId() } = payload;
@ -2407,6 +2448,28 @@ addActionHandler('focusNextMention', async (global, actions, payload): Promise<v
actions.focusMessage({ chatId, messageId: readState.unreadMentions[0], tabId }); actions.focusMessage({ chatId, messageId: readState.unreadMentions[0], tabId });
}); });
addActionHandler('focusNextPollVote', async (global, actions, payload): Promise<void> => {
const { chatId, threadId = MAIN_THREAD_ID, tabId = getCurrentTabId() } = payload;
let readState = selectThreadReadState(global, chatId, threadId);
if (!readState?.unreadPollVotes?.length) {
await getPromiseActions().loadUnreadPollVotes({ chatId, threadId });
global = getGlobal();
readState = selectThreadReadState(global, chatId, threadId);
if (!readState?.unreadPollVotes?.length) return;
}
actions.focusMessage({
chatId,
threadId,
messageId: readState.unreadPollVotes[0],
tabId,
scrollTargetPosition: 'end',
});
});
addActionHandler('readAllMentions', (global, actions, payload): ActionReturnType => { addActionHandler('readAllMentions', (global, actions, payload): ActionReturnType => {
const { chatId, threadId = MAIN_THREAD_ID } = payload; const { chatId, threadId = MAIN_THREAD_ID } = payload;
@ -2422,6 +2485,21 @@ addActionHandler('readAllMentions', (global, actions, payload): ActionReturnType
return global; return global;
}); });
addActionHandler('readAllPollVotes', (global, actions, payload): ActionReturnType => {
const { chatId, threadId = MAIN_THREAD_ID } = payload;
const chat = selectChat(global, chatId);
if (!chat) return undefined;
callApi('readAllPollVotes', { chat, threadId: threadId !== MAIN_THREAD_ID ? threadId : undefined });
global = updateThreadReadState(global, chatId, threadId, {
unreadPollVotesCount: 0,
unreadPollVotes: undefined,
});
return global;
});
addActionHandler('openUrl', (global, actions, payload): ActionReturnType => { addActionHandler('openUrl', (global, actions, payload): ActionReturnType => {
const { const {
url, shouldSkipModal, ignoreDeepLinks, linkContext, tabId = getCurrentTabId(), url, shouldSkipModal, ignoreDeepLinks, linkContext, tabId = getCurrentTabId(),

View File

@ -1,6 +1,11 @@
import type { ApiError, ApiReaction, ApiReactionEmoji } from '../../../api/types';
import type { ActionReturnType } from '../../types'; import type { ActionReturnType } from '../../types';
import { ApiMediaFormat, MAIN_THREAD_ID } from '../../../api/types'; import {
type ApiError,
ApiMediaFormat,
type ApiReaction,
type ApiReactionEmoji,
MAIN_THREAD_ID,
} from '../../../api/types';
import { GENERAL_REFETCH_INTERVAL } from '../../../config'; import { GENERAL_REFETCH_INTERVAL } from '../../../config';
import { getCurrentTabId } from '../../../util/establishMultitabRole'; import { getCurrentTabId } from '../../../util/establishMultitabRole';
@ -21,13 +26,11 @@ import {
} from '../../helpers'; } from '../../helpers';
import { addActionHandler, getGlobal, getPromiseActions, setGlobal } from '../../index'; import { addActionHandler, getGlobal, getPromiseActions, setGlobal } from '../../index';
import { import {
addChatMessagesById,
updateChatMessage, updateChatMessage,
updateTopicWithState, updateUnreadCounters,
} from '../../reducers'; } from '../../reducers';
import { import {
addMessageReaction, addMessageReaction,
addUnreadReactions,
removeUnreadReactions, removeUnreadReactions,
subtractXForEmojiInteraction, subtractXForEmojiInteraction,
} from '../../reducers/reactions'; } from '../../reducers/reactions';
@ -490,19 +493,15 @@ addActionHandler('loadUnreadReactions', async (global, actions, payload): Promis
const { messages, topics, totalCount } = result; const { messages, topics, totalCount } = result;
const byId = buildCollectionByKey(messages, 'id');
const ids = Object.keys(byId).map(Number);
global = getGlobal(); global = getGlobal();
global = addChatMessagesById(global, chat.id, byId); global = updateUnreadCounters({
topics.forEach((topicState) => {
global = updateTopicWithState(global, chat.id, topicState);
});
global = addUnreadReactions({
global, global,
chatId, chatId,
ids, threadId,
messages,
topics,
totalCount, totalCount,
unreadCountKey: 'unreadReactionsCount',
}); });
setGlobal(global); setGlobal(global);

View File

@ -8,12 +8,11 @@ import {
import { ARCHIVED_FOLDER_ID } from '../../config'; import { ARCHIVED_FOLDER_ID } from '../../config';
import { areDeepEqual } from '../../util/areDeepEqual'; import { areDeepEqual } from '../../util/areDeepEqual';
import { import {
areSortedArraysEqual, buildCollectionByKey, omit, omitUndefined, pick, unique, areSortedArraysEqual, buildCollectionByKey, omit, omitUndefined, pick,
} from '../../util/iteratees'; } from '../../util/iteratees';
import { groupMessageIdsByThreadId } from '../helpers';
import { selectChatFullInfo } from '../selectors'; import { selectChatFullInfo } from '../selectors';
import { selectThreadReadState } from '../selectors/threads'; import { updateThreadInfoLastMessageId } from './threads';
import { replaceThreadReadStateParam, updateThreadInfoLastMessageId } from './threads'; import { addUnreadCount, removeUnreadCount } from './unreadCounters';
const DEFAULT_CHAT_LISTS: ChatListType[] = ['active', 'archived']; const DEFAULT_CHAT_LISTS: ChatListType[] = ['active', 'archived'];
@ -132,28 +131,13 @@ export function addUnreadMentions<T extends GlobalState>({
ids: number[]; ids: number[];
totalCount?: number; totalCount?: number;
}): T { }): T {
const messageIdsByThreadId = groupMessageIdsByThreadId(global, chatId, ids, false); return addUnreadCount({
global,
for (const threadId in messageIdsByThreadId) { chatId,
const messageIds = messageIdsByThreadId[threadId]; messageIds: ids,
if (totalCount !== undefined) { // Assume that when `totalCount` is passed, server returned full id list totalCount,
global = replaceThreadReadStateParam(global, chatId, threadId, 'unreadMentions', messageIds); unreadCountKey: 'unreadMentionsCount',
global = replaceThreadReadStateParam(global, chatId, threadId, 'unreadMentionsCount', totalCount); });
continue;
}
const readState = selectThreadReadState(global, chatId, threadId);
const prevChatUnreadMentions = readState?.unreadMentions || [];
const updatedUnreadMentions = unique([...prevChatUnreadMentions, ...messageIds]).sort((a, b) => b - a);
global = replaceThreadReadStateParam(global, chatId, threadId, 'unreadMentions', updatedUnreadMentions);
const updatedUnreadMentionsCount = (readState?.unreadMentionsCount || 0)
+ Math.max(0, updatedUnreadMentions.length - prevChatUnreadMentions.length);
global = replaceThreadReadStateParam(global, chatId, threadId, 'unreadMentionsCount', updatedUnreadMentionsCount);
}
return global;
} }
export function removeUnreadMentions<T extends GlobalState>({ export function removeUnreadMentions<T extends GlobalState>({
@ -163,24 +147,12 @@ export function removeUnreadMentions<T extends GlobalState>({
chatId: string; chatId: string;
ids: number[]; ids: number[];
}): T { }): T {
const messageIdsByThreadId = groupMessageIdsByThreadId(global, chatId, ids, false); return removeUnreadCount({
global,
for (const threadId in messageIdsByThreadId) { chatId,
const messageIds = messageIdsByThreadId[threadId]; messageIds: ids,
const readState = selectThreadReadState(global, chatId, threadId); unreadCountKey: 'unreadMentionsCount',
const prevChatUnreadMentions = (readState?.unreadMentions || []); });
const updatedUnreadMentions = prevChatUnreadMentions?.filter((id) => !messageIds.includes(id));
global = replaceThreadReadStateParam(global, chatId, threadId, 'unreadMentions', updatedUnreadMentions);
const removedCount = prevChatUnreadMentions.length - updatedUnreadMentions.length;
if (removedCount && readState?.unreadMentionsCount) {
const updatedUnreadMentionsCount = Math.max(readState.unreadMentionsCount - removedCount, 0);
global = replaceThreadReadStateParam(global, chatId, threadId, 'unreadMentionsCount', updatedUnreadMentionsCount);
}
}
return global;
} }
export function updateChat<T extends GlobalState>( export function updateChat<T extends GlobalState>(

View File

@ -2,6 +2,7 @@ export * from './chats';
export * from './messages'; export * from './messages';
export * from './symbols'; export * from './symbols';
export * from './users'; export * from './users';
export * from './unreadCounters';
export * from './globalSearch'; export * from './globalSearch';
export * from './middleSearch'; export * from './middleSearch';
export * from './management'; export * from './management';
@ -13,5 +14,6 @@ export * from './statistics';
export * from './stories'; export * from './stories';
export * from './translations'; export * from './translations';
export * from './peers'; export * from './peers';
export * from './polls';
export * from './topics'; export * from './topics';
export * from './gifts'; export * from './gifts';

View File

@ -52,6 +52,7 @@ import {
import { selectThreadIdFromMessage, selectThreadInfo, selectThreadLocalStateParam } from '../selectors/threads'; import { selectThreadIdFromMessage, selectThreadInfo, selectThreadLocalStateParam } from '../selectors/threads';
import { removeUnreadMentions } from './chats'; import { removeUnreadMentions } from './chats';
import { removeIdFromSearchResults } from './middleSearch'; import { removeIdFromSearchResults } from './middleSearch';
import { removeUnreadPollVotes } from './polls';
import { removeUnreadReactions } from './reactions'; import { removeUnreadReactions } from './reactions';
import { updateTabState } from './tabs'; import { updateTabState } from './tabs';
import { import {
@ -437,6 +438,7 @@ export function deleteChatMessages<T extends GlobalState>(
global = removeUnreadReactions({ global, chatId, ids: messageIds }); global = removeUnreadReactions({ global, chatId, ids: messageIds });
global = removeUnreadMentions({ global, chatId, ids: messageIds }); global = removeUnreadMentions({ global, chatId, ids: messageIds });
global = removeUnreadPollVotes({ global, chatId, ids: messageIds });
const newById = omit(byId, messageIds); const newById = omit(byId, messageIds);
global = replaceChatMessages(global, chatId, newById); global = replaceChatMessages(global, chatId, newById);

View File

@ -0,0 +1,35 @@
import type { GlobalState } from '../types';
import { addUnreadCount, removeUnreadCount } from './unreadCounters';
export function addUnreadPollVotes<T extends GlobalState>({
global, chatId, ids, totalCount,
}: {
global: T;
chatId: string;
ids: number[];
totalCount?: number;
}): T {
return addUnreadCount({
global,
chatId,
messageIds: ids,
totalCount,
unreadCountKey: 'unreadPollVotesCount',
});
}
export function removeUnreadPollVotes<T extends GlobalState>({
global, chatId, ids,
}: {
global: T;
chatId: string;
ids: number[];
}): T {
return removeUnreadCount({
global,
chatId,
messageIds: ids,
unreadCountKey: 'unreadPollVotesCount',
});
}

View File

@ -2,17 +2,15 @@ import type { GlobalState } from '../types';
import { type ApiMessage, type ApiReactionWithPaid } from '../../api/types'; import { type ApiMessage, type ApiReactionWithPaid } from '../../api/types';
import { MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN, MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN } from '../../config'; import { MIN_SCREEN_WIDTH_FOR_STATIC_LEFT_COLUMN, MIN_SCREEN_WIDTH_FOR_STATIC_RIGHT_COLUMN } from '../../config';
import { unique } from '../../util/iteratees';
import windowSize from '../../util/windowSize'; import windowSize from '../../util/windowSize';
import { import {
MIN_LEFT_COLUMN_WIDTH, MIN_LEFT_COLUMN_WIDTH,
SIDE_COLUMN_MAX_WIDTH, SIDE_COLUMN_MAX_WIDTH,
} from '../../components/middle/helpers/calculateMiddleFooterTransforms'; } from '../../components/middle/helpers/calculateMiddleFooterTransforms';
import { groupMessageIdsByThreadId, updateReactionCount } from '../helpers'; import { updateReactionCount } from '../helpers';
import { selectIsChatWithSelf, selectSendAs, selectTabState } from '../selectors'; import { selectIsChatWithSelf, selectSendAs, selectTabState } from '../selectors';
import { selectThreadReadState } from '../selectors/threads';
import { updateChatMessage } from './messages'; import { updateChatMessage } from './messages';
import { replaceThreadReadStateParam } from './threads'; import { addUnreadCount, removeUnreadCount } from './unreadCounters';
import { getIsMobile } from '../../hooks/useAppLayout'; import { getIsMobile } from '../../hooks/useAppLayout';
@ -85,29 +83,13 @@ export function addUnreadReactions<T extends GlobalState>({
ids: number[]; ids: number[];
totalCount?: number; totalCount?: number;
}): T { }): T {
const messageIdsByThreadId = groupMessageIdsByThreadId(global, chatId, ids, false); return addUnreadCount({
global,
for (const threadId in messageIdsByThreadId) { chatId,
const messageIds = messageIdsByThreadId[threadId]; messageIds: ids,
if (totalCount !== undefined) { // Assume that when `totalCount` is passed, server returned full id list totalCount,
global = replaceThreadReadStateParam(global, chatId, threadId, 'unreadReactions', messageIds); unreadCountKey: 'unreadReactionsCount',
global = replaceThreadReadStateParam(global, chatId, threadId, 'unreadReactionsCount', totalCount); });
continue;
}
const readState = selectThreadReadState(global, chatId, threadId);
const prevChatUnreadReactions = readState?.unreadReactions || [];
const updatedUnreadReactions = unique([...prevChatUnreadReactions, ...messageIds]).sort((a, b) => b - a);
global = replaceThreadReadStateParam(global, chatId, threadId, 'unreadReactions', updatedUnreadReactions);
const delta = updatedUnreadReactions.length - prevChatUnreadReactions.length;
if (delta > 0) {
const unreadReactionsCount = (readState?.unreadReactionsCount || 0) + delta;
global = replaceThreadReadStateParam(global, chatId, threadId, 'unreadReactionsCount', unreadReactionsCount);
}
}
return global;
} }
export function removeUnreadReactions<T extends GlobalState>({ export function removeUnreadReactions<T extends GlobalState>({
@ -117,20 +99,10 @@ export function removeUnreadReactions<T extends GlobalState>({
chatId: string; chatId: string;
ids: number[]; ids: number[];
}): T { }): T {
const messageIdsByThreadId = groupMessageIdsByThreadId(global, chatId, ids, false); return removeUnreadCount({
global,
for (const threadId in messageIdsByThreadId) { chatId,
const messageIds = messageIdsByThreadId[threadId]; messageIds: ids,
const readState = selectThreadReadState(global, chatId, threadId); unreadCountKey: 'unreadReactionsCount',
const prevChatUnreadReactions = readState?.unreadReactions || []; });
const updatedUnreadReactions = prevChatUnreadReactions.filter((id) => !messageIds.includes(id));
global = replaceThreadReadStateParam(global, chatId, threadId, 'unreadReactions', updatedUnreadReactions);
const delta = prevChatUnreadReactions.length - updatedUnreadReactions.length;
if (delta > 0 && readState?.unreadReactionsCount) {
const unreadReactionsCount = Math.max(readState.unreadReactionsCount - delta, 0);
global = replaceThreadReadStateParam(global, chatId, threadId, 'unreadReactionsCount', unreadReactionsCount);
}
}
return global;
} }

View File

@ -0,0 +1,170 @@
import type { ApiMessage, ApiTopicWithState } from '../../api/types';
import type { ThreadId, ThreadReadState } from '../../types';
import type { GlobalState } from '../types';
import { buildCollectionByKey, unique } from '../../util/iteratees';
import { groupMessageIdsByThreadId } from '../helpers';
import { selectThreadReadState } from '../selectors/threads';
import { addChatMessagesById } from './messages';
import { replaceThreadReadStateParam } from './threads';
import { updateTopicWithState } from './topics';
type UnreadCountIdsKey = 'unreadMentions' | 'unreadReactions' | 'unreadPollVotes';
export type UnreadCountKey = 'unreadMentionsCount' | 'unreadReactionsCount' | 'unreadPollVotesCount';
type AddUnreadCountArgs<T extends GlobalState, TKey extends UnreadCountKey> = {
global: T;
chatId: string;
messageIds: number[];
totalCount?: number;
unreadCountKey: TKey;
};
type RemoveUnreadCountArgs<T extends GlobalState, TKey extends UnreadCountKey> = {
global: T;
chatId: string;
messageIds: number[];
unreadCountKey: TKey;
};
type UpdateUnreadCountersArgs<T extends GlobalState, TKey extends UnreadCountKey> = {
global: T;
chatId: string;
threadId: ThreadId;
messages: ApiMessage[];
topics: ApiTopicWithState[];
totalCount: number;
unreadCountKey: TKey;
};
const unreadIdsKeyByCountKey = {
unreadMentionsCount: 'unreadMentions',
unreadReactionsCount: 'unreadReactions',
unreadPollVotesCount: 'unreadPollVotes',
} satisfies Record<UnreadCountKey, UnreadCountIdsKey>;
function getUnreadIdsKey<TKey extends UnreadCountKey>(unreadCountKey: TKey) {
return unreadIdsKeyByCountKey[unreadCountKey];
}
function replaceThreadUnreadIds<T extends GlobalState, TKey extends UnreadCountKey>(
global: T,
chatId: string,
threadId: ThreadId,
unreadCountKey: TKey,
messageIds: number[] | undefined,
) {
return replaceThreadReadStateParam(global, chatId, threadId, getUnreadIdsKey(unreadCountKey), messageIds);
}
function replaceThreadUnreadCount<T extends GlobalState, TKey extends UnreadCountKey>(
global: T,
chatId: string,
threadId: ThreadId,
unreadCountKey: TKey,
count: number | undefined,
) {
return replaceThreadReadStateParam(global, chatId, threadId, unreadCountKey, count);
}
function selectThreadUnreadIds<TKey extends UnreadCountKey>(
readState: ThreadReadState | undefined,
unreadCountKey: TKey,
) {
return readState?.[getUnreadIdsKey(unreadCountKey)];
}
export function addUnreadCount<T extends GlobalState, TKey extends UnreadCountKey>({
global,
chatId,
messageIds,
totalCount,
unreadCountKey,
}: AddUnreadCountArgs<T, TKey>): T {
const messageIdsByThreadId = groupMessageIdsByThreadId(global, chatId, messageIds, false);
for (const threadId in messageIdsByThreadId) {
const threadMessageIds = messageIdsByThreadId[threadId];
if (totalCount !== undefined) {
global = replaceThreadUnreadIds(global, chatId, threadId, unreadCountKey, threadMessageIds);
global = replaceThreadUnreadCount(global, chatId, threadId, unreadCountKey, totalCount);
continue;
}
const readState = selectThreadReadState(global, chatId, threadId);
const previousIds = selectThreadUnreadIds(readState, unreadCountKey) || [];
const updatedIds = unique([...previousIds, ...threadMessageIds]).sort((a, b) => b - a);
global = replaceThreadUnreadIds(global, chatId, threadId, unreadCountKey, updatedIds);
const delta = updatedIds.length - previousIds.length;
if (delta > 0) {
global = replaceThreadUnreadCount(
global,
chatId,
threadId,
unreadCountKey,
(readState?.[unreadCountKey] || 0) + delta,
);
}
}
return global;
}
export function removeUnreadCount<T extends GlobalState, TKey extends UnreadCountKey>({
global,
chatId,
messageIds,
unreadCountKey,
}: RemoveUnreadCountArgs<T, TKey>): T {
const messageIdsByThreadId = groupMessageIdsByThreadId(global, chatId, messageIds, false);
for (const threadId in messageIdsByThreadId) {
const threadMessageIds = messageIdsByThreadId[threadId];
const threadMessageIdSet = new Set(threadMessageIds);
const readState = selectThreadReadState(global, chatId, threadId);
const previousIds = selectThreadUnreadIds(readState, unreadCountKey) || [];
const updatedIds = previousIds.filter((id) => !threadMessageIdSet.has(id));
global = replaceThreadUnreadIds(global, chatId, threadId, unreadCountKey, updatedIds);
const previousCount = readState?.[unreadCountKey];
const delta = previousIds.length - updatedIds.length;
if (delta > 0 && previousCount) {
global = replaceThreadUnreadCount(
global,
chatId,
threadId,
unreadCountKey,
Math.max(previousCount - delta, 0),
);
}
}
return global;
}
export function updateUnreadCounters<T extends GlobalState, TKey extends UnreadCountKey>({
global,
chatId,
threadId,
messages,
topics,
totalCount,
unreadCountKey,
}: UpdateUnreadCountersArgs<T, TKey>): T {
const messagesById = buildCollectionByKey(messages, 'id');
global = addChatMessagesById(global, chatId, messagesById);
topics.forEach((topicState) => {
global = updateTopicWithState(global, chatId, topicState);
});
const messageIds = Object.keys(messagesById).map(Number).sort((a, b) => b - a);
global = replaceThreadUnreadIds(global, chatId, threadId, unreadCountKey, messageIds);
global = replaceThreadUnreadCount(global, chatId, threadId, unreadCountKey, totalCount);
return global;
}

View File

@ -1434,6 +1434,11 @@ export interface ActionPayloads {
threadId?: ThreadId; threadId?: ThreadId;
offsetId?: number; offsetId?: number;
}; };
loadUnreadPollVotes: {
chatId: string;
threadId?: ThreadId;
offsetId?: number;
};
scheduleForViewsIncrement: { scheduleForViewsIncrement: {
chatId: string; chatId: string;
ids: number[]; ids: number[];
@ -1468,6 +1473,10 @@ export interface ActionPayloads {
chatId: string; chatId: string;
threadId?: ThreadId; threadId?: ThreadId;
} & WithTabId; } & WithTabId;
focusNextPollVote: {
chatId: string;
threadId?: ThreadId;
} & WithTabId;
readAllReactions: { readAllReactions: {
chatId: string; chatId: string;
threadId?: ThreadId; threadId?: ThreadId;
@ -1476,10 +1485,18 @@ export interface ActionPayloads {
chatId: string; chatId: string;
threadId?: ThreadId; threadId?: ThreadId;
}; };
readAllPollVotes: {
chatId: string;
threadId?: ThreadId;
};
markMentionsRead: { markMentionsRead: {
chatId: string; chatId: string;
messageIds: number[]; messageIds: number[];
}; };
markPollVotesRead: {
chatId: string;
messageIds: number[];
};
copyMessageLink: { copyMessageLink: {
chatId: string; chatId: string;
messageId: number; messageId: number;

View File

@ -1812,6 +1812,8 @@ messages.editChatParticipantRank#a00f32b0 peer:InputPeer participant:InputPeer r
messages.declineUrlAuth#35436bbc url:string = Bool; messages.declineUrlAuth#35436bbc url:string = Bool;
messages.checkUrlAuthMatchCode#c9a47b0b url:string match_code:string = Bool; messages.checkUrlAuthMatchCode#c9a47b0b url:string match_code:string = Bool;
messages.composeMessageWithAI#fd426afe flags:# proofread:flags.0?true emojify:flags.3?true text:TextWithEntities translate_to_lang:flags.1?string change_tone:flags.2?string = messages.ComposedMessageWithAI; messages.composeMessageWithAI#fd426afe flags:# proofread:flags.0?true emojify:flags.3?true text:TextWithEntities translate_to_lang:flags.1?string change_tone:flags.2?string = messages.ComposedMessageWithAI;
messages.getUnreadPollVotes#43286cf2 flags:# peer:InputPeer top_msg_id:flags.0?int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
messages.readPollVotes#1720b4d8 flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory;
updates.getState#edd4882a = updates.State; updates.getState#edd4882a = updates.State;
updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference; updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.1?int pts_total_limit:flags.0?int date:int qts:int qts_limit:flags.2?int = updates.Difference;
updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference; updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference;

View File

@ -224,6 +224,8 @@
"messages.clearRecentReactions", "messages.clearRecentReactions",
"messages.readMentions", "messages.readMentions",
"messages.getUnreadMentions", "messages.getUnreadMentions",
"messages.readPollVotes",
"messages.getUnreadPollVotes",
"messages.getSavedDialogs", "messages.getSavedDialogs",
"messages.getSavedHistory", "messages.getSavedHistory",
"messages.deleteSavedHistory", "messages.deleteSavedHistory",

File diff suppressed because it is too large Load Diff

View File

@ -24,330 +24,328 @@ $icons-map: (
"add-user": "\f106", "add-user": "\f106",
"add": "\f107", "add": "\f107",
"admin": "\f108", "admin": "\f108",
"ai-edit": "\f109", "allow-share": "\f109",
"ai-fix": "\f10a", "allow-speak": "\f10a",
"ai": "\f10b", "animals": "\f10b",
"allow-share": "\f10c", "animations": "\f10c",
"allow-speak": "\f10d", "archive-filled": "\f10d",
"animals": "\f10e", "archive-from-main": "\f10e",
"animations": "\f10f", "archive-to-main": "\f10f",
"archive-filled": "\f110", "archive": "\f110",
"archive-from-main": "\f111", "arrow-down-circle": "\f111",
"archive-to-main": "\f112", "arrow-down": "\f112",
"archive": "\f113", "arrow-left": "\f113",
"arrow-down-circle": "\f114", "arrow-right": "\f114",
"arrow-down": "\f115", "ask-support": "\f115",
"arrow-left": "\f116", "attach": "\f116",
"arrow-right": "\f117", "auction-drop": "\f117",
"ask-support": "\f118", "auction-filled": "\f118",
"attach": "\f119", "auction-next-round": "\f119",
"auction-drop": "\f11a", "auction": "\f11a",
"auction-filled": "\f11b", "author-hidden": "\f11b",
"auction-next-round": "\f11c", "avatar-archived-chats": "\f11c",
"auction": "\f11d", "avatar-deleted-account": "\f11d",
"author-hidden": "\f11e", "avatar-saved-messages": "\f11e",
"avatar-archived-chats": "\f11f", "bold": "\f11f",
"avatar-deleted-account": "\f120", "boost-craft-chance": "\f120",
"avatar-saved-messages": "\f121", "boost-outline": "\f121",
"bold": "\f122", "boost": "\f122",
"boost-craft-chance": "\f123", "boostcircle": "\f123",
"boost-outline": "\f124", "boosts": "\f124",
"boost": "\f125", "bot-command": "\f125",
"boostcircle": "\f126", "bot-commands-filled": "\f126",
"boosts": "\f127", "bots": "\f127",
"bot-command": "\f128", "brush": "\f128",
"bot-commands-filled": "\f129", "bug": "\f129",
"bots": "\f12a", "calendar-filter": "\f12a",
"brush": "\f12b", "calendar": "\f12b",
"bug": "\f12c", "camera-add": "\f12c",
"calendar-filter": "\f12d", "camera": "\f12d",
"calendar": "\f12e", "car": "\f12e",
"camera-add": "\f12f", "card": "\f12f",
"camera": "\f130", "cash-circle": "\f130",
"car": "\f131", "channel-filled": "\f131",
"card": "\f132", "channel": "\f132",
"cash-circle": "\f133", "channelviews": "\f133",
"channel-filled": "\f134", "chat-badge": "\f134",
"channel": "\f135", "chats-badge": "\f135",
"channelviews": "\f136", "check-bold": "\f136",
"chat-badge": "\f137", "check": "\f137",
"chats-badge": "\f138", "clock-edit": "\f138",
"check-bold": "\f139", "clock": "\f139",
"check": "\f13a", "close-circle": "\f13a",
"clock-edit": "\f13b", "close-topic": "\f13b",
"clock": "\f13c", "close": "\f13c",
"close-circle": "\f13d", "closed-gift": "\f13d",
"close-topic": "\f13e", "cloud-download": "\f13e",
"close": "\f13f", "collapse-modal": "\f13f",
"closed-gift": "\f140", "collapse": "\f140",
"cloud-download": "\f141", "colorize": "\f141",
"collapse-modal": "\f142", "combine-craft": "\f142",
"collapse": "\f143", "comments-sticker": "\f143",
"colorize": "\f144", "comments": "\f144",
"combine-craft": "\f145", "copy-media": "\f145",
"comments-sticker": "\f146", "copy": "\f146",
"comments": "\f147", "craft": "\f147",
"copy-media": "\f148", "crop": "\f148",
"copy": "\f149", "crown-take-off-outline": "\f149",
"craft": "\f14a", "crown-take-off": "\f14a",
"crop": "\f14b", "crown-wear-outline": "\f14b",
"crown-take-off-outline": "\f14c", "crown-wear": "\f14c",
"crown-take-off": "\f14d", "darkmode": "\f14d",
"crown-wear-outline": "\f14e", "data": "\f14e",
"crown-wear": "\f14f", "delete-filled": "\f14f",
"darkmode": "\f150", "delete-left": "\f150",
"data": "\f151", "delete-user": "\f151",
"delete-filled": "\f152", "delete": "\f152",
"delete-left": "\f153", "diamond": "\f153",
"delete-user": "\f154", "document": "\f154",
"delete": "\f155", "double-badge": "\f155",
"diamond": "\f156", "down": "\f156",
"document": "\f157", "download": "\f157",
"double-badge": "\f158", "dropdown-arrows": "\f158",
"down": "\f159", "eats": "\f159",
"download": "\f15a", "edit": "\f15a",
"dropdown-arrows": "\f15b", "email": "\f15b",
"eats": "\f15c", "enter": "\f15c",
"edit": "\f15d", "expand-modal": "\f15d",
"email": "\f15e", "expand": "\f15e",
"enter": "\f15f", "eye-crossed-outline": "\f15f",
"expand-modal": "\f160", "eye-crossed": "\f160",
"expand": "\f161", "eye-outline": "\f161",
"eye-crossed-outline": "\f162", "eye": "\f162",
"eye-crossed": "\f163", "favorite-filled": "\f163",
"eye-outline": "\f164", "favorite": "\f164",
"eye": "\f165", "file-badge": "\f165",
"favorite-filled": "\f166", "flag": "\f166",
"favorite": "\f167", "flip": "\f167",
"file-badge": "\f168", "folder-badge": "\f168",
"flag": "\f169", "folder-tabs-bot": "\f169",
"flip": "\f16a", "folder-tabs-channel": "\f16a",
"folder-badge": "\f16b", "folder-tabs-chat": "\f16b",
"folder-tabs-bot": "\f16c", "folder-tabs-chats": "\f16c",
"folder-tabs-channel": "\f16d", "folder-tabs-folder": "\f16d",
"folder-tabs-chat": "\f16e", "folder-tabs-group": "\f16e",
"folder-tabs-chats": "\f16f", "folder-tabs-star": "\f16f",
"folder-tabs-folder": "\f170", "folder-tabs-user": "\f170",
"folder-tabs-group": "\f171", "folder": "\f171",
"folder-tabs-star": "\f172", "fontsize": "\f172",
"folder-tabs-user": "\f173", "forums": "\f173",
"folder": "\f174", "forward": "\f174",
"fontsize": "\f175", "fragment": "\f175",
"forums": "\f176", "frozen-time": "\f176",
"forward": "\f177", "fullscreen": "\f177",
"fragment": "\f178", "gifs": "\f178",
"frozen-time": "\f179", "gift-transfer-inline": "\f179",
"fullscreen": "\f17a", "gift": "\f17a",
"gifs": "\f17b", "group-filled": "\f17b",
"gift-transfer-inline": "\f17c", "group": "\f17c",
"gift": "\f17d", "grouped-disable": "\f17d",
"group-filled": "\f17e", "grouped": "\f17e",
"group": "\f17f", "hand-stop-filled": "\f17f",
"grouped-disable": "\f180", "hand-stop": "\f180",
"grouped": "\f181", "hashtag": "\f181",
"hand-stop-filled": "\f182", "hd-photo": "\f182",
"hand-stop": "\f183", "heart-outline": "\f183",
"hashtag": "\f184", "heart": "\f184",
"hd-photo": "\f185", "help": "\f185",
"heart-outline": "\f186", "info-filled": "\f186",
"heart": "\f187", "info": "\f187",
"help": "\f188", "install": "\f188",
"info-filled": "\f189", "italic": "\f189",
"info": "\f18a", "key": "\f18a",
"install": "\f18b", "keyboard": "\f18b",
"italic": "\f18c", "lamp": "\f18c",
"key": "\f18d", "language": "\f18d",
"keyboard": "\f18e", "large-pause": "\f18e",
"lamp": "\f18f", "large-play": "\f18f",
"language": "\f190", "link-badge": "\f190",
"large-pause": "\f191", "link-broken": "\f191",
"large-play": "\f192", "link": "\f192",
"link-badge": "\f193", "location": "\f193",
"link-broken": "\f194", "lock-badge": "\f194",
"link": "\f195", "lock": "\f195",
"location": "\f196", "logout": "\f196",
"lock-badge": "\f197", "loop": "\f197",
"lock": "\f198", "mention": "\f198",
"logout": "\f199", "menu": "\f199",
"loop": "\f19a", "message-failed": "\f19a",
"mention": "\f19b", "message-pending": "\f19b",
"menu": "\f19c", "message-read": "\f19c",
"message-failed": "\f19d", "message-succeeded": "\f19d",
"message-pending": "\f19e", "message": "\f19e",
"message-read": "\f19f", "microphone-alt": "\f19f",
"message-succeeded": "\f1a0", "microphone": "\f1a0",
"message": "\f1a1", "monospace": "\f1a1",
"microphone-alt": "\f1a2", "more-circle": "\f1a2",
"microphone": "\f1a3", "more": "\f1a3",
"monospace": "\f1a4", "move-caption-down": "\f1a4",
"more-circle": "\f1a5", "move-caption-up": "\f1a5",
"more": "\f1a6", "mute": "\f1a6",
"move-caption-down": "\f1a7", "muted": "\f1a7",
"move-caption-up": "\f1a8", "my-notes": "\f1a8",
"mute": "\f1a9", "new-chat-filled": "\f1a9",
"muted": "\f1aa", "new-send": "\f1aa",
"my-notes": "\f1ab", "next-link": "\f1ab",
"new-chat-filled": "\f1ac", "next": "\f1ac",
"new-send": "\f1ad", "no-download": "\f1ad",
"next-link": "\f1ae", "no-share": "\f1ae",
"next": "\f1af", "nochannel": "\f1af",
"no-download": "\f1b0", "noise-suppression": "\f1b0",
"no-share": "\f1b1", "non-contacts": "\f1b1",
"nochannel": "\f1b2", "note": "\f1b2",
"noise-suppression": "\f1b3", "one-filled": "\f1b3",
"non-contacts": "\f1b4", "open-in-new-tab": "\f1b4",
"note": "\f1b5", "password-off": "\f1b5",
"one-filled": "\f1b6", "pause": "\f1b6",
"open-in-new-tab": "\f1b7", "permissions": "\f1b7",
"password-off": "\f1b8", "phone-discard-outline": "\f1b8",
"pause": "\f1b9", "phone-discard": "\f1b9",
"permissions": "\f1ba", "phone": "\f1ba",
"phone-discard-outline": "\f1bb", "photo": "\f1bb",
"phone-discard": "\f1bc", "pin-badge": "\f1bc",
"phone": "\f1bd", "pin-list": "\f1bd",
"photo": "\f1be", "pin": "\f1be",
"pin-badge": "\f1bf", "pinned-chat": "\f1bf",
"pin-list": "\f1c0", "pinned-message": "\f1c0",
"pin": "\f1c1", "pip": "\f1c1",
"pinned-chat": "\f1c2", "play-story": "\f1c2",
"pinned-message": "\f1c3", "play": "\f1c3",
"pip": "\f1c4", "poll-badge": "\f1c4",
"play-story": "\f1c5", "poll": "\f1c5",
"play": "\f1c6", "previous-link": "\f1c6",
"poll": "\f1c7", "previous": "\f1c7",
"previous-link": "\f1c8", "privacy-policy": "\f1c8",
"previous": "\f1c9", "proof-of-ownership": "\f1c9",
"privacy-policy": "\f1ca", "quote-text": "\f1ca",
"proof-of-ownership": "\f1cb", "quote": "\f1cb",
"quote-text": "\f1cc", "radial-badge": "\f1cc",
"quote": "\f1cd", "rating-icons-level1": "\f1cd",
"radial-badge": "\f1ce", "rating-icons-level10": "\f1ce",
"rating-icons-level1": "\f1cf", "rating-icons-level2": "\f1cf",
"rating-icons-level10": "\f1d0", "rating-icons-level20": "\f1d0",
"rating-icons-level2": "\f1d1", "rating-icons-level3": "\f1d1",
"rating-icons-level20": "\f1d2", "rating-icons-level30": "\f1d2",
"rating-icons-level3": "\f1d3", "rating-icons-level4": "\f1d3",
"rating-icons-level30": "\f1d4", "rating-icons-level40": "\f1d4",
"rating-icons-level4": "\f1d5", "rating-icons-level5": "\f1d5",
"rating-icons-level40": "\f1d6", "rating-icons-level50": "\f1d6",
"rating-icons-level5": "\f1d7", "rating-icons-level6": "\f1d7",
"rating-icons-level50": "\f1d8", "rating-icons-level60": "\f1d8",
"rating-icons-level6": "\f1d9", "rating-icons-level7": "\f1d9",
"rating-icons-level60": "\f1da", "rating-icons-level70": "\f1da",
"rating-icons-level7": "\f1db", "rating-icons-level8": "\f1db",
"rating-icons-level70": "\f1dc", "rating-icons-level80": "\f1dc",
"rating-icons-level8": "\f1dd", "rating-icons-level9": "\f1dd",
"rating-icons-level80": "\f1de", "rating-icons-level90": "\f1de",
"rating-icons-level9": "\f1df", "rating-icons-negative": "\f1df",
"rating-icons-level90": "\f1e0", "readchats": "\f1e0",
"rating-icons-negative": "\f1e1", "recent": "\f1e1",
"readchats": "\f1e2", "redo": "\f1e2",
"recent": "\f1e3", "refund": "\f1e3",
"redo": "\f1e4", "reload": "\f1e4",
"refund": "\f1e5", "remove-quote": "\f1e5",
"reload": "\f1e6", "remove": "\f1e6",
"remove-quote": "\f1e7", "reopen-topic": "\f1e7",
"remove": "\f1e8", "reorder-tabs": "\f1e8",
"reopen-topic": "\f1e9", "replace": "\f1e9",
"reorder-tabs": "\f1ea", "replies": "\f1ea",
"replace": "\f1eb", "reply-filled": "\f1eb",
"replies": "\f1ec", "reply": "\f1ec",
"reply-filled": "\f1ed", "revenue-split": "\f1ed",
"reply": "\f1ee", "revote": "\f1ee",
"revenue-split": "\f1ef", "rotate": "\f1ef",
"revote": "\f1f0", "save-story": "\f1f0",
"rotate": "\f1f1", "saved-messages": "\f1f1",
"save-story": "\f1f2", "schedule": "\f1f2",
"saved-messages": "\f1f3", "scheduled": "\f1f3",
"schedule": "\f1f4", "sd-photo": "\f1f4",
"scheduled": "\f1f5", "search": "\f1f5",
"sd-photo": "\f1f6", "select-filled": "\f1f6",
"search": "\f1f7", "select": "\f1f7",
"select-filled": "\f1f8", "sell-outline": "\f1f8",
"select": "\f1f9", "sell": "\f1f9",
"sell-outline": "\f1fa", "send-outline": "\f1fa",
"sell": "\f1fb", "send": "\f1fb",
"send-outline": "\f1fc", "settings-filled": "\f1fc",
"send": "\f1fd", "settings": "\f1fd",
"settings-filled": "\f1fe", "share-filled": "\f1fe",
"settings": "\f1ff", "share-screen-outlined": "\f1ff",
"share-filled": "\f200", "share-screen-stop": "\f200",
"share-screen-outlined": "\f201", "share-screen": "\f201",
"share-screen-stop": "\f202", "show-message": "\f202",
"share-screen": "\f203", "sidebar": "\f203",
"show-message": "\f204", "skip-next": "\f204",
"sidebar": "\f205", "skip-previous": "\f205",
"skip-next": "\f206", "smallscreen": "\f206",
"skip-previous": "\f207", "smile": "\f207",
"smallscreen": "\f208", "sort-by-date": "\f208",
"smile": "\f209", "sort-by-number": "\f209",
"sort-by-date": "\f20a", "sort-by-price": "\f20a",
"sort-by-number": "\f20b", "sort": "\f20b",
"sort-by-price": "\f20c", "speaker-muted-story": "\f20c",
"sort": "\f20d", "speaker-outline": "\f20d",
"speaker-muted-story": "\f20e", "speaker-story": "\f20e",
"speaker-outline": "\f20f", "speaker": "\f20f",
"speaker-story": "\f210", "spoiler-disable": "\f210",
"speaker": "\f211", "spoiler": "\f211",
"spoiler-disable": "\f212", "sport": "\f212",
"spoiler": "\f213", "star": "\f213",
"sport": "\f214", "stars-lock": "\f214",
"star": "\f215", "stars-refund": "\f215",
"stars-lock": "\f216", "stats": "\f216",
"stars-refund": "\f217", "stealth-future": "\f217",
"stats": "\f218", "stealth-past": "\f218",
"stealth-future": "\f219", "stickers": "\f219",
"stealth-past": "\f21a", "stop-raising-hand": "\f21a",
"stickers": "\f21b", "stop": "\f21b",
"stop-raising-hand": "\f21c", "story-caption": "\f21c",
"stop": "\f21d", "story-expired": "\f21d",
"story-caption": "\f21e", "story-priority": "\f21e",
"story-expired": "\f21f", "story-reply": "\f21f",
"story-priority": "\f220", "strikethrough": "\f220",
"story-reply": "\f221", "tag-add": "\f221",
"strikethrough": "\f222", "tag-crossed": "\f222",
"tag-add": "\f223", "tag-filter": "\f223",
"tag-crossed": "\f224", "tag-name": "\f224",
"tag-filter": "\f225", "tag": "\f225",
"tag-name": "\f226", "timer": "\f226",
"tag": "\f227", "toncoin": "\f227",
"timer": "\f228", "tools": "\f228",
"toncoin": "\f229", "topic-new": "\f229",
"tools": "\f22a", "trade": "\f22a",
"topic-new": "\f22b", "transcribe": "\f22b",
"trade": "\f22c", "truck": "\f22c",
"transcribe": "\f22d", "unarchive": "\f22d",
"truck": "\f22e", "underlined": "\f22e",
"unarchive": "\f22f", "understood": "\f22f",
"underlined": "\f230", "undo": "\f230",
"understood": "\f231", "unique-profile": "\f231",
"undo": "\f232", "unlist-outline": "\f232",
"unique-profile": "\f233", "unlist": "\f233",
"unlist-outline": "\f234", "unlock-badge": "\f234",
"unlist": "\f235", "unlock": "\f235",
"unlock-badge": "\f236", "unmute": "\f236",
"unlock": "\f237", "unpin": "\f237",
"unmute": "\f238", "unread": "\f238",
"unpin": "\f239", "up": "\f239",
"unread": "\f23a", "user-filled": "\f23a",
"up": "\f23b", "user-online": "\f23b",
"user-filled": "\f23c", "user-stars": "\f23c",
"user-online": "\f23d", "user-tag": "\f23d",
"user-stars": "\f23e", "user": "\f23e",
"user-tag": "\f23f", "video-outlined": "\f23f",
"user": "\f240", "video-stop": "\f240",
"video-outlined": "\f241", "video": "\f241",
"video-stop": "\f242", "view-once": "\f242",
"video": "\f243", "voice-chat": "\f243",
"view-once": "\f244", "volume-1": "\f244",
"voice-chat": "\f245", "volume-2": "\f245",
"volume-1": "\f246", "volume-3": "\f246",
"volume-2": "\f247", "warning": "\f247",
"volume-3": "\f248", "web": "\f248",
"warning": "\f249", "webapp": "\f249",
"web": "\f24a", "word-wrap": "\f24a",
"webapp": "\f24b", "zoom-in": "\f24b",
"word-wrap": "\f24c", "zoom-out": "\f24c",
"zoom-in": "\f24d",
"zoom-out": "\f24e",
); );

Binary file not shown.

Binary file not shown.

View File

@ -197,6 +197,7 @@ export type FontIconName =
| 'pip' | 'pip'
| 'play-story' | 'play-story'
| 'play' | 'play'
| 'poll-badge'
| 'poll' | 'poll'
| 'previous-link' | 'previous-link'
| 'previous' | 'previous'

View File

@ -623,8 +623,10 @@ export interface ThreadReadState {
unreadCount?: number; unreadCount?: number;
unreadMentionsCount?: number; unreadMentionsCount?: number;
unreadReactionsCount?: number; unreadReactionsCount?: number;
unreadPollVotesCount?: number;
unreadReactions?: number[]; unreadReactions?: number[];
unreadMentions?: number[]; unreadMentions?: number[];
unreadPollVotes?: number[];
hasUnreadMark?: boolean; hasUnreadMark?: boolean;
lastReadOutboxMessageId?: number; lastReadOutboxMessageId?: number;

View File

@ -91,6 +91,7 @@ export interface LangPair {
'AnonymousPoll': undefined; 'AnonymousPoll': undefined;
'AccDescrReactionMentionDown': undefined; 'AccDescrReactionMentionDown': undefined;
'AccDescrMentionDown': undefined; 'AccDescrMentionDown': undefined;
'AccDescrPollVoteDown': undefined;
'AccDescrPageDown': undefined; 'AccDescrPageDown': undefined;
'ChannelPrivate': undefined; 'ChannelPrivate': undefined;
'ChannelPrivateInfo': undefined; 'ChannelPrivateInfo': undefined;