Poll: Refactor update handling (#5090)
This commit is contained in:
parent
6ab84ff12f
commit
c5c87347d9
@ -58,7 +58,7 @@ export function buildMessageContent(
|
||||
const hasUnsupportedMedia = mtpMessage.media instanceof GramJs.MessageMediaUnsupported;
|
||||
|
||||
if (mtpMessage.message && !hasUnsupportedMedia
|
||||
&& !content.sticker && !content.poll && !content.contact && !content.video?.isRound) {
|
||||
&& !content.sticker && !content.pollId && !content.contact && !content.video?.isRound) {
|
||||
content = {
|
||||
...content,
|
||||
text: buildMessageTextContent(mtpMessage.message, mtpMessage.entities),
|
||||
@ -130,8 +130,8 @@ export function buildMessageMediaContent(
|
||||
const contact = buildContact(media);
|
||||
if (contact) return { contact };
|
||||
|
||||
const poll = buildPollFromMedia(media);
|
||||
if (poll) return { poll };
|
||||
const pollId = buildPollIdFromMedia(media);
|
||||
if (pollId) return { pollId };
|
||||
|
||||
const webPage = buildWebPage(media);
|
||||
if (webPage) return { webPage };
|
||||
@ -465,7 +465,15 @@ function buildContact(media: GramJs.TypeMessageMedia): ApiContact | undefined {
|
||||
};
|
||||
}
|
||||
|
||||
function buildPollFromMedia(media: GramJs.TypeMessageMedia): ApiPoll | undefined {
|
||||
function buildPollIdFromMedia(media: GramJs.TypeMessageMedia): string | undefined {
|
||||
if (!(media instanceof GramJs.MessageMediaPoll)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return media.poll.id.toString();
|
||||
}
|
||||
|
||||
export function buildPollFromMedia(media: GramJs.TypeMessageMedia): ApiPoll | undefined {
|
||||
if (!(media instanceof GramJs.MessageMediaPoll)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@ -935,30 +935,30 @@ export function buildLocalMessage(
|
||||
story?: ApiStory | ApiStorySkipped,
|
||||
isInvertedMedia?: true,
|
||||
effectId?: string,
|
||||
): ApiMessage {
|
||||
) {
|
||||
const localId = getNextLocalMessageId(lastMessageId);
|
||||
const media = attachment && buildUploadingMedia(attachment);
|
||||
const isChannel = chat.type === 'chatTypeChannel';
|
||||
|
||||
const resultReplyInfo = replyInfo && buildReplyInfo(replyInfo, chat.isForum);
|
||||
|
||||
const localPoll = poll && buildNewPoll(poll, localId);
|
||||
|
||||
const message = {
|
||||
id: localId,
|
||||
chatId: chat.id,
|
||||
content: {
|
||||
...(text && {
|
||||
text: {
|
||||
text,
|
||||
entities,
|
||||
},
|
||||
}),
|
||||
content: omitUndefined({
|
||||
text: text ? {
|
||||
text,
|
||||
entities,
|
||||
} : undefined,
|
||||
...media,
|
||||
...(sticker && { sticker }),
|
||||
...(gif && { video: gif }),
|
||||
...(poll && { poll: buildNewPoll(poll, localId) }),
|
||||
...(contact && { contact }),
|
||||
...(story && { storyData: { mediaType: 'storyData', ...story } }),
|
||||
},
|
||||
sticker,
|
||||
video: gif || media?.video,
|
||||
contact,
|
||||
storyData: story && { mediaType: 'storyData', ...story },
|
||||
pollId: localPoll?.id,
|
||||
}),
|
||||
date: scheduledAt || Math.round(Date.now() / 1000) + getServerTimeOffset(),
|
||||
isOutgoing: !isChannel,
|
||||
senderId: sendAs?.id || currentUserId,
|
||||
@ -975,10 +975,15 @@ export function buildLocalMessage(
|
||||
|
||||
const emojiOnlyCount = getEmojiOnlyCountForMessage(message.content, message.groupedId);
|
||||
|
||||
return {
|
||||
const finalMessage = {
|
||||
...message,
|
||||
...(emojiOnlyCount && { emojiOnlyCount }),
|
||||
};
|
||||
|
||||
return {
|
||||
message: finalMessage,
|
||||
poll: localPoll,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildLocalForwardedMessage({
|
||||
|
||||
@ -293,7 +293,10 @@ export function sendMessage(
|
||||
},
|
||||
onProgress?: ApiOnProgress,
|
||||
) {
|
||||
const localMessage = buildLocalMessage(
|
||||
const {
|
||||
message: localMessage,
|
||||
poll: localPoll,
|
||||
} = buildLocalMessage(
|
||||
chat,
|
||||
lastMessageId,
|
||||
text,
|
||||
@ -317,6 +320,7 @@ export function sendMessage(
|
||||
id: localMessage.id,
|
||||
chatId: chat.id,
|
||||
message: localMessage,
|
||||
poll: localPoll,
|
||||
wasDrafted,
|
||||
});
|
||||
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
import { Api as GramJs } from '../../../lib/gramjs';
|
||||
|
||||
import type { ApiChat, ApiThreadInfo, ApiUser } from '../../types';
|
||||
import type {
|
||||
ApiChat, ApiPoll, ApiThreadInfo, ApiUser,
|
||||
} from '../../types';
|
||||
|
||||
import { buildCollectionByKey } from '../../../util/iteratees';
|
||||
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||
import { buildPollFromMedia } from '../apiBuilders/messageContent';
|
||||
import { buildApiThreadInfoFromMessage } from '../apiBuilders/messages';
|
||||
import { buildApiUser } from '../apiBuilders/users';
|
||||
import { addChatToLocalDb, addMessageToLocalDb, addUserToLocalDb } from '../helpers';
|
||||
@ -19,7 +22,8 @@ export function processAndUpdateEntities(response?: GramJs.AnyRequest['__respons
|
||||
|
||||
let userById: Record<string, ApiUser> | undefined;
|
||||
let chatById: Record<string, ApiChat> | undefined;
|
||||
let threadInfos: ApiThreadInfo[] | undefined;
|
||||
const threadInfos: ApiThreadInfo[] | undefined = [];
|
||||
const polls: ApiPoll[] | undefined = [];
|
||||
|
||||
if ('users' in response && Array.isArray(response.users) && TYPE_USER.has(response.users[0]?.className)) {
|
||||
const users = response.users.map((user) => {
|
||||
@ -42,19 +46,29 @@ export function processAndUpdateEntities(response?: GramJs.AnyRequest['__respons
|
||||
}
|
||||
|
||||
if ('messages' in response && Array.isArray(response.messages) && TYPE_MESSAGE.has(response.messages[0]?.className)) {
|
||||
threadInfos = response.messages.map((message) => {
|
||||
response.messages.forEach((message) => {
|
||||
addMessageToLocalDb(message);
|
||||
return buildApiThreadInfoFromMessage(message);
|
||||
}).filter(Boolean);
|
||||
|
||||
const threadInfo = buildApiThreadInfoFromMessage(message);
|
||||
if (threadInfo) {
|
||||
threadInfos.push(threadInfo);
|
||||
}
|
||||
|
||||
const poll = buildPollFromMedia(message.media);
|
||||
if (poll) {
|
||||
polls.push(poll);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!userById && !chatById && !threadInfos) return;
|
||||
if (!userById && !chatById && !threadInfos?.length) return;
|
||||
|
||||
sendImmediateApiUpdate({
|
||||
'@type': 'updateEntities',
|
||||
users: userById,
|
||||
chats: chatById,
|
||||
threadInfos,
|
||||
threadInfos: threadInfos?.length ? threadInfos : undefined,
|
||||
polls: polls?.length ? polls : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@ -9,7 +9,9 @@ import type {
|
||||
ApiPremiumGiftCodeOption,
|
||||
ApiStarGift,
|
||||
} from './payments';
|
||||
import type { ApiMessageStoryData, ApiWebPageStickerData, ApiWebPageStoryData } from './stories';
|
||||
import type {
|
||||
ApiMessageStoryData, ApiStory, ApiWebPageStickerData, ApiWebPageStoryData,
|
||||
} from './stories';
|
||||
import type { ApiUser } from './users';
|
||||
|
||||
export interface ApiDimensions {
|
||||
@ -667,7 +669,7 @@ export type MediaContent = {
|
||||
document?: ApiDocument;
|
||||
sticker?: ApiSticker;
|
||||
contact?: ApiContact;
|
||||
poll?: ApiPoll;
|
||||
pollId?: string;
|
||||
action?: ApiAction;
|
||||
webPage?: ApiWebPage;
|
||||
audio?: ApiAudio;
|
||||
@ -687,6 +689,11 @@ export type MediaContainer = {
|
||||
content: MediaContent;
|
||||
};
|
||||
|
||||
export type StatefulMediaContent = {
|
||||
poll?: ApiPoll;
|
||||
story?: ApiStory;
|
||||
};
|
||||
|
||||
export type BoughtPaidMedia = Pick<MediaContent, 'photo' | 'video'>;
|
||||
|
||||
export interface ApiMessage {
|
||||
|
||||
@ -204,6 +204,7 @@ export type ApiUpdateNewScheduledMessage = {
|
||||
id: number;
|
||||
message: ApiMessage;
|
||||
wasDrafted?: boolean;
|
||||
poll?: ApiPoll;
|
||||
};
|
||||
|
||||
export type ApiUpdateNewMessage = {
|
||||
@ -213,6 +214,7 @@ export type ApiUpdateNewMessage = {
|
||||
message: Partial<ApiMessage>;
|
||||
shouldForceReply?: boolean;
|
||||
wasDrafted?: boolean;
|
||||
poll?: ApiPoll;
|
||||
};
|
||||
|
||||
export type ApiUpdateMessage = {
|
||||
@ -764,6 +766,7 @@ export type ApiUpdateEntities = {
|
||||
users?: Record<string, ApiUser>;
|
||||
chats?: Record<string, ApiChat>;
|
||||
threadInfos?: ApiThreadInfo[];
|
||||
polls?: ApiPoll[];
|
||||
};
|
||||
|
||||
export type ApiUpdatePaidReactionPrivacy = {
|
||||
|
||||
@ -1,12 +1,16 @@
|
||||
import React, { memo } from '../../lib/teact/teact';
|
||||
import { withGlobal } from '../../global';
|
||||
|
||||
import type { ApiFormattedText, ApiMessage } from '../../api/types';
|
||||
import type {
|
||||
ApiFormattedText, ApiMessage, ApiPoll, ApiTypeStory,
|
||||
} from '../../api/types';
|
||||
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
||||
import { ApiMessageEntityTypes } from '../../api/types';
|
||||
|
||||
import {
|
||||
extractMessageText,
|
||||
getMessagePoll,
|
||||
getMessagePollId,
|
||||
groupStatetefulContent,
|
||||
} from '../../global/helpers';
|
||||
import {
|
||||
getMessageSummaryDescription,
|
||||
@ -14,6 +18,7 @@ import {
|
||||
getMessageSummaryText,
|
||||
TRUNCATED_SUMMARY_LENGTH,
|
||||
} from '../../global/helpers/messageSummary';
|
||||
import { selectPeerStory, selectPollFromMessage } from '../../global/selectors';
|
||||
import trimText from '../../util/trimText';
|
||||
import renderText from './helpers/renderText';
|
||||
|
||||
@ -21,7 +26,7 @@ import useOldLang from '../../hooks/useOldLang';
|
||||
|
||||
import MessageText from './MessageText';
|
||||
|
||||
interface OwnProps {
|
||||
type OwnProps = {
|
||||
message: ApiMessage;
|
||||
translatedText?: ApiFormattedText;
|
||||
noEmoji?: boolean;
|
||||
@ -32,7 +37,12 @@ interface OwnProps {
|
||||
emojiSize?: number;
|
||||
observeIntersectionForLoading?: ObserveFn;
|
||||
observeIntersectionForPlaying?: ObserveFn;
|
||||
}
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
poll?: ApiPoll;
|
||||
story?: ApiTypeStory;
|
||||
};
|
||||
|
||||
function MessageSummary({
|
||||
message,
|
||||
@ -43,17 +53,22 @@ function MessageSummary({
|
||||
withTranslucentThumbs = false,
|
||||
inChatList = false,
|
||||
emojiSize,
|
||||
poll,
|
||||
story,
|
||||
observeIntersectionForLoading,
|
||||
observeIntersectionForPlaying,
|
||||
}: OwnProps) {
|
||||
}: OwnProps & StateProps) {
|
||||
const lang = useOldLang();
|
||||
const { text, entities } = extractMessageText(message, inChatList) || {};
|
||||
const hasSpoilers = entities?.some((e) => e.type === ApiMessageEntityTypes.Spoiler);
|
||||
const hasCustomEmoji = entities?.some((e) => e.type === ApiMessageEntityTypes.CustomEmoji);
|
||||
const hasPoll = Boolean(getMessagePoll(message));
|
||||
const hasPoll = Boolean(getMessagePollId(message));
|
||||
|
||||
const statefulContent = groupStatetefulContent({ poll, story });
|
||||
|
||||
if ((!text || (!hasSpoilers && !hasCustomEmoji)) && !hasPoll) {
|
||||
const summaryText = translatedText?.text || getMessageSummaryText(lang, message, noEmoji, truncateLength);
|
||||
const summaryText = translatedText?.text
|
||||
|| getMessageSummaryText(lang, message, statefulContent, noEmoji, truncateLength);
|
||||
const trimmedText = trimText(summaryText, truncateLength);
|
||||
|
||||
return (
|
||||
@ -90,10 +105,21 @@ function MessageSummary({
|
||||
<>
|
||||
{[
|
||||
emoji ? renderText(`${emoji} `) : undefined,
|
||||
getMessageSummaryDescription(lang, message, renderMessageText()),
|
||||
getMessageSummaryDescription(lang, message, statefulContent, renderMessageText()),
|
||||
].flat().filter(Boolean)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(MessageSummary);
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { message }): StateProps => {
|
||||
const poll = selectPollFromMessage(global, message);
|
||||
const storyData = message.content.storyData;
|
||||
const story = storyData && selectPeerStory(global, storyData.peerId, storyData.id);
|
||||
|
||||
return {
|
||||
poll,
|
||||
story,
|
||||
};
|
||||
},
|
||||
)(MessageSummary));
|
||||
|
||||
@ -164,7 +164,7 @@ const EmbeddedMessage: FC<OwnProps> = ({
|
||||
|
||||
function renderMediaContentType(media?: MediaContainer) {
|
||||
if (!media || media.content.text) return NBSP;
|
||||
const description = getMediaContentTypeDescription(lang, media.content);
|
||||
const description = getMediaContentTypeDescription(lang, media.content, {});
|
||||
if (!description || description === CONTENT_NOT_SUPPORTED) return NBSP;
|
||||
return (
|
||||
<span>
|
||||
|
||||
@ -278,7 +278,7 @@ function renderMessageContent(
|
||||
const { asPlainText, isEmbedded } = options;
|
||||
|
||||
if (asPlainText) {
|
||||
return getMessageSummaryText(lang, message, undefined, MAX_LENGTH);
|
||||
return getMessageSummaryText(lang, message, undefined, undefined, MAX_LENGTH);
|
||||
}
|
||||
|
||||
const messageSummary = (
|
||||
|
||||
@ -1,9 +1,12 @@
|
||||
import { getGlobal } from '../../../global';
|
||||
|
||||
import type { ApiMessage, ApiSponsoredMessage } from '../../../api/types';
|
||||
import type { LangFn } from '../../../hooks/useOldLang';
|
||||
import type { TextPart } from '../../../types';
|
||||
import { ApiMessageEntityTypes } from '../../../api/types';
|
||||
|
||||
import {
|
||||
getMessageStatefulContent,
|
||||
getMessageText,
|
||||
} from '../../../global/helpers';
|
||||
import {
|
||||
@ -70,10 +73,13 @@ export function renderMessageSummary(
|
||||
): TextPart[] {
|
||||
const { entities } = message.content.text || {};
|
||||
|
||||
const global = getGlobal();
|
||||
const statefulContent = getMessageStatefulContent(global, message);
|
||||
|
||||
const hasSpoilers = entities?.some((e) => e.type === ApiMessageEntityTypes.Spoiler);
|
||||
const hasCustomEmoji = entities?.some((e) => e.type === ApiMessageEntityTypes.CustomEmoji);
|
||||
if (!hasSpoilers && !hasCustomEmoji) {
|
||||
const text = trimText(getMessageSummaryText(lang, message, noEmoji), truncateLength);
|
||||
const text = trimText(getMessageSummaryText(lang, message, statefulContent, noEmoji), truncateLength);
|
||||
|
||||
if (highlight) {
|
||||
return renderText(text, ['emoji', 'highlight'], { highlight });
|
||||
@ -88,7 +94,7 @@ export function renderMessageSummary(
|
||||
const text = renderMessageText({
|
||||
message, highlight, isSimple: true, truncateLength,
|
||||
});
|
||||
const description = getMessageSummaryDescription(lang, message, text);
|
||||
const description = getMessageSummaryDescription(lang, message, statefulContent, text);
|
||||
|
||||
return [
|
||||
...renderText(emojiWithSpace),
|
||||
|
||||
@ -8,6 +8,7 @@ import type {
|
||||
ApiMessageOutgoingStatus,
|
||||
ApiPeer,
|
||||
ApiTopic,
|
||||
ApiTypeStory,
|
||||
ApiTypingStatus,
|
||||
ApiUser,
|
||||
ApiUserStatus,
|
||||
@ -21,6 +22,7 @@ import { StoryViewerOrigin } from '../../../types';
|
||||
import {
|
||||
getMessageAction,
|
||||
getPrivateChatUserId,
|
||||
groupStatetefulContent,
|
||||
isUserId,
|
||||
isUserOnline,
|
||||
selectIsChatMuted,
|
||||
@ -40,6 +42,7 @@ import {
|
||||
selectNotifySettings,
|
||||
selectOutgoingStatus,
|
||||
selectPeer,
|
||||
selectPeerStory,
|
||||
selectTabState,
|
||||
selectThreadParam,
|
||||
selectTopicFromMessage,
|
||||
@ -92,6 +95,7 @@ type OwnProps = {
|
||||
|
||||
type StateProps = {
|
||||
chat?: ApiChat;
|
||||
lastMessageStory?: ApiTypeStory;
|
||||
listedTopicIds?: number[];
|
||||
topics?: Record<number, ApiTopic>;
|
||||
isMuted?: boolean;
|
||||
@ -127,6 +131,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
||||
topics,
|
||||
observeIntersection,
|
||||
chat,
|
||||
lastMessageStory,
|
||||
isMuted,
|
||||
user,
|
||||
userStatus,
|
||||
@ -187,6 +192,7 @@ const Chat: FC<OwnProps & StateProps> = ({
|
||||
lastMessage,
|
||||
typingStatus,
|
||||
draft,
|
||||
statefulMediaContent: groupStatetefulContent({ story: lastMessageStory }),
|
||||
actionTargetMessage,
|
||||
actionTargetUserIds,
|
||||
actionTargetChatId,
|
||||
@ -483,6 +489,9 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
const topicsInfo = selectTopicsInfo(global, chatId);
|
||||
|
||||
const storyData = lastMessage?.content.storyData;
|
||||
const lastMessageStory = storyData && selectPeerStory(global, storyData.peerId, storyData.id);
|
||||
|
||||
return {
|
||||
chat,
|
||||
isMuted: selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global)),
|
||||
@ -510,6 +519,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
listedTopicIds: topicsInfo?.listedTopicIds,
|
||||
topics: topicsInfo?.topicsById,
|
||||
isSynced: global.isSynced,
|
||||
lastMessageStory,
|
||||
};
|
||||
},
|
||||
)(Chat));
|
||||
|
||||
@ -4,13 +4,13 @@ import { getActions, withGlobal } from '../../../global';
|
||||
|
||||
import type {
|
||||
ApiChat, ApiMessage, ApiMessageOutgoingStatus,
|
||||
ApiPeer, ApiTopic, ApiTypingStatus,
|
||||
ApiPeer, ApiTopic, ApiTypeStory, ApiTypingStatus,
|
||||
} from '../../../api/types';
|
||||
import type { ApiDraft } from '../../../global/types';
|
||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||
import type { ChatAnimationTypes } from './hooks';
|
||||
|
||||
import { getMessageAction } from '../../../global/helpers';
|
||||
import { getMessageAction, groupStatetefulContent } from '../../../global/helpers';
|
||||
import { getMessageReplyInfo } from '../../../global/helpers/replies';
|
||||
import {
|
||||
selectCanAnimateInterface,
|
||||
@ -20,6 +20,7 @@ import {
|
||||
selectCurrentMessageList,
|
||||
selectDraft,
|
||||
selectOutgoingStatus,
|
||||
selectPeerStory,
|
||||
selectThreadInfo,
|
||||
selectThreadParam,
|
||||
selectTopics,
|
||||
@ -59,6 +60,7 @@ type StateProps = {
|
||||
chat: ApiChat;
|
||||
canDelete?: boolean;
|
||||
lastMessage?: ApiMessage;
|
||||
lastMessageStory?: ApiTypeStory;
|
||||
lastMessageOutgoingStatus?: ApiMessageOutgoingStatus;
|
||||
actionTargetMessage?: ApiMessage;
|
||||
actionTargetUserIds?: string[];
|
||||
@ -79,6 +81,7 @@ const Topic: FC<OwnProps & StateProps> = ({
|
||||
chat,
|
||||
style,
|
||||
lastMessage,
|
||||
lastMessageStory,
|
||||
canScrollDown,
|
||||
lastMessageOutgoingStatus,
|
||||
observeIntersection,
|
||||
@ -142,6 +145,7 @@ const Topic: FC<OwnProps & StateProps> = ({
|
||||
isTopic: true,
|
||||
typingStatus,
|
||||
topics,
|
||||
statefulMediaContent: groupStatetefulContent({ story: lastMessageStory }),
|
||||
|
||||
animationType,
|
||||
withInterfaceAnimations,
|
||||
@ -262,6 +266,9 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
const { chatId: currentChatId, threadId: currentThreadId } = selectCurrentMessageList(global) || {};
|
||||
|
||||
const storyData = lastMessage?.content.storyData;
|
||||
const lastMessageStory = storyData && selectPeerStory(global, storyData.peerId, storyData.id);
|
||||
|
||||
return {
|
||||
chat,
|
||||
lastMessage,
|
||||
@ -279,6 +286,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
canScrollDown: isSelected && chat?.id === currentChatId && currentThreadId === topic.id,
|
||||
wasTopicOpened,
|
||||
topics,
|
||||
lastMessageStory,
|
||||
};
|
||||
},
|
||||
)(Topic));
|
||||
|
||||
@ -5,6 +5,7 @@ import { getGlobal } from '../../../../global';
|
||||
|
||||
import type {
|
||||
ApiChat, ApiMessage, ApiPeer, ApiTopic, ApiTypingStatus, ApiUser,
|
||||
StatefulMediaContent,
|
||||
} from '../../../../api/types';
|
||||
import type { ApiDraft } from '../../../../global/types';
|
||||
import type { ObserveFn } from '../../../../hooks/useIntersectionObserver';
|
||||
@ -32,6 +33,7 @@ import { renderTextWithEntities } from '../../../common/helpers/renderTextWithEn
|
||||
import { ChatAnimationTypes } from './useChatAnimationType';
|
||||
|
||||
import useEnsureMessage from '../../../../hooks/useEnsureMessage';
|
||||
import useEnsureStory from '../../../../hooks/useEnsureStory';
|
||||
import useMedia from '../../../../hooks/useMedia';
|
||||
import useOldLang from '../../../../hooks/useOldLang';
|
||||
|
||||
@ -45,6 +47,7 @@ export default function useChatListEntry({
|
||||
chat,
|
||||
topics,
|
||||
lastMessage,
|
||||
statefulMediaContent,
|
||||
chatId,
|
||||
typingStatus,
|
||||
draft,
|
||||
@ -64,6 +67,7 @@ export default function useChatListEntry({
|
||||
chat?: ApiChat;
|
||||
topics?: Record<number, ApiTopic>;
|
||||
lastMessage?: ApiMessage;
|
||||
statefulMediaContent: StatefulMediaContent | undefined;
|
||||
chatId: string;
|
||||
typingStatus?: ApiTypingStatus;
|
||||
draft?: ApiDraft;
|
||||
@ -90,10 +94,16 @@ export default function useChatListEntry({
|
||||
const replyToMessageId = lastMessage && getMessageReplyInfo(lastMessage)?.replyToMsgId;
|
||||
useEnsureMessage(chatId, isAction ? replyToMessageId : undefined, actionTargetMessage);
|
||||
|
||||
const mediaHasPreview = lastMessage && !getMessageSticker(lastMessage);
|
||||
const storyData = lastMessage?.content.storyData;
|
||||
const shouldTryLoadingStory = statefulMediaContent && !statefulMediaContent.story;
|
||||
|
||||
const mediaThumbnail = mediaHasPreview ? getMessageMediaThumbDataUri(lastMessage) : undefined;
|
||||
const mediaBlobUrl = useMedia(mediaHasPreview ? getMessageMediaHash(lastMessage, 'micro') : undefined);
|
||||
useEnsureStory(shouldTryLoadingStory ? storyData?.peerId : undefined, storyData?.id, statefulMediaContent?.story);
|
||||
|
||||
const mediaContent = statefulMediaContent?.story || lastMessage;
|
||||
const mediaHasPreview = mediaContent && !getMessageSticker(mediaContent);
|
||||
|
||||
const mediaThumbnail = mediaHasPreview ? getMessageMediaThumbDataUri(mediaContent) : undefined;
|
||||
const mediaBlobUrl = useMedia(mediaHasPreview ? getMessageMediaHash(mediaContent, 'micro') : undefined);
|
||||
const isRoundVideo = Boolean(lastMessage && getMessageRoundVideo(lastMessage));
|
||||
|
||||
const actionTargetUsers = useMemo(() => {
|
||||
|
||||
@ -8,10 +8,12 @@ import type {
|
||||
ApiAvailableReaction,
|
||||
ApiChatReactions,
|
||||
ApiMessage,
|
||||
ApiPoll,
|
||||
ApiReaction,
|
||||
ApiStickerSet,
|
||||
ApiStickerSetInfo,
|
||||
ApiThreadInfo,
|
||||
ApiTypeStory,
|
||||
} from '../../../api/types';
|
||||
import type { ActiveDownloads, MessageListType } from '../../../global/types';
|
||||
import type { IAlbum, IAnchorPosition, ThreadId } from '../../../types';
|
||||
@ -50,6 +52,8 @@ import {
|
||||
selectIsReactionPickerOpen,
|
||||
selectMessageCustomEmojiSets,
|
||||
selectMessageTranslations,
|
||||
selectPeerStory,
|
||||
selectPollFromMessage,
|
||||
selectRequestedChatTranslationLanguage,
|
||||
selectRequestedMessageTranslationLanguage,
|
||||
selectStickerSet,
|
||||
@ -88,6 +92,8 @@ export type OwnProps = {
|
||||
|
||||
type StateProps = {
|
||||
threadId?: ThreadId;
|
||||
poll?: ApiPoll;
|
||||
story?: ApiTypeStory;
|
||||
availableReactions?: ApiAvailableReaction[];
|
||||
topReactions?: ApiReaction[];
|
||||
defaultTagReactions?: ApiReaction[];
|
||||
@ -150,6 +156,8 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
customEmojiSetsInfo,
|
||||
customEmojiSets,
|
||||
album,
|
||||
poll,
|
||||
story,
|
||||
anchor,
|
||||
targetHref,
|
||||
noOptions,
|
||||
@ -642,6 +650,8 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
||||
seenByRecentPeers={seenByRecentPeers}
|
||||
isInSavedMessages={isInSavedMessages}
|
||||
noReplies={noReplies}
|
||||
poll={poll}
|
||||
story={story}
|
||||
onOpenThread={handleOpenThread}
|
||||
onReply={handleReply}
|
||||
onEdit={handleEdit}
|
||||
@ -795,6 +805,10 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
const isInSavedMessages = selectIsChatWithSelf(global, message.chatId);
|
||||
|
||||
const poll = selectPollFromMessage(global, message);
|
||||
const storyData = message.content.storyData;
|
||||
const story = storyData ? selectPeerStory(global, storyData.peerId, storyData.id) : undefined;
|
||||
|
||||
return {
|
||||
threadId,
|
||||
availableReactions,
|
||||
@ -845,6 +859,8 @@ export default memo(withGlobal<OwnProps>(
|
||||
isChannel,
|
||||
canReplyInChat,
|
||||
isWithPaidReaction: chatFullInfo?.isPaidReactionAvailable,
|
||||
poll,
|
||||
story,
|
||||
};
|
||||
},
|
||||
)(ContextMenuContainer));
|
||||
|
||||
@ -19,6 +19,7 @@ import type {
|
||||
ApiMessage,
|
||||
ApiMessageOutgoingStatus,
|
||||
ApiPeer,
|
||||
ApiPoll,
|
||||
ApiReaction,
|
||||
ApiReactionKey,
|
||||
ApiSavedReactionTag,
|
||||
@ -90,6 +91,7 @@ import {
|
||||
selectPeer,
|
||||
selectPeerStory,
|
||||
selectPerformanceSettingsValue,
|
||||
selectPollFromMessage,
|
||||
selectRequestedChatTranslationLanguage,
|
||||
selectRequestedMessageTranslationLanguage,
|
||||
selectSender,
|
||||
@ -295,6 +297,7 @@ type StateProps = {
|
||||
viaBusinessBot?: ApiUser;
|
||||
effect?: ApiAvailableEffect;
|
||||
availableStars?: number;
|
||||
poll?: ApiPoll;
|
||||
};
|
||||
|
||||
type MetaPosition =
|
||||
@ -416,6 +419,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
viaBusinessBot,
|
||||
effect,
|
||||
availableStars,
|
||||
poll,
|
||||
onIntersectPinnedMessage,
|
||||
}) => {
|
||||
const {
|
||||
@ -505,7 +509,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
const {
|
||||
photo = paidMediaPhoto, video = paidMediaVideo, audio,
|
||||
voice, document, sticker, contact,
|
||||
poll, webPage, invoice, location,
|
||||
webPage, invoice, location,
|
||||
action, game, storyData, giveaway,
|
||||
giveawayResults,
|
||||
} = getMessageContent(message);
|
||||
@ -741,6 +745,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
||||
&& (isCustomShape || ((photo || video || storyData || (location?.mediaType === 'geo')) && !hasText));
|
||||
|
||||
const contentClassName = buildContentClassName(message, album, {
|
||||
poll,
|
||||
hasSubheader,
|
||||
isCustomShape,
|
||||
isLastInGroup,
|
||||
@ -1818,6 +1823,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
const effect = effectId ? global.availableEffectById[effectId] : undefined;
|
||||
|
||||
const { balance: availableStars } = global.stars || {};
|
||||
const poll = selectPollFromMessage(global, message);
|
||||
|
||||
return {
|
||||
theme: selectTheme(global),
|
||||
@ -1906,6 +1912,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
viaBusinessBot,
|
||||
effect,
|
||||
availableStars,
|
||||
poll,
|
||||
};
|
||||
},
|
||||
)(Message));
|
||||
|
||||
@ -10,15 +10,17 @@ import type {
|
||||
ApiChatReactions,
|
||||
ApiMessage,
|
||||
ApiPeer,
|
||||
ApiPoll,
|
||||
ApiReaction,
|
||||
ApiSponsoredMessage,
|
||||
ApiStickerSet,
|
||||
ApiThreadInfo,
|
||||
ApiTypeStory,
|
||||
ApiUser,
|
||||
} from '../../../api/types';
|
||||
import type { IAnchorPosition } from '../../../types';
|
||||
|
||||
import { getUserFullName, isUserId } from '../../../global/helpers';
|
||||
import { getUserFullName, groupStatetefulContent, isUserId } from '../../../global/helpers';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { disableScrolling } from '../../../util/scrollLock';
|
||||
import { REM } from '../../common/helpers/mediaDimensions';
|
||||
@ -49,6 +51,8 @@ type OwnProps = {
|
||||
anchor: IAnchorPosition;
|
||||
targetHref?: string;
|
||||
message: ApiMessage | ApiSponsoredMessage;
|
||||
poll?: ApiPoll;
|
||||
story?: ApiTypeStory;
|
||||
canSendNow?: boolean;
|
||||
enabledReactions?: ApiChatReactions;
|
||||
isWithPaidReaction?: boolean;
|
||||
@ -138,6 +142,8 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
defaultTagReactions,
|
||||
isOpen,
|
||||
message,
|
||||
poll,
|
||||
story,
|
||||
isPrivate,
|
||||
isCurrentUserPremium,
|
||||
enabledReactions,
|
||||
@ -282,6 +288,7 @@ const MessageContextMenu: FC<OwnProps> = ({
|
||||
? []
|
||||
: getMessageCopyOptions(
|
||||
message,
|
||||
groupStatetefulContent({ poll, story }),
|
||||
targetHref,
|
||||
canCopy,
|
||||
handleAfterCopy,
|
||||
|
||||
@ -39,9 +39,14 @@
|
||||
.Checkbox,
|
||||
.Radio {
|
||||
padding-left: 2.25rem;
|
||||
padding-bottom: 1rem;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0.75rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
|
||||
@ -7,7 +7,7 @@ import React, {
|
||||
useRef,
|
||||
useState,
|
||||
} from '../../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../../global';
|
||||
import { getActions, getGlobal } from '../../../global';
|
||||
|
||||
import type {
|
||||
ApiMessage, ApiPeer, ApiPoll, ApiPollAnswer,
|
||||
@ -15,6 +15,7 @@ import type {
|
||||
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||
import type { LangFn } from '../../../hooks/useOldLang';
|
||||
|
||||
import { selectPeer } from '../../../global/selectors';
|
||||
import { formatMediaDuration } from '../../../util/dates/dateFormat';
|
||||
import { getServerTime } from '../../../util/serverTime';
|
||||
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
|
||||
@ -39,10 +40,6 @@ type OwnProps = {
|
||||
onSendVote: (options: string[]) => void;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
recentVoterIds?: number[];
|
||||
};
|
||||
|
||||
const SOLUTION_CONTAINER_ID = '#middle-column-portals';
|
||||
const SOLUTION_DURATION = 5000;
|
||||
const TIMER_RADIUS = 6;
|
||||
@ -50,10 +47,9 @@ const TIMER_CIRCUMFERENCE = TIMER_RADIUS * 2 * Math.PI;
|
||||
const TIMER_UPDATE_INTERVAL = 1000;
|
||||
const NBSP = '\u00A0';
|
||||
|
||||
const Poll: FC<OwnProps & StateProps> = ({
|
||||
const Poll: FC<OwnProps> = ({
|
||||
message,
|
||||
poll,
|
||||
recentVoterIds,
|
||||
observeIntersectionForLoading,
|
||||
observeIntersectionForPlaying,
|
||||
onSendVote,
|
||||
@ -80,6 +76,7 @@ const Poll: FC<OwnProps & StateProps> = ({
|
||||
const canVote = !summary.closed && !hasVoted;
|
||||
const canViewResult = !canVote && summary.isPublic && Number(results.totalVoters) > 0;
|
||||
const isMultiple = canVote && summary.multipleChoice;
|
||||
const recentVoterIds = results.recentVoterIds;
|
||||
const maxVotersCount = voteResults ? Math.max(...voteResults.map((r) => r.votersCount)) : totalVoters;
|
||||
const correctResults = useMemo(() => {
|
||||
return voteResults?.filter((r) => r.isCorrect).map((r) => r.option) || [];
|
||||
@ -147,15 +144,11 @@ const Poll: FC<OwnProps & StateProps> = ({
|
||||
|
||||
const recentVoters = useMemo(() => {
|
||||
// No need for expensive global updates on chats or users, so we avoid them
|
||||
const chatsById = getGlobal().chats.byId;
|
||||
const usersById = getGlobal().users.byId;
|
||||
const global = getGlobal();
|
||||
return recentVoterIds ? recentVoterIds.reduce((result: ApiPeer[], id) => {
|
||||
const chat = chatsById[id];
|
||||
const user = usersById[id];
|
||||
if (user) {
|
||||
result.push(user);
|
||||
} else if (chat) {
|
||||
result.push(chat);
|
||||
const peer = selectPeer(global, id);
|
||||
if (peer) {
|
||||
result.push(peer);
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -372,17 +365,4 @@ function stopPropagation(e: React.MouseEvent<HTMLDivElement>) {
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { poll }) => {
|
||||
const { recentVoterIds } = poll.results;
|
||||
const { users: { byId: usersById } } = global;
|
||||
if (!recentVoterIds || recentVoterIds.length === 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
recentVoterIds,
|
||||
usersById,
|
||||
};
|
||||
},
|
||||
)(Poll));
|
||||
export default memo(Poll);
|
||||
|
||||
@ -44,8 +44,8 @@
|
||||
background: var(--color-error);
|
||||
}
|
||||
|
||||
.is-forwarded & > .icon {
|
||||
margin-left: 1px;
|
||||
.poll-option-icon {
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
&.animate {
|
||||
|
||||
@ -9,6 +9,8 @@ import type { ApiPollAnswer, ApiPollResult } from '../../../api/types';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { renderTextWithEntities } from '../../common/helpers/renderTextWithEntities';
|
||||
|
||||
import Icon from '../../common/icons/Icon';
|
||||
|
||||
import './PollOption.scss';
|
||||
|
||||
type OwnProps = {
|
||||
@ -59,7 +61,7 @@ const PollOption: FC<OwnProps> = ({
|
||||
shouldAnimate && 'animate',
|
||||
)}
|
||||
>
|
||||
<i className={buildClassName('icon', correctAnswer ? 'icon-check' : 'icon-close')} />
|
||||
<Icon name={correctAnswer ? 'check' : 'close'} className="poll-option-icon" />
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ApiMessage } from '../../../../api/types';
|
||||
import type { ApiMessage, ApiPoll } from '../../../../api/types';
|
||||
import type { IAlbum } from '../../../../types';
|
||||
|
||||
import { EMOJI_SIZES, MESSAGE_CONTENT_CLASS_NAME } from '../../../../config';
|
||||
@ -9,6 +9,7 @@ export function buildContentClassName(
|
||||
message: ApiMessage,
|
||||
album?: IAlbum,
|
||||
{
|
||||
poll,
|
||||
hasSubheader,
|
||||
isCustomShape,
|
||||
isLastInGroup,
|
||||
@ -23,6 +24,7 @@ export function buildContentClassName(
|
||||
peerColorClass,
|
||||
hasOutsideReactions,
|
||||
}: {
|
||||
poll?: ApiPoll;
|
||||
hasSubheader?: boolean;
|
||||
isCustomShape?: boolean | number;
|
||||
isLastInGroup?: boolean;
|
||||
@ -44,7 +46,7 @@ export function buildContentClassName(
|
||||
const content = getMessageContent(message);
|
||||
const {
|
||||
photo = paidMediaPhoto, video = paidMediaVideo,
|
||||
audio, voice, document, poll, webPage, contact, location, invoice, storyData,
|
||||
audio, voice, document, webPage, contact, location, invoice, storyData,
|
||||
giveaway, giveawayResults,
|
||||
} = content;
|
||||
const text = album?.hasMultipleCaptions ? undefined : getMessageContent(album?.captionMessage || message).text;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import type { ApiMessage } from '../../../../api/types';
|
||||
import type { ApiMessage, StatefulMediaContent } from '../../../../api/types';
|
||||
import type { IconName } from '../../../../types/icons';
|
||||
import { ApiMediaFormat } from '../../../../api/types';
|
||||
|
||||
@ -32,6 +32,7 @@ type ICopyOptions = {
|
||||
|
||||
export function getMessageCopyOptions(
|
||||
message: ApiMessage,
|
||||
statefulContent: StatefulMediaContent | undefined,
|
||||
href?: string,
|
||||
canCopy?: boolean,
|
||||
afterEffect?: () => void,
|
||||
@ -94,7 +95,12 @@ export function getMessageCopyOptions(
|
||||
const clipboardText = renderMessageText(
|
||||
{ message, shouldRenderAsHtml: true },
|
||||
);
|
||||
if (clipboardText) copyHtmlToClipboard(clipboardText.join(''), getMessageTextWithSpoilers(message)!);
|
||||
if (clipboardText) {
|
||||
copyHtmlToClipboard(
|
||||
clipboardText.join(''),
|
||||
getMessageTextWithSpoilers(message, statefulContent)!,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
afterEffect?.();
|
||||
|
||||
@ -2,10 +2,11 @@ import type { FC } from '../../lib/teact/teact';
|
||||
import React, { memo } from '../../lib/teact/teact';
|
||||
import { withGlobal } from '../../global';
|
||||
|
||||
import type { ApiChat, ApiMessage } from '../../api/types';
|
||||
import type { ApiChat, ApiMessage, ApiPoll } from '../../api/types';
|
||||
|
||||
import { getMessagePoll } from '../../global/helpers';
|
||||
import { selectChat, selectChatMessage, selectTabState } from '../../global/selectors';
|
||||
import {
|
||||
selectChat, selectChatMessage, selectPollFromMessage, selectTabState,
|
||||
} from '../../global/selectors';
|
||||
import { buildCollectionByKey } from '../../util/iteratees';
|
||||
import { renderTextWithEntities } from '../common/helpers/renderTextWithEntities';
|
||||
|
||||
@ -25,12 +26,14 @@ type OwnProps = {
|
||||
type StateProps = {
|
||||
chat?: ApiChat;
|
||||
message?: ApiMessage;
|
||||
poll?: ApiPoll;
|
||||
};
|
||||
|
||||
const PollResults: FC<OwnProps & StateProps> = ({
|
||||
isActive,
|
||||
chat,
|
||||
message,
|
||||
poll,
|
||||
onClose,
|
||||
}) => {
|
||||
const lang = useOldLang();
|
||||
@ -40,11 +43,11 @@ const PollResults: FC<OwnProps & StateProps> = ({
|
||||
onBack: onClose,
|
||||
});
|
||||
|
||||
if (!message || !chat) {
|
||||
if (!message || !poll || !chat) {
|
||||
return <Loading />;
|
||||
}
|
||||
|
||||
const { summary, results } = getMessagePoll(message)!;
|
||||
const { summary, results } = poll;
|
||||
if (!results.results) {
|
||||
return undefined;
|
||||
}
|
||||
@ -62,7 +65,7 @@ const PollResults: FC<OwnProps & StateProps> = ({
|
||||
<div className="poll-results-list custom-scroll">
|
||||
{summary.answers.map((answer) => (
|
||||
<PollAnswerResults
|
||||
key={`${message.id}-${answer.option}`}
|
||||
key={`${poll.id}-${answer.option}`}
|
||||
chat={chat}
|
||||
message={message}
|
||||
answer={answer}
|
||||
@ -87,10 +90,12 @@ export default memo(withGlobal(
|
||||
|
||||
const chat = selectChat(global, chatId);
|
||||
const message = selectChatMessage(global, chatId, messageId);
|
||||
const poll = message && selectPollFromMessage(global, message);
|
||||
|
||||
return {
|
||||
chat,
|
||||
message,
|
||||
poll,
|
||||
};
|
||||
},
|
||||
)(PollResults));
|
||||
|
||||
@ -120,6 +120,7 @@ import {
|
||||
selectOutlyingListByMessageId,
|
||||
selectPeerStory,
|
||||
selectPinnedIds,
|
||||
selectPollFromMessage,
|
||||
selectRealLastReadId,
|
||||
selectReplyCanBeSentToChat,
|
||||
selectScheduledMessage,
|
||||
@ -977,7 +978,7 @@ addActionHandler('clearWebPagePreview', (global, actions, payload): ActionReturn
|
||||
});
|
||||
|
||||
addActionHandler('sendPollVote', (global, actions, payload): ActionReturnType => {
|
||||
const { chatId, messageId, options } = payload!;
|
||||
const { chatId, messageId, options } = payload;
|
||||
const chat = selectChat(global, chatId);
|
||||
|
||||
if (chat) {
|
||||
@ -986,7 +987,7 @@ addActionHandler('sendPollVote', (global, actions, payload): ActionReturnType =>
|
||||
});
|
||||
|
||||
addActionHandler('cancelPollVote', (global, actions, payload): ActionReturnType => {
|
||||
const { chatId, messageId } = payload!;
|
||||
const { chatId, messageId } = payload;
|
||||
const chat = selectChat(global, chatId);
|
||||
|
||||
if (chat) {
|
||||
@ -997,7 +998,8 @@ addActionHandler('cancelPollVote', (global, actions, payload): ActionReturnType
|
||||
addActionHandler('closePoll', (global, actions, payload): ActionReturnType => {
|
||||
const { chatId, messageId } = payload;
|
||||
const chat = selectChat(global, chatId);
|
||||
const poll = selectChatMessage(global, chatId, messageId)?.content.poll;
|
||||
const message = selectChatMessage(global, chatId, messageId);
|
||||
const poll = message && selectPollFromMessage(global, message);
|
||||
if (chat && poll) {
|
||||
void callApi('closePoll', { chat, messageId, poll });
|
||||
}
|
||||
@ -1006,7 +1008,7 @@ addActionHandler('closePoll', (global, actions, payload): ActionReturnType => {
|
||||
addActionHandler('loadPollOptionResults', async (global, actions, payload): Promise<void> => {
|
||||
const {
|
||||
chat, messageId, option, offset, limit, shouldResetVoters, tabId = getCurrentTabId(),
|
||||
} = payload!;
|
||||
} = payload;
|
||||
|
||||
const result = await callApi('loadPollOptionResults', {
|
||||
chat, messageId, option, offset, limit,
|
||||
@ -1103,7 +1105,7 @@ addActionHandler('forwardMessages', (global, actions, payload): ActionReturnType
|
||||
serviceMessages
|
||||
.forEach((message) => {
|
||||
const { text, entities } = message.content.text || {};
|
||||
const { sticker, poll } = message.content;
|
||||
const { sticker } = message.content;
|
||||
|
||||
const replyInfo = selectMessageReplyInfo(global, toChat.id, toThreadId);
|
||||
|
||||
@ -1113,7 +1115,6 @@ addActionHandler('forwardMessages', (global, actions, payload): ActionReturnType
|
||||
text,
|
||||
entities,
|
||||
sticker,
|
||||
poll,
|
||||
isSilent,
|
||||
scheduledAt,
|
||||
sendAs,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type {
|
||||
ApiChat, ApiMediaExtendedPreview, ApiMessage, ApiPollResult, ApiReactions,
|
||||
ApiChat, ApiMediaExtendedPreview, ApiMessage, ApiReactions,
|
||||
MediaContent,
|
||||
} from '../../../api/types';
|
||||
import type { ThreadId } from '../../../types';
|
||||
@ -43,6 +43,8 @@ import {
|
||||
updateChatMessage,
|
||||
updateListedIds,
|
||||
updateMessageTranslations,
|
||||
updatePoll,
|
||||
updatePollVote,
|
||||
updateQuickReplies,
|
||||
updateQuickReplyMessage,
|
||||
updateScheduledMessage,
|
||||
@ -57,7 +59,6 @@ import {
|
||||
selectChat,
|
||||
selectChatLastMessageId,
|
||||
selectChatMessage,
|
||||
selectChatMessageByPollId,
|
||||
selectChatMessages,
|
||||
selectChatScheduledMessages,
|
||||
selectCommonBoxChatId,
|
||||
@ -74,7 +75,6 @@ import {
|
||||
selectSavedDialogIdFromMessage,
|
||||
selectScheduledIds,
|
||||
selectScheduledMessage,
|
||||
selectSendAs,
|
||||
selectTabState,
|
||||
selectThreadByMessage,
|
||||
selectThreadIdFromMessage,
|
||||
@ -90,7 +90,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
switch (update['@type']) {
|
||||
case 'newMessage': {
|
||||
const {
|
||||
chatId, id, message, shouldForceReply, wasDrafted,
|
||||
chatId, id, message, shouldForceReply, wasDrafted, poll,
|
||||
} = update;
|
||||
global = updateWithLocalMedia(global, chatId, id, message);
|
||||
global = updateListedAndViewportIds(global, actions, message as ApiMessage);
|
||||
@ -154,6 +154,10 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
}
|
||||
});
|
||||
|
||||
if (poll) {
|
||||
global = updatePoll(global, poll.id, poll);
|
||||
}
|
||||
|
||||
setGlobal(global);
|
||||
|
||||
// Reload dialogs if chat is not present in the list
|
||||
@ -208,7 +212,9 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
}
|
||||
|
||||
case 'newScheduledMessage': {
|
||||
const { chatId, id, message } = update;
|
||||
const {
|
||||
chatId, id, message, poll,
|
||||
} = update;
|
||||
|
||||
global = updateWithLocalMedia(global, chatId, id, message, true);
|
||||
|
||||
@ -221,6 +227,10 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
global = replaceThreadParam(global, chatId, threadId, 'scheduledIds', unique([...threadScheduledIds, id]));
|
||||
}
|
||||
|
||||
if (poll) {
|
||||
global = updatePoll(global, poll.id, poll);
|
||||
}
|
||||
|
||||
setGlobal(global);
|
||||
|
||||
break;
|
||||
@ -560,97 +570,15 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
case 'updateMessagePoll': {
|
||||
const { pollId, pollUpdate } = update;
|
||||
|
||||
const message = selectChatMessageByPollId(global, pollId);
|
||||
global = updatePoll(global, pollId, pollUpdate);
|
||||
|
||||
if (message?.content.poll) {
|
||||
const oldResults = message.content.poll.results;
|
||||
let newResults = oldResults;
|
||||
if (pollUpdate.results?.results) {
|
||||
if (!oldResults.results || !pollUpdate.results.isMin) {
|
||||
newResults = pollUpdate.results;
|
||||
} else if (oldResults.results) {
|
||||
newResults = {
|
||||
...pollUpdate.results,
|
||||
results: pollUpdate.results.results.map((result) => ({
|
||||
...result,
|
||||
isChosen: oldResults.results!.find((r) => r.option === result.option)?.isChosen,
|
||||
})),
|
||||
isMin: undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
const updatedPoll = { ...message.content.poll, ...pollUpdate, results: newResults };
|
||||
|
||||
global = updateChatMessage(
|
||||
global,
|
||||
message.chatId,
|
||||
message.id,
|
||||
{
|
||||
content: {
|
||||
...message.content,
|
||||
poll: updatedPoll,
|
||||
},
|
||||
},
|
||||
);
|
||||
setGlobal(global);
|
||||
}
|
||||
setGlobal(global);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'updateMessagePollVote': {
|
||||
const { pollId, peerId, options } = update;
|
||||
const message = selectChatMessageByPollId(global, pollId);
|
||||
if (!message || !message.content.poll || !message.content.poll.results) {
|
||||
break;
|
||||
}
|
||||
|
||||
const { poll } = message.content;
|
||||
|
||||
const currentSendAs = selectSendAs(global, message.chatId);
|
||||
|
||||
const { recentVoterIds, totalVoters, results } = poll.results;
|
||||
const newRecentVoterIds = recentVoterIds ? [...recentVoterIds] : [];
|
||||
const newTotalVoters = totalVoters ? totalVoters + 1 : 1;
|
||||
const newResults = results ? [...results] : [];
|
||||
|
||||
newRecentVoterIds.push(peerId);
|
||||
|
||||
options.forEach((option) => {
|
||||
const targetOptionIndex = newResults.findIndex((result) => result.option === option);
|
||||
const targetOption = newResults[targetOptionIndex];
|
||||
const updatedOption: ApiPollResult = targetOption ? { ...targetOption } : { option, votersCount: 0 };
|
||||
|
||||
updatedOption.votersCount += 1;
|
||||
if (currentSendAs?.id === peerId || peerId === global.currentUserId) {
|
||||
updatedOption.isChosen = true;
|
||||
}
|
||||
|
||||
if (targetOptionIndex) {
|
||||
newResults[targetOptionIndex] = updatedOption;
|
||||
} else {
|
||||
newResults.push(updatedOption);
|
||||
}
|
||||
});
|
||||
|
||||
global = updateChatMessage(
|
||||
global,
|
||||
message.chatId,
|
||||
message.id,
|
||||
{
|
||||
content: {
|
||||
...message.content,
|
||||
poll: {
|
||||
...poll,
|
||||
results: {
|
||||
...poll.results,
|
||||
recentVoterIds: newRecentVoterIds,
|
||||
totalVoters: newTotalVoters,
|
||||
results: newResults,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
global = updatePollVote(global, pollId, peerId, options);
|
||||
setGlobal(global);
|
||||
|
||||
break;
|
||||
|
||||
@ -14,6 +14,7 @@ import {
|
||||
updateLastReadStoryForPeer,
|
||||
updatePeerStory,
|
||||
updatePeersWithStories,
|
||||
updatePoll,
|
||||
updateStealthMode,
|
||||
updateThreadInfos,
|
||||
} from '../../reducers';
|
||||
@ -22,10 +23,17 @@ import { selectPeerStories, selectPeerStory } from '../../selectors';
|
||||
addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
switch (update['@type']) {
|
||||
case 'updateEntities': {
|
||||
const { users, chats, threadInfos } = update;
|
||||
const {
|
||||
users, chats, threadInfos, polls,
|
||||
} = update;
|
||||
if (users) global = addUsers(global, users);
|
||||
if (chats) global = addChats(global, chats);
|
||||
if (threadInfos) global = updateThreadInfos(global, threadInfos);
|
||||
if (polls) {
|
||||
polls.forEach((poll) => {
|
||||
global = updatePoll(global, poll.id, poll);
|
||||
});
|
||||
}
|
||||
setGlobal(global);
|
||||
break;
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ import {
|
||||
getMediaFormat,
|
||||
getMediaHash,
|
||||
getMessageDownloadableMedia,
|
||||
getMessageStatefulContent,
|
||||
getSenderTitle,
|
||||
isChatChannel,
|
||||
isJoinedChannelMessage,
|
||||
@ -985,12 +986,13 @@ function copyTextForMessages(global: GlobalState, chatId: string, messageIds: nu
|
||||
messages.forEach((message) => {
|
||||
const sender = isChatChannel(chat) ? chat : selectSender(global, message);
|
||||
const senderTitle = `> ${sender ? getSenderTitle(lang, sender) : message.forwardInfo?.hiddenUserName || ''}:`;
|
||||
const statefulContent = getMessageStatefulContent(global, message);
|
||||
|
||||
resultHtml.push(senderTitle);
|
||||
resultHtml.push(`${renderMessageSummaryHtml(lang, message)}\n`);
|
||||
|
||||
resultText.push(senderTitle);
|
||||
resultText.push(`${getMessageSummaryText(lang, message, false, 0, true)}\n`);
|
||||
resultText.push(`${getMessageSummaryText(lang, message, statefulContent, false, 0, true)}\n`);
|
||||
});
|
||||
|
||||
copyHtmlToClipboard(resultHtml.join('\n'), resultText.join('\n'));
|
||||
|
||||
@ -257,6 +257,10 @@ function unsafeMigrateCache(cached: GlobalState, initialState: GlobalState) {
|
||||
cached.chats.topicsInfoById = initialState.chats.topicsInfoById;
|
||||
}
|
||||
|
||||
if (!cached.messages.pollById) {
|
||||
cached.messages.pollById = initialState.messages.pollById;
|
||||
}
|
||||
|
||||
if (!cached.stickers.starGifts) {
|
||||
cached.stickers.starGifts = initialState.stickers.starGifts;
|
||||
cached.users.giftsById = initialState.users.giftsById;
|
||||
@ -495,6 +499,8 @@ function reduceMessages<T extends GlobalState>(global: T): GlobalState['messages
|
||||
return acc;
|
||||
}, {} as Record<string, Set<ThreadId>>);
|
||||
|
||||
const pollIdsToSave: string[] = [];
|
||||
|
||||
chatIdsToSave.forEach((chatId) => {
|
||||
const current = global.messages.byChatId[chatId];
|
||||
if (!current) {
|
||||
@ -537,6 +543,11 @@ function reduceMessages<T extends GlobalState>(global: T): GlobalState['messages
|
||||
let cleanedMessage = omitLocalMedia(message);
|
||||
cleanedMessage = omitLocalPaidReactions(cleanedMessage);
|
||||
acc[message.id] = cleanedMessage;
|
||||
|
||||
if (message.content.pollId) {
|
||||
pollIdsToSave.push(message.content.pollId);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {} as Record<number, ApiMessage>);
|
||||
|
||||
@ -548,6 +559,7 @@ function reduceMessages<T extends GlobalState>(global: T): GlobalState['messages
|
||||
|
||||
return {
|
||||
byChatId,
|
||||
pollById: pickTruthy(global.messages.pollById, pollIdsToSave),
|
||||
sponsoredByChatId: {},
|
||||
};
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ export function hasMessageMedia(message: MediaContainer) {
|
||||
|| getMessageDocument(message)
|
||||
|| getMessageSticker(message)
|
||||
|| getMessageContact(message)
|
||||
|| getMessagePoll(message)
|
||||
|| getMessagePollId(message)
|
||||
|| getMessageAction(message)
|
||||
|| getMessageAudio(message)
|
||||
|| getMessageVoice(message)
|
||||
@ -128,8 +128,8 @@ export function getMessageContact(message: MediaContainer) {
|
||||
return message.content.contact;
|
||||
}
|
||||
|
||||
export function getMessagePoll(message: MediaContainer) {
|
||||
return message.content.poll;
|
||||
export function getMessagePollId(message: MediaContainer) {
|
||||
return message.content.pollId;
|
||||
}
|
||||
|
||||
export function getMessageInvoice(message: MediaContainer) {
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
import type { TeactNode } from '../../lib/teact/teact';
|
||||
|
||||
import type { ApiMediaExtendedPreview, ApiMessage, MediaContent } from '../../api/types';
|
||||
import type {
|
||||
ApiMediaExtendedPreview, ApiMessage, MediaContent, StatefulMediaContent,
|
||||
} from '../../api/types';
|
||||
import type { LangFn } from '../../hooks/useOldLang';
|
||||
import { ApiMessageEntityTypes } from '../../api/types';
|
||||
|
||||
import { CONTENT_NOT_SUPPORTED } from '../../config';
|
||||
import trimText from '../../util/trimText';
|
||||
import { renderTextWithEntities } from '../../components/common/helpers/renderTextWithEntities';
|
||||
import { getGlobal } from '../index';
|
||||
import {
|
||||
getExpiredMessageContentDescription, getMessageText, getMessageTranscription, isExpiredMessageContent,
|
||||
} from './messages';
|
||||
import { getUserFirstOrLastName } from './users';
|
||||
|
||||
const SPOILER_CHARS = ['⠺', '⠵', '⠞', '⠟'];
|
||||
export const TRUNCATED_SUMMARY_LENGTH = 80;
|
||||
@ -19,22 +19,23 @@ export const TRUNCATED_SUMMARY_LENGTH = 80;
|
||||
export function getMessageSummaryText(
|
||||
lang: LangFn,
|
||||
message: ApiMessage,
|
||||
statefulContent: StatefulMediaContent | undefined,
|
||||
noEmoji = false,
|
||||
truncateLength = TRUNCATED_SUMMARY_LENGTH,
|
||||
isExtended = false,
|
||||
) {
|
||||
const emoji = !noEmoji && getMessageSummaryEmoji(message);
|
||||
const emojiWithSpace = emoji ? `${emoji} ` : '';
|
||||
const text = trimText(getMessageTextWithSpoilers(message), truncateLength);
|
||||
const description = getMessageSummaryDescription(lang, message, text, isExtended);
|
||||
const text = trimText(getMessageTextWithSpoilers(message, statefulContent), truncateLength);
|
||||
const description = getMessageSummaryDescription(lang, message, statefulContent, text, isExtended);
|
||||
|
||||
return `${emojiWithSpace}${description}`;
|
||||
}
|
||||
|
||||
export function getMessageTextWithSpoilers(message: ApiMessage) {
|
||||
export function getMessageTextWithSpoilers(message: ApiMessage, statefulContent: StatefulMediaContent | undefined) {
|
||||
const transcription = getMessageTranscription(message);
|
||||
|
||||
const textWithoutTranscription = getMessageText(message);
|
||||
const textWithoutTranscription = getMessageText(statefulContent?.story || message);
|
||||
if (!textWithoutTranscription) {
|
||||
return transcription;
|
||||
}
|
||||
@ -69,7 +70,7 @@ export function getMessageSummaryEmoji(message: ApiMessage) {
|
||||
voice,
|
||||
document,
|
||||
sticker,
|
||||
poll,
|
||||
pollId,
|
||||
paidMedia,
|
||||
} = message.content;
|
||||
|
||||
@ -97,27 +98,31 @@ export function getMessageSummaryEmoji(message: ApiMessage) {
|
||||
return '📎';
|
||||
}
|
||||
|
||||
if (poll) {
|
||||
if (pollId) {
|
||||
return '📊';
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function getMediaContentTypeDescription(lang: LangFn, content: MediaContent) {
|
||||
return getSummaryDescription(lang, content);
|
||||
export function getMediaContentTypeDescription(
|
||||
lang: LangFn, content: MediaContent, statefulContent: StatefulMediaContent | undefined,
|
||||
) {
|
||||
return getSummaryDescription(lang, content, statefulContent);
|
||||
}
|
||||
export function getMessageSummaryDescription(
|
||||
lang: LangFn,
|
||||
message: ApiMessage,
|
||||
statefulContent: StatefulMediaContent | undefined,
|
||||
truncatedText?: string | TeactNode,
|
||||
isExtended = false,
|
||||
) {
|
||||
return getSummaryDescription(lang, message.content, message, truncatedText, isExtended);
|
||||
return getSummaryDescription(lang, message.content, statefulContent, message, truncatedText, isExtended);
|
||||
}
|
||||
function getSummaryDescription(
|
||||
lang: LangFn,
|
||||
mediaContent: MediaContent,
|
||||
statefulContent: StatefulMediaContent | undefined,
|
||||
message?: ApiMessage,
|
||||
truncatedText?: string | TeactNode,
|
||||
isExtended = false,
|
||||
@ -131,7 +136,6 @@ function getSummaryDescription(
|
||||
document,
|
||||
sticker,
|
||||
contact,
|
||||
poll,
|
||||
invoice,
|
||||
location,
|
||||
game,
|
||||
@ -140,6 +144,7 @@ function getSummaryDescription(
|
||||
giveawayResults,
|
||||
paidMedia,
|
||||
} = mediaContent;
|
||||
const { poll } = statefulContent || {};
|
||||
|
||||
let hasUsedTruncatedText = false;
|
||||
let summary: string | TeactNode | undefined;
|
||||
@ -231,16 +236,7 @@ function getSummaryDescription(
|
||||
}
|
||||
|
||||
if (storyData) {
|
||||
if (message && storyData.isMention) {
|
||||
// eslint-disable-next-line eslint-multitab-tt/no-immediate-global
|
||||
const global = getGlobal();
|
||||
const firstName = getUserFirstOrLastName(global.users.byId[message.chatId]);
|
||||
summary = message.isOutgoing
|
||||
? lang('Chat.Service.StoryMentioned.You', firstName)
|
||||
: lang('Chat.Service.StoryMentioned', firstName);
|
||||
} else {
|
||||
summary = message ? lang('ForwardedStory') : lang('Chat.ReplyStory');
|
||||
}
|
||||
summary = truncatedText || (message ? lang('ForwardedStory') : lang('Chat.ReplyStory'));
|
||||
}
|
||||
|
||||
if (isExpiredMessageContent(mediaContent)) {
|
||||
|
||||
@ -3,13 +3,15 @@ import type {
|
||||
ApiMessage,
|
||||
ApiMessageEntityTextUrl,
|
||||
ApiPeer,
|
||||
ApiSponsoredMessage,
|
||||
ApiStory,
|
||||
MediaContainer,
|
||||
ApiTypeStory,
|
||||
} from '../../api/types';
|
||||
import type { MediaContent } from '../../api/types/messages';
|
||||
import type {
|
||||
ApiPoll, MediaContainer, MediaContent, StatefulMediaContent,
|
||||
} from '../../api/types/messages';
|
||||
import type { LangFn } from '../../hooks/useOldLang';
|
||||
import type { ThreadId } from '../../types';
|
||||
import type { GlobalState } from '../types';
|
||||
import { ApiMessageEntityTypes, MAIN_THREAD_ID } from '../../api/types';
|
||||
|
||||
import {
|
||||
@ -52,26 +54,48 @@ export function getMessageTranscription(message: ApiMessage) {
|
||||
return transcriptionId && global.transcriptions[transcriptionId]?.text;
|
||||
}
|
||||
|
||||
export function hasMessageText(message: ApiMessage | ApiStory | ApiSponsoredMessage | MediaContainer) {
|
||||
export function hasMessageText(message: MediaContainer) {
|
||||
const {
|
||||
text, sticker, photo, video, audio, voice, document, poll, webPage, contact, invoice, location,
|
||||
text, sticker, photo, video, audio, voice, document, pollId, webPage, contact, invoice, location,
|
||||
game, action, storyData, giveaway, giveawayResults, isExpiredVoice, paidMedia,
|
||||
} = message.content;
|
||||
|
||||
return Boolean(text) || !(
|
||||
sticker || photo || video || audio || voice || document || contact || poll || webPage || invoice || location
|
||||
sticker || photo || video || audio || voice || document || contact || pollId || webPage || invoice || location
|
||||
|| game || action?.phoneCall || storyData || giveaway || giveawayResults || isExpiredVoice || paidMedia
|
||||
);
|
||||
}
|
||||
|
||||
export function getMessageText(message: ApiMessage | ApiStory | ApiSponsoredMessage | MediaContainer) {
|
||||
export function getMessageStatefulContent(global: GlobalState, message: ApiMessage): StatefulMediaContent {
|
||||
const poll = message.content.pollId ? global.messages.pollById[message.content.pollId] : undefined;
|
||||
|
||||
const { peerId: storyPeerId, id: storyId } = message.content.storyData || {};
|
||||
const story = storyId && storyPeerId ? global.stories.byPeerId[storyPeerId]?.byId[storyId] : undefined;
|
||||
|
||||
return groupStatetefulContent({ poll, story });
|
||||
}
|
||||
|
||||
export function groupStatetefulContent({
|
||||
poll,
|
||||
story,
|
||||
} : {
|
||||
poll?: ApiPoll;
|
||||
story?: ApiTypeStory;
|
||||
}) {
|
||||
return {
|
||||
poll,
|
||||
story: story && 'content' in story ? story : undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function getMessageText(message: MediaContainer) {
|
||||
return hasMessageText(message) ? message.content.text?.text || CONTENT_NOT_SUPPORTED : undefined;
|
||||
}
|
||||
|
||||
export function getMessageCustomShape(message: ApiMessage): boolean {
|
||||
const {
|
||||
text, sticker, photo, video, audio, voice,
|
||||
document, poll, webPage, contact, action,
|
||||
document, pollId, webPage, contact, action,
|
||||
game, invoice, location, storyData,
|
||||
} = message.content;
|
||||
|
||||
@ -79,7 +103,7 @@ export function getMessageCustomShape(message: ApiMessage): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!text || photo || video || audio || voice || document || poll || webPage || contact || action || game || invoice
|
||||
if (!text || photo || video || audio || voice || document || pollId || webPage || contact || action || game || invoice
|
||||
|| location || storyData) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -2,18 +2,24 @@ import type { ApiMessage } from '../../api/types';
|
||||
import type { LangFn } from '../../hooks/useOldLang';
|
||||
|
||||
import { renderMessageText } from '../../components/common/helpers/renderMessageText';
|
||||
import { getGlobal } from '..';
|
||||
import { getMessageStatefulContent } from './messages';
|
||||
import { getMessageSummaryDescription, getMessageSummaryEmoji } from './messageSummary';
|
||||
|
||||
export function renderMessageSummaryHtml(
|
||||
lang: LangFn,
|
||||
message: ApiMessage,
|
||||
) {
|
||||
const global = getGlobal();
|
||||
const emoji = getMessageSummaryEmoji(message);
|
||||
const emojiWithSpace = emoji ? `${emoji} ` : '';
|
||||
const text = renderMessageText(
|
||||
{ message, shouldRenderAsHtml: true },
|
||||
)?.join('');
|
||||
const description = getMessageSummaryDescription(lang, message, text, true);
|
||||
|
||||
const statefulContent = getMessageStatefulContent(global, message);
|
||||
|
||||
const description = getMessageSummaryDescription(lang, message, statefulContent, text, true);
|
||||
|
||||
return `${emojiWithSpace}${description}`;
|
||||
}
|
||||
|
||||
@ -122,6 +122,7 @@ export const INITIAL_GLOBAL_STATE: GlobalState = {
|
||||
messages: {
|
||||
byChatId: {},
|
||||
sponsoredByChatId: {},
|
||||
pollById: {},
|
||||
},
|
||||
|
||||
stories: {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import type {
|
||||
ApiMessage, ApiQuickReply, ApiSponsoredMessage, ApiThreadInfo,
|
||||
ApiMessage, ApiPoll, ApiPollResult, ApiQuickReply, ApiSponsoredMessage, ApiThreadInfo,
|
||||
} from '../../api/types';
|
||||
import type { FocusDirection, ScrollTargetPosition, ThreadId } from '../../types';
|
||||
import type {
|
||||
@ -29,6 +29,7 @@ import {
|
||||
selectMessageIdsByGroupId,
|
||||
selectOutlyingLists,
|
||||
selectPinnedIds,
|
||||
selectPoll,
|
||||
selectQuickReplyMessage,
|
||||
selectScheduledIds,
|
||||
selectScheduledMessage,
|
||||
@ -934,3 +935,94 @@ export function deleteQuickReply<T extends GlobalState>(
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function updatePoll<T extends GlobalState>(
|
||||
global: T,
|
||||
pollId: string,
|
||||
pollUpdate: Partial<ApiPoll>,
|
||||
) {
|
||||
const poll = selectPoll(global, pollId);
|
||||
|
||||
const oldResults = poll?.results;
|
||||
let newResults = oldResults || pollUpdate.results;
|
||||
if (poll && pollUpdate.results?.results) {
|
||||
if (!poll.results || !pollUpdate.results.isMin) {
|
||||
newResults = pollUpdate.results;
|
||||
} else if (oldResults.results) {
|
||||
// Update voters counts, but keep local `isChosen` values
|
||||
newResults = {
|
||||
...pollUpdate.results,
|
||||
results: pollUpdate.results.results.map((result) => ({
|
||||
...result,
|
||||
isChosen: oldResults.results!.find((r) => r.option === result.option)?.isChosen,
|
||||
})),
|
||||
isMin: undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const updatedPoll = {
|
||||
...poll,
|
||||
...pollUpdate,
|
||||
results: newResults,
|
||||
} satisfies ApiPoll;
|
||||
if (!updatedPoll.id) {
|
||||
return global;
|
||||
}
|
||||
|
||||
return {
|
||||
...global,
|
||||
messages: {
|
||||
...global.messages,
|
||||
pollById: {
|
||||
...global.messages.pollById,
|
||||
[pollId]: updatedPoll,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function updatePollVote<T extends GlobalState>(
|
||||
global: T,
|
||||
pollId: string,
|
||||
peerId: string,
|
||||
options: string[],
|
||||
) {
|
||||
const poll = selectPoll(global, pollId);
|
||||
if (!poll) {
|
||||
return global;
|
||||
}
|
||||
|
||||
const { recentVoterIds, totalVoters, results } = poll.results;
|
||||
const newRecentVoterIds = recentVoterIds ? [...recentVoterIds] : [];
|
||||
const newTotalVoters = totalVoters ? totalVoters + 1 : 1;
|
||||
const newResults = results ? [...results] : [];
|
||||
|
||||
newRecentVoterIds.push(peerId);
|
||||
|
||||
options.forEach((option) => {
|
||||
const targetOptionIndex = newResults.findIndex((result) => result.option === option);
|
||||
const targetOption = newResults[targetOptionIndex];
|
||||
const updatedOption: ApiPollResult = targetOption ? { ...targetOption } : { option, votersCount: 0 };
|
||||
|
||||
updatedOption.votersCount += 1;
|
||||
if (peerId === global.currentUserId) {
|
||||
updatedOption.isChosen = true;
|
||||
}
|
||||
|
||||
if (targetOptionIndex) {
|
||||
newResults[targetOptionIndex] = updatedOption;
|
||||
} else {
|
||||
newResults.push(updatedOption);
|
||||
}
|
||||
});
|
||||
|
||||
return updatePoll(global, pollId, {
|
||||
results: {
|
||||
...poll.results,
|
||||
recentVoterIds: newRecentVoterIds,
|
||||
totalVoters: newTotalVoters,
|
||||
results: newResults,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@ -357,23 +357,6 @@ export function selectEditingMessage<T extends GlobalState>(
|
||||
}
|
||||
}
|
||||
|
||||
export function selectChatMessageByPollId<T extends GlobalState>(global: T, pollId: string) {
|
||||
let messageWithPoll: ApiMessage | undefined;
|
||||
|
||||
// eslint-disable-next-line no-restricted-syntax
|
||||
for (const chatMessages of Object.values(global.messages.byChatId)) {
|
||||
const { byId } = chatMessages;
|
||||
messageWithPoll = Object.values(byId).find((message) => {
|
||||
return message.content.poll && message.content.poll.id === pollId;
|
||||
});
|
||||
if (messageWithPoll) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return messageWithPoll;
|
||||
}
|
||||
|
||||
export function selectFocusedMessageId<T extends GlobalState>(
|
||||
global: T, chatId: string, ...[tabId = getCurrentTabId()]: TabArgs<T>
|
||||
) {
|
||||
@ -484,6 +467,15 @@ export function selectForwardedSender<T extends GlobalState>(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function selectPoll<T extends GlobalState>(global: T, pollId: string) {
|
||||
return global.messages.pollById[pollId];
|
||||
}
|
||||
|
||||
export function selectPollFromMessage<T extends GlobalState>(global: T, message: ApiMessage) {
|
||||
if (!message.content.pollId) return undefined;
|
||||
return selectPoll(global, message.content.pollId);
|
||||
}
|
||||
|
||||
export function selectTopicFromMessage<T extends GlobalState>(global: T, message: ApiMessage) {
|
||||
const { chatId } = message;
|
||||
const chat = selectChat(global, chatId);
|
||||
@ -647,7 +639,7 @@ export function selectAllowedMessageActionsSlow<T extends GlobalState>(
|
||||
canEditMessagesIndefinitely
|
||||
|| getServerTime() - message.date < MESSAGE_EDIT_ALLOWED_TIME
|
||||
) && !(
|
||||
content.sticker || content.contact || content.poll || content.action
|
||||
content.sticker || content.contact || content.pollId || content.action
|
||||
|| (content.video?.isRound) || content.location || content.invoice || content.giveaway || content.giveawayResults
|
||||
|| isDocumentSticker
|
||||
)
|
||||
@ -724,7 +716,7 @@ export function selectAllowedMessageActionsSlow<T extends GlobalState>(
|
||||
|
||||
const canSaveGif = message.content.video?.isGif;
|
||||
|
||||
const poll = content.poll;
|
||||
const poll = content.pollId ? selectPoll(global, content.pollId) : undefined;
|
||||
const canRevote = !poll?.summary.closed && !poll?.summary.quiz && poll?.results.results?.some((r) => r.isChosen);
|
||||
const canClosePoll = hasMessageEditRight && poll && !poll.summary.closed && !isForwarded;
|
||||
|
||||
|
||||
@ -50,6 +50,7 @@ import type {
|
||||
ApiPeerStories,
|
||||
ApiPhoneCall,
|
||||
ApiPhoto,
|
||||
ApiPoll,
|
||||
ApiPostStatistics,
|
||||
ApiPremiumGiftCodeOption,
|
||||
ApiPremiumPromo,
|
||||
@ -1048,6 +1049,7 @@ export type GlobalState = {
|
||||
threadsById: Record<ThreadId, Thread>;
|
||||
}>;
|
||||
sponsoredByChatId: Record<string, ApiSponsoredMessage>;
|
||||
pollById: Record<string, ApiPoll>;
|
||||
};
|
||||
|
||||
stories: {
|
||||
|
||||
@ -13,6 +13,7 @@ import {
|
||||
getMessageAction,
|
||||
getMessageRecentReaction,
|
||||
getMessageSenderName,
|
||||
getMessageStatefulContent,
|
||||
getPrivateChatUserId,
|
||||
getUserFullName,
|
||||
isActionMessage,
|
||||
@ -366,7 +367,8 @@ function getNotificationContent(chat: ApiChat, message: ApiMessage, reaction?: A
|
||||
} else {
|
||||
// TODO[forums] Support ApiChat
|
||||
const senderName = getMessageSenderName(oldTranslate, chat.id, isChat ? messageSenderChat : messageSenderUser);
|
||||
let summary = getMessageSummaryText(oldTranslate, message, hasReaction, 60);
|
||||
const statefulContent = getMessageStatefulContent(global, message);
|
||||
let summary = getMessageSummaryText(oldTranslate, message, statefulContent, hasReaction, 60);
|
||||
|
||||
if (hasReaction) {
|
||||
const emoji = getReactionEmoji(reaction);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user