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