Message: Reply re-design and blockquotes (#3926)
This commit is contained in:
parent
f894010a70
commit
330bc42c98
@ -1,13 +1,4 @@
|
|||||||
@use "sass:map";
|
@use "sass:map";
|
||||||
${{ name }}-font: "{{ name }}";
|
|
||||||
|
|
||||||
@font-face {
|
|
||||||
font-family: ${{ name }}-font;
|
|
||||||
src: {{{ fontSrc }}};
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
font-display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-char::before {
|
.icon-char::before {
|
||||||
font-family: Roboto, "Helvetica Neue", sans-serif;
|
font-family: Roboto, "Helvetica Neue", sans-serif;
|
||||||
@ -17,9 +8,7 @@ ${{ name }}-font: "{{ name }}";
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
{{# if selector }}{{ selector }}::before {
|
@mixin icon {
|
||||||
{{ else }}{{ tag }}.{{prefix}} {
|
|
||||||
{{/ if }}
|
|
||||||
/* use !important to prevent issues with browser extensions that change fonts */
|
/* use !important to prevent issues with browser extensions that change fonts */
|
||||||
/* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
|
/* stylelint-disable-next-line font-family-no-missing-generic-family-keyword */
|
||||||
font-family: "{{ name }}" !important;
|
font-family: "{{ name }}" !important;
|
||||||
@ -35,6 +24,12 @@ ${{ name }}-font: "{{ name }}";
|
|||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{{# if selector }}{{ selector }}::before {
|
||||||
|
{{ else }}{{ tag }}.{{prefix}} {
|
||||||
|
{{/ if }}
|
||||||
|
@include icon;
|
||||||
|
}
|
||||||
|
|
||||||
${{ name }}-map: (
|
${{ name }}-map: (
|
||||||
{{# each codepoints }}
|
{{# each codepoints }}
|
||||||
"{{ @key }}": "\\{{ codepoint this }}",
|
"{{ @key }}": "\\{{ codepoint this }}",
|
||||||
|
|||||||
@ -52,6 +52,8 @@ export interface GramJsAppConfig extends LimitsConfig {
|
|||||||
story_expire_period: number;
|
story_expire_period: number;
|
||||||
story_viewers_expire_period: number;
|
story_viewers_expire_period: number;
|
||||||
stories_changelog_user_id?: number;
|
stories_changelog_user_id?: number;
|
||||||
|
peer_colors: Record<string, string[]>;
|
||||||
|
dark_peer_colors: Record<string, string[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildEmojiSounds(appConfig: GramJsAppConfig) {
|
function buildEmojiSounds(appConfig: GramJsAppConfig) {
|
||||||
@ -117,5 +119,7 @@ export function buildAppConfig(json: GramJs.TypeJSONValue, hash: number): ApiApp
|
|||||||
storyExpirePeriod: appConfig.story_expire_period ?? STORY_EXPIRE_PERIOD,
|
storyExpirePeriod: appConfig.story_expire_period ?? STORY_EXPIRE_PERIOD,
|
||||||
storyViewersExpirePeriod: appConfig.story_viewers_expire_period ?? STORY_VIEWERS_EXPIRE_PERIOD,
|
storyViewersExpirePeriod: appConfig.story_viewers_expire_period ?? STORY_VIEWERS_EXPIRE_PERIOD,
|
||||||
storyChangelogUserId: appConfig.stories_changelog_user_id?.toString() ?? SERVICE_NOTIFICATIONS_USER_ID,
|
storyChangelogUserId: appConfig.stories_changelog_user_id?.toString() ?? SERVICE_NOTIFICATIONS_USER_ID,
|
||||||
|
peerColors: appConfig.peer_colors,
|
||||||
|
darkPeerColors: appConfig.dark_peer_colors,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -72,7 +72,6 @@ export function buildBotSwitchWebview(switchWebview?: GramJs.InlineBotWebView) {
|
|||||||
export function buildApiAttachBot(bot: GramJs.AttachMenuBot): ApiAttachBot {
|
export function buildApiAttachBot(bot: GramJs.AttachMenuBot): ApiAttachBot {
|
||||||
return {
|
return {
|
||||||
id: bot.botId.toString(),
|
id: bot.botId.toString(),
|
||||||
hasSettings: bot.hasSettings,
|
|
||||||
shouldRequestWriteAccess: bot.requestWriteAccess,
|
shouldRequestWriteAccess: bot.requestWriteAccess,
|
||||||
shortName: bot.shortName,
|
shortName: bot.shortName,
|
||||||
isForAttachMenu: bot.showInAttachMenu!,
|
isForAttachMenu: bot.showInAttachMenu!,
|
||||||
|
|||||||
@ -54,6 +54,8 @@ function buildApiChatFieldsFromPeerEntity(
|
|||||||
const areStoriesHidden = Boolean('storiesHidden' in peerEntity && peerEntity.storiesHidden);
|
const areStoriesHidden = Boolean('storiesHidden' in peerEntity && peerEntity.storiesHidden);
|
||||||
const maxStoryId = 'storiesMaxId' in peerEntity ? peerEntity.storiesMaxId : undefined;
|
const maxStoryId = 'storiesMaxId' in peerEntity ? peerEntity.storiesMaxId : undefined;
|
||||||
const storiesUnavailable = Boolean('storiesUnavailable' in peerEntity && peerEntity.storiesUnavailable);
|
const storiesUnavailable = Boolean('storiesUnavailable' in peerEntity && peerEntity.storiesUnavailable);
|
||||||
|
const color = 'color' in peerEntity ? peerEntity.color : undefined;
|
||||||
|
const backgroundEmojiId = 'backgroundEmojiId' in peerEntity ? peerEntity.backgroundEmojiId?.toString() : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isMin,
|
isMin,
|
||||||
@ -66,7 +68,7 @@ function buildApiChatFieldsFromPeerEntity(
|
|||||||
...('verified' in peerEntity && { isVerified: peerEntity.verified }),
|
...('verified' in peerEntity && { isVerified: peerEntity.verified }),
|
||||||
...('callActive' in peerEntity && { isCallActive: peerEntity.callActive }),
|
...('callActive' in peerEntity && { isCallActive: peerEntity.callActive }),
|
||||||
...('callNotEmpty' in peerEntity && { isCallNotEmpty: peerEntity.callNotEmpty }),
|
...('callNotEmpty' in peerEntity && { isCallNotEmpty: peerEntity.callNotEmpty }),
|
||||||
...('date' in peerEntity && { joinDate: peerEntity.date }),
|
...('date' in peerEntity && { creationDate: peerEntity.date }),
|
||||||
...('participantsCount' in peerEntity && peerEntity.participantsCount !== undefined && {
|
...('participantsCount' in peerEntity && peerEntity.participantsCount !== undefined && {
|
||||||
membersCount: peerEntity.participantsCount,
|
membersCount: peerEntity.participantsCount,
|
||||||
}),
|
}),
|
||||||
@ -77,6 +79,8 @@ function buildApiChatFieldsFromPeerEntity(
|
|||||||
...buildApiChatRestrictions(peerEntity),
|
...buildApiChatRestrictions(peerEntity),
|
||||||
...buildApiChatMigrationInfo(peerEntity),
|
...buildApiChatMigrationInfo(peerEntity),
|
||||||
fakeType: isScam ? 'scam' : (isFake ? 'fake' : undefined),
|
fakeType: isScam ? 'scam' : (isFake ? 'fake' : undefined),
|
||||||
|
color,
|
||||||
|
backgroundEmojiId,
|
||||||
isJoinToSend,
|
isJoinToSend,
|
||||||
isJoinRequest,
|
isJoinRequest,
|
||||||
isForum,
|
isForum,
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import type {
|
|||||||
ApiGame,
|
ApiGame,
|
||||||
ApiInvoice,
|
ApiInvoice,
|
||||||
ApiLocation,
|
ApiLocation,
|
||||||
ApiMessage,
|
|
||||||
ApiMessageExtendedMediaPreview,
|
ApiMessageExtendedMediaPreview,
|
||||||
ApiMessageStoryData,
|
ApiMessageStoryData,
|
||||||
ApiPhoto,
|
ApiPhoto,
|
||||||
@ -19,6 +18,7 @@ import type {
|
|||||||
ApiWebDocument,
|
ApiWebDocument,
|
||||||
ApiWebPage,
|
ApiWebPage,
|
||||||
ApiWebPageStoryData,
|
ApiWebPageStoryData,
|
||||||
|
MediaContent,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import type { UniversalMessage } from './messages';
|
import type { UniversalMessage } from './messages';
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ import { buildStickerFromDocument } from './symbols';
|
|||||||
export function buildMessageContent(
|
export function buildMessageContent(
|
||||||
mtpMessage: UniversalMessage | GramJs.UpdateServiceNotification,
|
mtpMessage: UniversalMessage | GramJs.UpdateServiceNotification,
|
||||||
) {
|
) {
|
||||||
let content: ApiMessage['content'] = {};
|
let content: MediaContent = {};
|
||||||
|
|
||||||
if (mtpMessage.media) {
|
if (mtpMessage.media) {
|
||||||
content = {
|
content = {
|
||||||
@ -69,7 +69,7 @@ export function buildMessageTextContent(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildMessageMediaContent(media: GramJs.TypeMessageMedia): ApiMessage['content'] | undefined {
|
export function buildMessageMediaContent(media: GramJs.TypeMessageMedia): MediaContent | undefined {
|
||||||
if ('ttlSeconds' in media && media.ttlSeconds) {
|
if ('ttlSeconds' in media && media.ttlSeconds) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
import { Api as GramJs } from '../../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
|
|
||||||
|
import type { ApiDraft } from '../../../global/types';
|
||||||
import type {
|
import type {
|
||||||
ApiAction,
|
ApiAction,
|
||||||
ApiAttachment,
|
ApiAttachment,
|
||||||
ApiChat,
|
ApiChat,
|
||||||
ApiContact,
|
ApiContact,
|
||||||
ApiGroupCall,
|
ApiGroupCall,
|
||||||
|
ApiInputMessageReplyInfo,
|
||||||
|
ApiInputReplyInfo,
|
||||||
ApiKeyboardButton,
|
ApiKeyboardButton,
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiMessageEntity,
|
ApiMessageEntity,
|
||||||
@ -13,14 +16,15 @@ import type {
|
|||||||
ApiNewPoll,
|
ApiNewPoll,
|
||||||
ApiPeer,
|
ApiPeer,
|
||||||
ApiPhoto,
|
ApiPhoto,
|
||||||
|
ApiReplyInfo,
|
||||||
ApiReplyKeyboard,
|
ApiReplyKeyboard,
|
||||||
ApiSponsoredMessage,
|
ApiSponsoredMessage,
|
||||||
ApiSticker,
|
ApiSticker,
|
||||||
ApiStory,
|
ApiStory,
|
||||||
ApiStorySkipped,
|
ApiStorySkipped,
|
||||||
ApiThreadInfo,
|
ApiThreadInfo,
|
||||||
ApiTypeReplyTo,
|
|
||||||
ApiVideo,
|
ApiVideo,
|
||||||
|
MediaContent,
|
||||||
PhoneCallAction,
|
PhoneCallAction,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import {
|
import {
|
||||||
@ -49,7 +53,7 @@ import { buildApiCallDiscardReason } from './calls';
|
|||||||
import {
|
import {
|
||||||
buildApiPhoto,
|
buildApiPhoto,
|
||||||
} from './common';
|
} from './common';
|
||||||
import { buildMessageContent, buildMessageTextContent } from './messageContent';
|
import { buildMessageContent, buildMessageMediaContent, buildMessageTextContent } from './messageContent';
|
||||||
import { buildApiPeerId, getApiChatIdFromMtpPeer, isPeerUser } from './peers';
|
import { buildApiPeerId, getApiChatIdFromMtpPeer, isPeerUser } from './peers';
|
||||||
import { buildMessageReactions } from './reactions';
|
import { buildMessageReactions } from './reactions';
|
||||||
|
|
||||||
@ -171,23 +175,6 @@ export function buildApiMessageWithChatId(
|
|||||||
const isInvoiceMedia = mtpMessage.media instanceof GramJs.MessageMediaInvoice
|
const isInvoiceMedia = mtpMessage.media instanceof GramJs.MessageMediaInvoice
|
||||||
&& Boolean(mtpMessage.media.extendedMedia);
|
&& Boolean(mtpMessage.media.extendedMedia);
|
||||||
|
|
||||||
let replyToMsgId: number | undefined;
|
|
||||||
let replyToTopId: number | undefined;
|
|
||||||
let replyToStoryUserId: string | undefined;
|
|
||||||
let replyToStoryId: number | undefined;
|
|
||||||
let forumTopic: boolean | undefined;
|
|
||||||
let replyToPeerId: GramJs.TypePeer | undefined;
|
|
||||||
if (mtpMessage.replyTo instanceof GramJs.MessageReplyHeader) {
|
|
||||||
replyToMsgId = mtpMessage.replyTo.replyToMsgId;
|
|
||||||
replyToTopId = mtpMessage.replyTo.replyToTopId;
|
|
||||||
forumTopic = mtpMessage.replyTo.forumTopic;
|
|
||||||
replyToPeerId = mtpMessage.replyTo.replyToPeerId;
|
|
||||||
}
|
|
||||||
if (mtpMessage.replyTo instanceof GramJs.MessageReplyStoryHeader) {
|
|
||||||
replyToStoryUserId = buildApiPeerId(mtpMessage.replyTo.userId, 'user');
|
|
||||||
replyToStoryId = mtpMessage.replyTo.storyId;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isEdited = mtpMessage.editDate && !mtpMessage.editHide;
|
const isEdited = mtpMessage.editDate && !mtpMessage.editHide;
|
||||||
const {
|
const {
|
||||||
inlineButtons, keyboardButtons, keyboardPlaceholder, isKeyboardSingleUse, isKeyboardSelective,
|
inlineButtons, keyboardButtons, keyboardPlaceholder, isKeyboardSingleUse, isKeyboardSelective,
|
||||||
@ -218,12 +205,8 @@ export function buildApiMessageWithChatId(
|
|||||||
isPinned: mtpMessage.pinned,
|
isPinned: mtpMessage.pinned,
|
||||||
reactions: mtpMessage.reactions && buildMessageReactions(mtpMessage.reactions),
|
reactions: mtpMessage.reactions && buildMessageReactions(mtpMessage.reactions),
|
||||||
emojiOnlyCount,
|
emojiOnlyCount,
|
||||||
...(replyToMsgId && { replyToMessageId: replyToMsgId }),
|
...(mtpMessage.replyTo && { replyInfo: buildApiReplyInfo(mtpMessage.replyTo) }),
|
||||||
...(forumTopic && { isTopicReply: true }),
|
|
||||||
...(replyToPeerId && { replyToChatId: getApiChatIdFromMtpPeer(replyToPeerId) }),
|
|
||||||
...(replyToTopId && { replyToTopMessageId: replyToTopId }),
|
|
||||||
...(forwardInfo && { forwardInfo }),
|
...(forwardInfo && { forwardInfo }),
|
||||||
...(replyToStoryUserId && { replyToStoryUserId, replyToStoryId }),
|
|
||||||
...(isEdited && { isEdited }),
|
...(isEdited && { isEdited }),
|
||||||
...(mtpMessage.editDate && { editDate: mtpMessage.editDate }),
|
...(mtpMessage.editDate && { editDate: mtpMessage.editDate }),
|
||||||
...(isMediaUnread && { isMediaUnread }),
|
...(isMediaUnread && { isMediaUnread }),
|
||||||
@ -246,18 +229,26 @@ export function buildApiMessageWithChatId(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildMessageDraft(draft: GramJs.TypeDraftMessage) {
|
export function buildMessageDraft(draft: GramJs.TypeDraftMessage): ApiDraft | undefined {
|
||||||
if (draft instanceof GramJs.DraftMessageEmpty) {
|
if (draft instanceof GramJs.DraftMessageEmpty) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const {
|
const {
|
||||||
message, entities, replyToMsgId, date,
|
message, entities, replyTo, date,
|
||||||
} = draft;
|
} = draft;
|
||||||
|
|
||||||
|
const replyInfo = replyTo instanceof GramJs.InputReplyToMessage ? {
|
||||||
|
type: 'message',
|
||||||
|
replyToMsgId: replyTo.replyToMsgId,
|
||||||
|
replyToTopId: replyTo.topMsgId,
|
||||||
|
replyToPeerId: replyTo.replyToPeerId && getApiChatIdFromMtpPeer(replyTo.replyToPeerId),
|
||||||
|
quoteText: replyTo.quoteText ? buildMessageTextContent(replyTo.quoteText, replyTo.quoteEntities) : undefined,
|
||||||
|
} satisfies ApiInputMessageReplyInfo : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
formattedText: message ? buildMessageTextContent(message, entities) : undefined,
|
text: message ? buildMessageTextContent(message, entities) : undefined,
|
||||||
replyingToId: replyToMsgId,
|
replyInfo,
|
||||||
date,
|
date,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -280,6 +271,44 @@ function buildApiMessageForwardInfo(fwdFrom: GramJs.MessageFwdHeader, isChatWith
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildApiReplyInfo(replyHeader: GramJs.TypeMessageReplyHeader): ApiReplyInfo | undefined {
|
||||||
|
if (replyHeader instanceof GramJs.MessageReplyStoryHeader) {
|
||||||
|
return {
|
||||||
|
type: 'story',
|
||||||
|
userId: replyHeader.userId.toString(),
|
||||||
|
storyId: replyHeader.storyId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (replyHeader instanceof GramJs.MessageReplyHeader) {
|
||||||
|
const {
|
||||||
|
replyFrom,
|
||||||
|
replyToMsgId,
|
||||||
|
replyToTopId,
|
||||||
|
replyMedia,
|
||||||
|
replyToPeerId,
|
||||||
|
forumTopic,
|
||||||
|
quote,
|
||||||
|
quoteText,
|
||||||
|
quoteEntities,
|
||||||
|
} = replyHeader;
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'message',
|
||||||
|
replyToMsgId,
|
||||||
|
replyToTopId,
|
||||||
|
isForumTopic: forumTopic,
|
||||||
|
replyFrom: replyFrom && buildApiMessageForwardInfo(replyFrom),
|
||||||
|
replyToPeerId: replyToPeerId && getApiChatIdFromMtpPeer(replyToPeerId),
|
||||||
|
replyMedia: replyMedia && buildMessageMediaContent(replyMedia),
|
||||||
|
isQuote: quote,
|
||||||
|
quoteText: quoteText ? buildMessageTextContent(quoteText, quoteEntities) : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
function buildAction(
|
function buildAction(
|
||||||
action: GramJs.TypeMessageAction,
|
action: GramJs.TypeMessageAction,
|
||||||
senderId: string | undefined,
|
senderId: string | undefined,
|
||||||
@ -682,7 +711,7 @@ export function buildLocalMessage(
|
|||||||
chat: ApiChat,
|
chat: ApiChat,
|
||||||
text?: string,
|
text?: string,
|
||||||
entities?: ApiMessageEntity[],
|
entities?: ApiMessageEntity[],
|
||||||
replyingTo?: ApiTypeReplyTo,
|
replyInfo?: ApiInputReplyInfo,
|
||||||
attachment?: ApiAttachment,
|
attachment?: ApiAttachment,
|
||||||
sticker?: ApiSticker,
|
sticker?: ApiSticker,
|
||||||
gif?: ApiVideo,
|
gif?: ApiVideo,
|
||||||
@ -696,21 +725,8 @@ export function buildLocalMessage(
|
|||||||
const localId = getNextLocalMessageId(chat.lastMessage?.id);
|
const localId = getNextLocalMessageId(chat.lastMessage?.id);
|
||||||
const media = attachment && buildUploadingMedia(attachment);
|
const media = attachment && buildUploadingMedia(attachment);
|
||||||
const isChannel = chat.type === 'chatTypeChannel';
|
const isChannel = chat.type === 'chatTypeChannel';
|
||||||
const isForum = chat.isForum;
|
|
||||||
|
|
||||||
let replyToMessageId: number | undefined;
|
const resultReplyInfo = replyInfo && buildReplyInfo(replyInfo, chat.isForum);
|
||||||
let replyingToTopId: number | undefined;
|
|
||||||
let replyToStoryUserId: string | undefined;
|
|
||||||
let replyToStoryId: number | undefined;
|
|
||||||
if (replyingTo) {
|
|
||||||
if ('replyingTo' in replyingTo) {
|
|
||||||
replyToMessageId = replyingTo.replyingTo;
|
|
||||||
replyingToTopId = replyingTo.replyingToTopId;
|
|
||||||
} else {
|
|
||||||
replyToStoryUserId = replyingTo.userId;
|
|
||||||
replyToStoryId = replyingTo.storyId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = {
|
const message = {
|
||||||
id: localId,
|
id: localId,
|
||||||
@ -732,10 +748,7 @@ export function buildLocalMessage(
|
|||||||
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,
|
||||||
...(replyToMessageId && { replyToMessageId }),
|
replyInfo: resultReplyInfo,
|
||||||
...(replyingToTopId && { replyToTopMessageId: replyingToTopId }),
|
|
||||||
...((replyToMessageId || replyingToTopId) && isForum && { isTopicReply: true }),
|
|
||||||
...(replyToStoryUserId && { replyToStoryUserId, replyToStoryId }),
|
|
||||||
...(groupedId && {
|
...(groupedId && {
|
||||||
groupedId,
|
groupedId,
|
||||||
...(media && (media.photo || media.video) && { isInAlbum: true }),
|
...(media && (media.photo || media.video) && { isInAlbum: true }),
|
||||||
@ -796,6 +809,12 @@ export function buildLocalForwardedMessage({
|
|||||||
text: !shouldHideText ? strippedText : undefined,
|
text: !shouldHideText ? strippedText : undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const replyInfo: ApiReplyInfo | undefined = toThreadId ? {
|
||||||
|
type: 'message',
|
||||||
|
replyToTopId: toThreadId,
|
||||||
|
isForumTopic: toChat.isForum || undefined,
|
||||||
|
} : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: localId,
|
id: localId,
|
||||||
chatId: toChat.id,
|
chatId: toChat.id,
|
||||||
@ -807,7 +826,7 @@ export function buildLocalForwardedMessage({
|
|||||||
groupedId,
|
groupedId,
|
||||||
isInAlbum,
|
isInAlbum,
|
||||||
isForwardingAllowed: true,
|
isForwardingAllowed: true,
|
||||||
replyToTopMessageId: toThreadId,
|
replyInfo,
|
||||||
...(toThreadId && toChat?.isForum && { isTopicReply: true }),
|
...(toThreadId && toChat?.isForum && { isTopicReply: true }),
|
||||||
|
|
||||||
...(emojiOnlyCount && { emojiOnlyCount }),
|
...(emojiOnlyCount && { emojiOnlyCount }),
|
||||||
@ -826,9 +845,28 @@ export function buildLocalForwardedMessage({
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildReplyInfo(inputInfo: ApiInputReplyInfo, isForum?: boolean): ApiReplyInfo {
|
||||||
|
if (inputInfo.type === 'story') {
|
||||||
|
return {
|
||||||
|
type: 'story',
|
||||||
|
userId: inputInfo.userId,
|
||||||
|
storyId: inputInfo.storyId,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'message',
|
||||||
|
replyToMsgId: inputInfo.replyToMsgId,
|
||||||
|
replyToTopId: inputInfo.replyToTopId,
|
||||||
|
replyToPeerId: inputInfo.replyToPeerId,
|
||||||
|
quoteText: inputInfo.quoteText,
|
||||||
|
isForumTopic: isForum && inputInfo.replyToTopId ? true : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function buildUploadingMedia(
|
function buildUploadingMedia(
|
||||||
attachment: ApiAttachment,
|
attachment: ApiAttachment,
|
||||||
): ApiMessage['content'] {
|
): MediaContent {
|
||||||
const {
|
const {
|
||||||
filename: fileName,
|
filename: fileName,
|
||||||
blobUrl,
|
blobUrl,
|
||||||
|
|||||||
@ -1,18 +1,17 @@
|
|||||||
import { Api as GramJs, errors } from '../../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiApplyBoostInfo,
|
|
||||||
ApiBoostsStatus,
|
ApiBoostsStatus,
|
||||||
ApiMediaArea,
|
ApiMediaArea,
|
||||||
ApiMediaAreaCoordinates,
|
ApiMediaAreaCoordinates,
|
||||||
ApiMessage,
|
ApiMyBoost,
|
||||||
ApiStealthMode,
|
ApiStealthMode,
|
||||||
ApiStoryView,
|
ApiStoryView,
|
||||||
ApiTypeStory,
|
ApiTypeStory,
|
||||||
|
MediaContent,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { buildCollectionByCallback } from '../../../util/iteratees';
|
import { buildCollectionByCallback } from '../../../util/iteratees';
|
||||||
import { getServerTime } from '../../../util/serverTime';
|
|
||||||
import { buildPrivacyRules } from './common';
|
import { buildPrivacyRules } from './common';
|
||||||
import { buildGeoPoint, buildMessageMediaContent, buildMessageTextContent } from './messageContent';
|
import { buildGeoPoint, buildMessageMediaContent, buildMessageTextContent } from './messageContent';
|
||||||
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
import { buildApiPeerId, getApiChatIdFromMtpPeer } from './peers';
|
||||||
@ -49,7 +48,7 @@ export function buildApiStory(peerId: string, story: GramJs.TypeStoryItem): ApiT
|
|||||||
mediaAreas, sentReaction, out,
|
mediaAreas, sentReaction, out,
|
||||||
} = story;
|
} = story;
|
||||||
|
|
||||||
const content: ApiMessage['content'] = {
|
const content: MediaContent = {
|
||||||
...buildMessageMediaContent(media),
|
...buildMessageMediaContent(media),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -171,45 +170,7 @@ export function buildApiPeerStories(peerStories: GramJs.PeerStories) {
|
|||||||
return buildCollectionByCallback(peerStories.stories, (story) => [story.id, buildApiStory(peerId, story)]);
|
return buildCollectionByCallback(peerStories.stories, (story) => [story.id, buildApiStory(peerId, story)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildApiApplyBoostInfo(
|
export function buildApiBoostsStatus(boostStatus: GramJs.premium.BoostsStatus): ApiBoostsStatus {
|
||||||
applyBoostInfo: GramJs.stories.TypeCanApplyBoostResult,
|
|
||||||
): ApiApplyBoostInfo | undefined {
|
|
||||||
if (applyBoostInfo instanceof GramJs.stories.CanApplyBoostOk) {
|
|
||||||
return { type: 'ok' };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (applyBoostInfo instanceof GramJs.stories.CanApplyBoostReplace) {
|
|
||||||
return {
|
|
||||||
type: 'replace',
|
|
||||||
boostedChatId: getApiChatIdFromMtpPeer(applyBoostInfo.currentBoost),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildApiApplyBoostInfoFromError(
|
|
||||||
error: unknown,
|
|
||||||
): ApiApplyBoostInfo | undefined {
|
|
||||||
if (error instanceof errors.FloodWaitError) {
|
|
||||||
return {
|
|
||||||
type: 'wait',
|
|
||||||
waitUntil: getServerTime() + error.seconds,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error instanceof Error) {
|
|
||||||
if (error.message === 'BOOST_NOT_MODIFIED') {
|
|
||||||
return {
|
|
||||||
type: 'already',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildApiBoostsStatus(boostStatus: GramJs.stories.BoostsStatus): ApiBoostsStatus {
|
|
||||||
const {
|
const {
|
||||||
level, boostUrl, boosts, myBoost, currentLevelBoosts, nextLevelBoosts, premiumAudience,
|
level, boostUrl, boosts, myBoost, currentLevelBoosts, nextLevelBoosts, premiumAudience,
|
||||||
} = boostStatus;
|
} = boostStatus;
|
||||||
@ -223,3 +184,17 @@ export function buildApiBoostsStatus(boostStatus: GramJs.stories.BoostsStatus):
|
|||||||
...(premiumAudience && { premiumSubscribers: buildStatisticsPercentage(premiumAudience) }),
|
...(premiumAudience && { premiumSubscribers: buildStatisticsPercentage(premiumAudience) }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildApiMyBoost(myBoost: GramJs.MyBoost): ApiMyBoost {
|
||||||
|
const {
|
||||||
|
date, expires, slot, cooldownUntilDate, peer,
|
||||||
|
} = myBoost;
|
||||||
|
|
||||||
|
return {
|
||||||
|
date,
|
||||||
|
expires,
|
||||||
|
slot,
|
||||||
|
cooldownUntil: cooldownUntilDate,
|
||||||
|
chatId: peer && getApiChatIdFromMtpPeer(peer),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@ -85,6 +85,8 @@ export function buildApiUser(mtpUser: GramJs.TypeUser): ApiUser | undefined {
|
|||||||
hasStories: Boolean(storiesMaxId) && !storiesUnavailable,
|
hasStories: Boolean(storiesMaxId) && !storiesUnavailable,
|
||||||
...(mtpUser.bot && mtpUser.botInlinePlaceholder && { botPlaceholder: mtpUser.botInlinePlaceholder }),
|
...(mtpUser.bot && mtpUser.botInlinePlaceholder && { botPlaceholder: mtpUser.botInlinePlaceholder }),
|
||||||
...(mtpUser.bot && mtpUser.botAttachMenu && { isAttachBot: mtpUser.botAttachMenu }),
|
...(mtpUser.bot && mtpUser.botAttachMenu && { isAttachBot: mtpUser.botAttachMenu }),
|
||||||
|
color: mtpUser.color,
|
||||||
|
backgroundEmojiId: mtpUser.backgroundEmojiId?.toString(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import type {
|
|||||||
ApiChatReactions,
|
ApiChatReactions,
|
||||||
ApiFormattedText,
|
ApiFormattedText,
|
||||||
ApiGroupCall,
|
ApiGroupCall,
|
||||||
|
ApiInputReplyInfo,
|
||||||
ApiMessageEntity,
|
ApiMessageEntity,
|
||||||
ApiNewPoll,
|
ApiNewPoll,
|
||||||
ApiPhoneCall,
|
ApiPhoneCall,
|
||||||
@ -24,7 +25,6 @@ import type {
|
|||||||
ApiStory,
|
ApiStory,
|
||||||
ApiStorySkipped,
|
ApiStorySkipped,
|
||||||
ApiThemeParameters,
|
ApiThemeParameters,
|
||||||
ApiTypeReplyTo,
|
|
||||||
ApiVideo,
|
ApiVideo,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import {
|
import {
|
||||||
@ -643,24 +643,28 @@ export function buildInputBotApp(app: ApiBotApp) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildInputReplyToMessage(replyToMsgId: number, topMsgId?: number) {
|
export function buildInputReplyTo(replyInfo: ApiInputReplyInfo) {
|
||||||
return new GramJs.InputReplyToMessage({
|
if (replyInfo.type === 'story') {
|
||||||
replyToMsgId,
|
return new GramJs.InputReplyToStory({
|
||||||
topMsgId,
|
userId: buildInputPeerFromLocalDb(replyInfo.userId)!,
|
||||||
});
|
storyId: replyInfo.storyId,
|
||||||
}
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function buildInputReplyToStory(userId: string, storyId: number) {
|
if (replyInfo.type === 'message') {
|
||||||
return new GramJs.InputReplyToStory({
|
const {
|
||||||
userId: buildInputPeerFromLocalDb(userId)!,
|
replyToMsgId, replyToTopId, replyToPeerId, quoteText,
|
||||||
storyId,
|
} = replyInfo;
|
||||||
});
|
return new GramJs.InputReplyToMessage({
|
||||||
}
|
replyToMsgId,
|
||||||
|
topMsgId: replyToTopId,
|
||||||
|
replyToPeerId: replyToPeerId ? buildInputPeerFromLocalDb(replyToPeerId)! : undefined,
|
||||||
|
quoteText: quoteText?.text,
|
||||||
|
quoteEntities: quoteText?.entities?.map(buildMtpMessageEntity),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function buildInputReplyTo(replyingTo: ApiTypeReplyTo) {
|
return undefined;
|
||||||
return 'replyingTo' in replyingTo
|
|
||||||
? buildInputReplyToMessage(replyingTo.replyingTo, replyingTo.replyingToTopId)
|
|
||||||
: buildInputReplyToStory(replyingTo.userId, replyingTo.storyId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildInputPrivacyRules(
|
export function buildInputPrivacyRules(
|
||||||
|
|||||||
@ -50,29 +50,10 @@ export function addMessageToLocalDb(message: GramJs.Message | GramJs.MessageServ
|
|||||||
localDb.messages[messageFullId] = mockMessage;
|
localDb.messages[messageFullId] = mockMessage;
|
||||||
|
|
||||||
if (mockMessage instanceof GramJs.Message) {
|
if (mockMessage instanceof GramJs.Message) {
|
||||||
if (mockMessage.media instanceof GramJs.MessageMediaDocument
|
if (mockMessage.media) addMediaToLocalDb(mockMessage.media);
|
||||||
&& mockMessage.media.document instanceof GramJs.Document
|
|
||||||
) {
|
|
||||||
localDb.documents[String(mockMessage.media.document.id)] = mockMessage.media.document;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mockMessage.media instanceof GramJs.MessageMediaWebPage
|
if (mockMessage.replyTo instanceof GramJs.MessageReplyHeader && mockMessage.replyTo.replyMedia) {
|
||||||
&& mockMessage.media.webpage instanceof GramJs.WebPage
|
addMediaToLocalDb(mockMessage.replyTo.replyMedia);
|
||||||
&& mockMessage.media.webpage.document instanceof GramJs.Document
|
|
||||||
) {
|
|
||||||
localDb.documents[String(mockMessage.media.webpage.document.id)] = mockMessage.media.webpage.document;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mockMessage.media instanceof GramJs.MessageMediaGame) {
|
|
||||||
if (mockMessage.media.game.document instanceof GramJs.Document) {
|
|
||||||
localDb.documents[String(mockMessage.media.game.document.id)] = mockMessage.media.game.document;
|
|
||||||
}
|
|
||||||
addPhotoToLocalDb(mockMessage.media.game.photo);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mockMessage.media instanceof GramJs.MessageMediaInvoice
|
|
||||||
&& mockMessage.media.photo) {
|
|
||||||
localDb.webDocuments[String(mockMessage.media.photo.url)] = mockMessage.media.photo;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,6 +62,33 @@ export function addMessageToLocalDb(message: GramJs.Message | GramJs.MessageServ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addMediaToLocalDb(media: GramJs.TypeMessageMedia) {
|
||||||
|
if (media instanceof GramJs.MessageMediaDocument
|
||||||
|
&& media.document instanceof GramJs.Document
|
||||||
|
) {
|
||||||
|
localDb.documents[String(media.document.id)] = media.document;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (media instanceof GramJs.MessageMediaWebPage
|
||||||
|
&& media.webpage instanceof GramJs.WebPage
|
||||||
|
&& media.webpage.document instanceof GramJs.Document
|
||||||
|
) {
|
||||||
|
localDb.documents[String(media.webpage.document.id)] = media.webpage.document;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (media instanceof GramJs.MessageMediaGame) {
|
||||||
|
if (media.game.document instanceof GramJs.Document) {
|
||||||
|
localDb.documents[String(media.game.document.id)] = media.game.document;
|
||||||
|
}
|
||||||
|
addPhotoToLocalDb(media.game.photo);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (media instanceof GramJs.MessageMediaInvoice
|
||||||
|
&& media.photo) {
|
||||||
|
localDb.webDocuments[String(media.photo.url)] = media.photo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function addStoryToLocalDb(story: GramJs.TypeStoryItem, peerId: string) {
|
export function addStoryToLocalDb(story: GramJs.TypeStoryItem, peerId: string) {
|
||||||
if (!(story instanceof GramJs.StoryItem)) {
|
if (!(story instanceof GramJs.StoryItem)) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { Api as GramJs } from '../../../lib/gramjs';
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiBotApp,
|
ApiBotApp,
|
||||||
ApiChat, ApiPeer, ApiThemeParameters, ApiUser, OnApiUpdate,
|
ApiChat, ApiInputMessageReplyInfo, ApiPeer, ApiThemeParameters, ApiUser, OnApiUpdate,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
|
|
||||||
import { WEB_APP_PLATFORM } from '../../../config';
|
import { WEB_APP_PLATFORM } from '../../../config';
|
||||||
@ -24,7 +24,7 @@ import {
|
|||||||
buildInputBotApp,
|
buildInputBotApp,
|
||||||
buildInputEntity,
|
buildInputEntity,
|
||||||
buildInputPeer,
|
buildInputPeer,
|
||||||
buildInputReplyToMessage,
|
buildInputReplyTo,
|
||||||
buildInputThemeParams,
|
buildInputThemeParams,
|
||||||
generateRandomBigInt,
|
generateRandomBigInt,
|
||||||
} from '../gramjsBuilders';
|
} from '../gramjsBuilders';
|
||||||
@ -124,13 +124,12 @@ export async function fetchInlineBotResults({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function sendInlineBotResult({
|
export async function sendInlineBotResult({
|
||||||
chat, replyingToTopId, resultId, queryId, replyingTo, sendAs, isSilent, scheduleDate,
|
chat, replyInfo, resultId, queryId, sendAs, isSilent, scheduleDate,
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
replyingToTopId?: number;
|
replyInfo?: ApiInputMessageReplyInfo;
|
||||||
resultId: string;
|
resultId: string;
|
||||||
queryId: string;
|
queryId: string;
|
||||||
replyingTo?: number;
|
|
||||||
sendAs?: ApiPeer;
|
sendAs?: ApiPeer;
|
||||||
isSilent?: boolean;
|
isSilent?: boolean;
|
||||||
scheduleDate?: number;
|
scheduleDate?: number;
|
||||||
@ -144,9 +143,8 @@ export async function sendInlineBotResult({
|
|||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
id: resultId,
|
id: resultId,
|
||||||
scheduleDate,
|
scheduleDate,
|
||||||
...(replyingToTopId && { topMsgId: replyingToTopId }),
|
replyTo: replyInfo && buildInputReplyTo(replyInfo),
|
||||||
...(isSilent && { silent: true }),
|
...(isSilent && { silent: true }),
|
||||||
...(replyingTo && { replyToMsgId: replyingTo }),
|
|
||||||
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -173,8 +171,7 @@ export async function requestWebView({
|
|||||||
bot,
|
bot,
|
||||||
url,
|
url,
|
||||||
startParam,
|
startParam,
|
||||||
replyToMessageId,
|
replyInfo,
|
||||||
threadId,
|
|
||||||
theme,
|
theme,
|
||||||
sendAs,
|
sendAs,
|
||||||
isFromBotMenu,
|
isFromBotMenu,
|
||||||
@ -184,8 +181,7 @@ export async function requestWebView({
|
|||||||
bot: ApiUser;
|
bot: ApiUser;
|
||||||
url?: string;
|
url?: string;
|
||||||
startParam?: string;
|
startParam?: string;
|
||||||
replyToMessageId?: number;
|
replyInfo?: ApiInputMessageReplyInfo;
|
||||||
threadId?: number;
|
|
||||||
theme?: ApiThemeParameters;
|
theme?: ApiThemeParameters;
|
||||||
sendAs?: ApiPeer;
|
sendAs?: ApiPeer;
|
||||||
isFromBotMenu?: boolean;
|
isFromBotMenu?: boolean;
|
||||||
@ -199,7 +195,7 @@ export async function requestWebView({
|
|||||||
themeParams: theme ? buildInputThemeParams(theme) : undefined,
|
themeParams: theme ? buildInputThemeParams(theme) : undefined,
|
||||||
fromBotMenu: isFromBotMenu || undefined,
|
fromBotMenu: isFromBotMenu || undefined,
|
||||||
platform: WEB_APP_PLATFORM,
|
platform: WEB_APP_PLATFORM,
|
||||||
...(replyToMessageId && { replyTo: buildInputReplyToMessage(replyToMessageId, threadId) }),
|
replyTo: replyInfo && buildInputReplyTo(replyInfo),
|
||||||
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -292,16 +288,14 @@ export function prolongWebView({
|
|||||||
peer,
|
peer,
|
||||||
bot,
|
bot,
|
||||||
queryId,
|
queryId,
|
||||||
replyToMessageId,
|
replyInfo,
|
||||||
threadId,
|
|
||||||
sendAs,
|
sendAs,
|
||||||
}: {
|
}: {
|
||||||
isSilent?: boolean;
|
isSilent?: boolean;
|
||||||
peer: ApiPeer;
|
peer: ApiPeer;
|
||||||
bot: ApiUser;
|
bot: ApiUser;
|
||||||
queryId: string;
|
queryId: string;
|
||||||
replyToMessageId?: number;
|
replyInfo?: ApiInputMessageReplyInfo;
|
||||||
threadId?: number;
|
|
||||||
sendAs?: ApiPeer;
|
sendAs?: ApiPeer;
|
||||||
}) {
|
}) {
|
||||||
return invokeRequest(new GramJs.messages.ProlongWebView({
|
return invokeRequest(new GramJs.messages.ProlongWebView({
|
||||||
@ -309,7 +303,7 @@ export function prolongWebView({
|
|||||||
peer: buildInputPeer(peer.id, peer.accessHash),
|
peer: buildInputPeer(peer.id, peer.accessHash),
|
||||||
bot: buildInputPeer(bot.id, bot.accessHash),
|
bot: buildInputPeer(bot.id, bot.accessHash),
|
||||||
queryId: BigInt(queryId),
|
queryId: BigInt(queryId),
|
||||||
...(replyToMessageId && { replyTo: buildInputReplyToMessage(replyToMessageId, threadId) }),
|
replyTo: replyInfo && buildInputReplyTo(replyInfo),
|
||||||
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import BigInt from 'big-integer';
|
import BigInt from 'big-integer';
|
||||||
import { Api as GramJs } from '../../../lib/gramjs';
|
import { Api as GramJs } from '../../../lib/gramjs';
|
||||||
|
|
||||||
|
import type { ApiDraft } from '../../../global/types';
|
||||||
import type {
|
import type {
|
||||||
ApiChat,
|
ApiChat,
|
||||||
ApiChatAdminRights,
|
ApiChatAdminRights,
|
||||||
@ -8,10 +9,8 @@ import type {
|
|||||||
ApiChatFolder,
|
ApiChatFolder,
|
||||||
ApiChatFullInfo,
|
ApiChatFullInfo,
|
||||||
ApiChatReactions,
|
ApiChatReactions,
|
||||||
ApiFormattedText,
|
|
||||||
ApiGroupCall,
|
ApiGroupCall,
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiMessageEntity,
|
|
||||||
ApiPeer,
|
ApiPeer,
|
||||||
ApiPhoto,
|
ApiPhoto,
|
||||||
ApiTopic,
|
ApiTopic,
|
||||||
@ -58,6 +57,7 @@ import {
|
|||||||
buildInputEntity,
|
buildInputEntity,
|
||||||
buildInputPeer,
|
buildInputPeer,
|
||||||
buildInputPhoto,
|
buildInputPhoto,
|
||||||
|
buildInputReplyTo,
|
||||||
buildMtpMessageEntity,
|
buildMtpMessageEntity,
|
||||||
generateRandomBigInt,
|
generateRandomBigInt,
|
||||||
isMessageWithMedia,
|
isMessageWithMedia,
|
||||||
@ -135,7 +135,7 @@ export async function fetchChats({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const chats: ApiChat[] = [];
|
const chats: ApiChat[] = [];
|
||||||
const draftsById: Record<string, ApiFormattedText> = {};
|
const draftsById: Record<string, ApiDraft> = {};
|
||||||
const replyingToById: Record<string, number> = {};
|
const replyingToById: Record<string, number> = {};
|
||||||
|
|
||||||
const dialogs = (resultPinned ? resultPinned.dialogs : []).concat(result.dialogs);
|
const dialogs = (resultPinned ? resultPinned.dialogs : []).concat(result.dialogs);
|
||||||
@ -180,12 +180,9 @@ export async function fetchChats({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (dialog.draft) {
|
if (dialog.draft) {
|
||||||
const { formattedText, replyingToId } = buildMessageDraft(dialog.draft) || {};
|
const draft = buildMessageDraft(dialog.draft);
|
||||||
if (formattedText) {
|
if (draft) {
|
||||||
draftsById[chat.id] = formattedText;
|
draftsById[chat.id] = draft;
|
||||||
}
|
|
||||||
if (replyingToId) {
|
|
||||||
replyingToById[chat.id] = replyingToId;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -364,33 +361,16 @@ export async function requestChatUpdate({
|
|||||||
|
|
||||||
export function saveDraft({
|
export function saveDraft({
|
||||||
chat,
|
chat,
|
||||||
text,
|
draft,
|
||||||
entities,
|
|
||||||
threadId,
|
|
||||||
replyToMsgId,
|
|
||||||
}: {
|
}: {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
text: string;
|
draft?: ApiDraft;
|
||||||
entities?: ApiMessageEntity[];
|
|
||||||
threadId?: number;
|
|
||||||
replyToMsgId?: number;
|
|
||||||
}) {
|
}) {
|
||||||
return invokeRequest(new GramJs.messages.SaveDraft({
|
return invokeRequest(new GramJs.messages.SaveDraft({
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
message: text,
|
message: draft?.text?.text || '',
|
||||||
...(entities && {
|
entities: draft?.text?.entities?.map(buildMtpMessageEntity),
|
||||||
entities: entities.map(buildMtpMessageEntity),
|
replyTo: draft?.replyInfo && buildInputReplyTo(draft.replyInfo),
|
||||||
}),
|
|
||||||
replyToMsgId,
|
|
||||||
topMsgId: threadId,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
export function clearDraft(chat: ApiChat, threadId?: number) {
|
|
||||||
return invokeRequest(new GramJs.messages.SaveDraft({
|
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
|
||||||
message: '',
|
|
||||||
...(threadId && { topMsgId: threadId }),
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,7 +13,7 @@ export {
|
|||||||
|
|
||||||
export {
|
export {
|
||||||
fetchChats, fetchFullChat, searchChats, requestChatUpdate, fetchChatSettings,
|
fetchChats, fetchFullChat, searchChats, requestChatUpdate, fetchChatSettings,
|
||||||
saveDraft, clearDraft, fetchChat, updateChatMutedState, updateTopicMutedState,
|
saveDraft, fetchChat, updateChatMutedState, updateTopicMutedState,
|
||||||
createChannel, joinChannel, deleteChatUser, deleteChat, leaveChannel, deleteChannel, createGroupChat, editChatPhoto,
|
createChannel, joinChannel, deleteChatUser, deleteChat, leaveChannel, deleteChannel, createGroupChat, editChatPhoto,
|
||||||
toggleChatPinned, toggleChatArchived, toggleDialogUnread, setChatEnabledReactions,
|
toggleChatPinned, toggleChatArchived, toggleDialogUnread, setChatEnabledReactions,
|
||||||
fetchChatFolders, editChatFolder, deleteChatFolder, sortChatFolders, fetchRecommendedChatFolders,
|
fetchChatFolders, editChatFolder, deleteChatFolder, sortChatFolders, fetchRecommendedChatFolders,
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import type {
|
|||||||
ApiContact,
|
ApiContact,
|
||||||
ApiFormattedText,
|
ApiFormattedText,
|
||||||
ApiGlobalMessageSearchType,
|
ApiGlobalMessageSearchType,
|
||||||
|
ApiInputReplyInfo,
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiMessageEntity,
|
ApiMessageEntity,
|
||||||
ApiMessageSearchType,
|
ApiMessageSearchType,
|
||||||
@ -18,8 +19,8 @@ import type {
|
|||||||
ApiSticker,
|
ApiSticker,
|
||||||
ApiStory,
|
ApiStory,
|
||||||
ApiStorySkipped,
|
ApiStorySkipped,
|
||||||
ApiTypeReplyTo,
|
|
||||||
ApiVideo,
|
ApiVideo,
|
||||||
|
MediaContent,
|
||||||
OnApiUpdate,
|
OnApiUpdate,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import {
|
import {
|
||||||
@ -238,7 +239,7 @@ export function sendMessage(
|
|||||||
chat,
|
chat,
|
||||||
text,
|
text,
|
||||||
entities,
|
entities,
|
||||||
replyingTo,
|
replyInfo,
|
||||||
attachment,
|
attachment,
|
||||||
sticker,
|
sticker,
|
||||||
story,
|
story,
|
||||||
@ -256,7 +257,7 @@ export function sendMessage(
|
|||||||
lastMessageId?: number;
|
lastMessageId?: number;
|
||||||
text?: string;
|
text?: string;
|
||||||
entities?: ApiMessageEntity[];
|
entities?: ApiMessageEntity[];
|
||||||
replyingTo?: ApiTypeReplyTo;
|
replyInfo?: ApiInputReplyInfo;
|
||||||
attachment?: ApiAttachment;
|
attachment?: ApiAttachment;
|
||||||
sticker?: ApiSticker;
|
sticker?: ApiSticker;
|
||||||
story?: ApiStory | ApiStorySkipped;
|
story?: ApiStory | ApiStorySkipped;
|
||||||
@ -276,7 +277,7 @@ export function sendMessage(
|
|||||||
chat,
|
chat,
|
||||||
text,
|
text,
|
||||||
entities,
|
entities,
|
||||||
replyingTo,
|
replyInfo,
|
||||||
attachment,
|
attachment,
|
||||||
sticker,
|
sticker,
|
||||||
gif,
|
gif,
|
||||||
@ -315,7 +316,7 @@ export function sendMessage(
|
|||||||
chat,
|
chat,
|
||||||
text,
|
text,
|
||||||
entities,
|
entities,
|
||||||
replyingTo,
|
replyInfo,
|
||||||
attachment: attachment!,
|
attachment: attachment!,
|
||||||
groupedId,
|
groupedId,
|
||||||
isSilent,
|
isSilent,
|
||||||
@ -356,7 +357,6 @@ export function sendMessage(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const RequestClass = media ? GramJs.messages.SendMedia : GramJs.messages.SendMessage;
|
const RequestClass = media ? GramJs.messages.SendMedia : GramJs.messages.SendMessage;
|
||||||
const replyTo = replyingTo ? buildInputReplyTo(replyingTo) : undefined;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const update = await invokeRequest(new RequestClass({
|
const update = await invokeRequest(new RequestClass({
|
||||||
@ -365,9 +365,9 @@ export function sendMessage(
|
|||||||
entities: entities ? entities.map(buildMtpMessageEntity) : undefined,
|
entities: entities ? entities.map(buildMtpMessageEntity) : undefined,
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
randomId,
|
randomId,
|
||||||
|
replyTo: replyInfo && buildInputReplyTo(replyInfo),
|
||||||
...(isSilent && { silent: isSilent }),
|
...(isSilent && { silent: isSilent }),
|
||||||
...(scheduledAt && { scheduleDate: scheduledAt }),
|
...(scheduledAt && { scheduleDate: scheduledAt }),
|
||||||
...(replyTo && { replyTo }),
|
|
||||||
...(media && { media }),
|
...(media && { media }),
|
||||||
...(noWebPage && { noWebpage: noWebPage }),
|
...(noWebPage && { noWebpage: noWebPage }),
|
||||||
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
||||||
@ -402,7 +402,7 @@ function sendGroupedMedia(
|
|||||||
chat,
|
chat,
|
||||||
text,
|
text,
|
||||||
entities,
|
entities,
|
||||||
replyingTo,
|
replyInfo,
|
||||||
attachment,
|
attachment,
|
||||||
groupedId,
|
groupedId,
|
||||||
isSilent,
|
isSilent,
|
||||||
@ -412,7 +412,7 @@ function sendGroupedMedia(
|
|||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
text?: string;
|
text?: string;
|
||||||
entities?: ApiMessageEntity[];
|
entities?: ApiMessageEntity[];
|
||||||
replyingTo?: ApiTypeReplyTo;
|
replyInfo?: ApiInputReplyInfo;
|
||||||
attachment: ApiAttachment;
|
attachment: ApiAttachment;
|
||||||
groupedId: string;
|
groupedId: string;
|
||||||
isSilent?: boolean;
|
isSilent?: boolean;
|
||||||
@ -484,13 +484,12 @@ function sendGroupedMedia(
|
|||||||
|
|
||||||
const { singleMediaByIndex, localMessages } = groupedUploads[groupedId];
|
const { singleMediaByIndex, localMessages } = groupedUploads[groupedId];
|
||||||
delete groupedUploads[groupedId];
|
delete groupedUploads[groupedId];
|
||||||
const replyTo = replyingTo ? buildInputReplyTo(replyingTo) : undefined;
|
|
||||||
|
|
||||||
const update = await invokeRequest(new GramJs.messages.SendMultiMedia({
|
const update = await invokeRequest(new GramJs.messages.SendMultiMedia({
|
||||||
clearDraft: true,
|
clearDraft: true,
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
multiMedia: Object.values(singleMediaByIndex), // Object keys are usually ordered
|
multiMedia: Object.values(singleMediaByIndex), // Object keys are usually ordered
|
||||||
...(replyingTo && { replyTo }),
|
replyTo: replyInfo && buildInputReplyTo(replyInfo),
|
||||||
...(isSilent && { silent: isSilent }),
|
...(isSilent && { silent: isSilent }),
|
||||||
...(scheduledAt && { scheduleDate: scheduledAt }),
|
...(scheduledAt && { scheduleDate: scheduledAt }),
|
||||||
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
...(sendAs && { sendAs: buildInputPeer(sendAs.id, sendAs.accessHash) }),
|
||||||
@ -1738,7 +1737,7 @@ function handleLocalMessageUpdate(localMessage: ApiMessage, update: GramJs.TypeU
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let newContent: ApiMessage['content'] | undefined;
|
let newContent: MediaContent | undefined;
|
||||||
if (messageUpdate instanceof GramJs.UpdateShortSentMessage) {
|
if (messageUpdate instanceof GramJs.UpdateShortSentMessage) {
|
||||||
if (localMessage.content.text && messageUpdate.entities) {
|
if (localMessage.content.text && messageUpdate.entities) {
|
||||||
newContent = {
|
newContent = {
|
||||||
|
|||||||
@ -17,9 +17,8 @@ import { buildCollectionByCallback } from '../../../util/iteratees';
|
|||||||
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
import { buildApiChatFromPreview } from '../apiBuilders/chats';
|
||||||
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
import { getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
||||||
import {
|
import {
|
||||||
buildApiApplyBoostInfo,
|
|
||||||
buildApiApplyBoostInfoFromError,
|
|
||||||
buildApiBoostsStatus,
|
buildApiBoostsStatus,
|
||||||
|
buildApiMyBoost,
|
||||||
buildApiPeerStories,
|
buildApiPeerStories,
|
||||||
buildApiStealthMode,
|
buildApiStealthMode,
|
||||||
buildApiStory,
|
buildApiStory,
|
||||||
@ -430,50 +429,35 @@ export function activateStealthMode({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchCanApplyBoost({
|
export async function fetchMyBoosts() {
|
||||||
chat,
|
const result = await invokeRequest(new GramJs.premium.GetMyBoosts());
|
||||||
} : {
|
|
||||||
chat: ApiChat;
|
|
||||||
}) {
|
|
||||||
let result: GramJs.stories.TypeCanApplyBoostResult | undefined;
|
|
||||||
try {
|
|
||||||
result = await invokeRequest(new GramJs.stories.CanApplyBoost({
|
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
|
||||||
}), {
|
|
||||||
shouldThrow: true,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
const info = buildApiApplyBoostInfoFromError(error);
|
|
||||||
if (!info) return undefined;
|
|
||||||
return {
|
|
||||||
info,
|
|
||||||
chats: [],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result) {
|
if (!result) return undefined;
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mtpChats = 'chats' in result ? result.chats : [];
|
addEntitiesToLocalDb(result.users);
|
||||||
addEntitiesToLocalDb(mtpChats);
|
addEntitiesToLocalDb(result.chats);
|
||||||
|
|
||||||
const chats = mtpChats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
|
const users = result.users.map(buildApiUser).filter(Boolean);
|
||||||
const info = buildApiApplyBoostInfo(result);
|
const chats = result.chats.map((c) => buildApiChatFromPreview(c)).filter(Boolean);
|
||||||
|
const boosts = result.myBoosts.map(buildApiMyBoost);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
info,
|
users,
|
||||||
chats,
|
chats,
|
||||||
|
boosts,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function applyBoost({
|
export function applyBoost({
|
||||||
chat,
|
chat,
|
||||||
|
slots,
|
||||||
} : {
|
} : {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
|
slots: number[];
|
||||||
}) {
|
}) {
|
||||||
return invokeRequest(new GramJs.stories.ApplyBoost({
|
return invokeRequest(new GramJs.premium.ApplyBoost({
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
|
slots,
|
||||||
}), {
|
}), {
|
||||||
shouldReturnTrue: true,
|
shouldReturnTrue: true,
|
||||||
});
|
});
|
||||||
@ -484,7 +468,7 @@ export async function fetchBoostsStatus({
|
|||||||
}: {
|
}: {
|
||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
}) {
|
}) {
|
||||||
const result = await invokeRequest(new GramJs.stories.GetBoostsStatus({
|
const result = await invokeRequest(new GramJs.premium.GetBoostsStatus({
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
@ -504,7 +488,7 @@ export async function fetchBoostersList({
|
|||||||
offset?: string;
|
offset?: string;
|
||||||
limit?: number;
|
limit?: number;
|
||||||
}) {
|
}) {
|
||||||
const result = await invokeRequest(new GramJs.stories.GetBoostersList({
|
const result = await invokeRequest(new GramJs.premium.GetBoostsList({
|
||||||
peer: buildInputPeer(chat.id, chat.accessHash),
|
peer: buildInputPeer(chat.id, chat.accessHash),
|
||||||
offset,
|
offset,
|
||||||
limit,
|
limit,
|
||||||
@ -518,9 +502,10 @@ export async function fetchBoostersList({
|
|||||||
|
|
||||||
const users = result.users.map(buildApiUser).filter(Boolean);
|
const users = result.users.map(buildApiUser).filter(Boolean);
|
||||||
|
|
||||||
const boosterIds = result.boosters.map((booster) => booster.userId.toString());
|
const userBoosts = result.boosts.filter((boost) => boost.userId);
|
||||||
const boosters = buildCollectionByCallback(result.boosters, (booster) => (
|
const boosterIds = userBoosts.map((boost) => boost.userId!.toString());
|
||||||
[booster.userId.toString(), booster.expires]
|
const boosters = buildCollectionByCallback(userBoosts, (boost) => (
|
||||||
|
[boost.userId!.toString(), boost.expires]
|
||||||
));
|
));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { Api as GramJs, connection } from '../../lib/gramjs';
|
|||||||
import type { GroupCallConnectionData } from '../../lib/secret-sauce';
|
import type { GroupCallConnectionData } from '../../lib/secret-sauce';
|
||||||
import type {
|
import type {
|
||||||
ApiMessage, ApiMessageExtendedMediaPreview, ApiStory, ApiStorySkipped,
|
ApiMessage, ApiMessageExtendedMediaPreview, ApiStory, ApiStorySkipped,
|
||||||
ApiUpdate, ApiUpdateConnectionStateType, OnApiUpdate,
|
ApiUpdate, ApiUpdateConnectionStateType, MediaContent, OnApiUpdate,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
import { DEBUG, GENERAL_TOPIC_ID } from '../../config';
|
import { DEBUG, GENERAL_TOPIC_ID } from '../../config';
|
||||||
@ -363,7 +363,7 @@ export function updater(update: Update) {
|
|||||||
reactions: buildMessageReactions(update.reactions),
|
reactions: buildMessageReactions(update.reactions),
|
||||||
});
|
});
|
||||||
} else if (update instanceof GramJs.UpdateMessageExtendedMedia) {
|
} else if (update instanceof GramJs.UpdateMessageExtendedMedia) {
|
||||||
let media: ApiMessage['content'] | undefined;
|
let media: MediaContent | undefined;
|
||||||
if (update.extendedMedia instanceof GramJs.MessageExtendedMedia) {
|
if (update.extendedMedia instanceof GramJs.MessageExtendedMedia) {
|
||||||
media = buildMessageMediaContent(update.extendedMedia.media);
|
media = buildMessageMediaContent(update.extendedMedia.media);
|
||||||
}
|
}
|
||||||
@ -902,7 +902,7 @@ export function updater(update: Update) {
|
|||||||
'@type': 'draftMessage',
|
'@type': 'draftMessage',
|
||||||
chatId: getApiChatIdFromMtpPeer(update.peer),
|
chatId: getApiChatIdFromMtpPeer(update.peer),
|
||||||
threadId: update.topMsgId,
|
threadId: update.topMsgId,
|
||||||
...buildMessageDraft(update.draft),
|
draft: buildMessageDraft(update.draft),
|
||||||
});
|
});
|
||||||
} else if (update instanceof GramJs.UpdateContactsReset) {
|
} else if (update instanceof GramJs.UpdateContactsReset) {
|
||||||
onUpdate({ '@type': 'updateResetContactList' });
|
onUpdate({ '@type': 'updateResetContactList' });
|
||||||
|
|||||||
@ -36,12 +36,14 @@ export interface ApiChat {
|
|||||||
avatarHash?: string;
|
avatarHash?: string;
|
||||||
usernames?: ApiUsername[];
|
usernames?: ApiUsername[];
|
||||||
membersCount?: number;
|
membersCount?: number;
|
||||||
joinDate?: number;
|
creationDate?: number;
|
||||||
isSupport?: true;
|
isSupport?: true;
|
||||||
photos?: ApiPhoto[];
|
photos?: ApiPhoto[];
|
||||||
draftDate?: number;
|
draftDate?: number;
|
||||||
isProtected?: boolean;
|
isProtected?: boolean;
|
||||||
fakeType?: ApiFakeType;
|
fakeType?: ApiFakeType;
|
||||||
|
color?: number;
|
||||||
|
backgroundEmojiId?: string;
|
||||||
isForum?: boolean;
|
isForum?: boolean;
|
||||||
topics?: Record<number, ApiTopic>;
|
topics?: Record<number, ApiTopic>;
|
||||||
listedTopicIds?: number[];
|
listedTopicIds?: number[];
|
||||||
|
|||||||
@ -298,18 +298,42 @@ export interface ApiWebPage {
|
|||||||
story?: ApiWebPageStoryData;
|
story?: ApiWebPageStoryData;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ApiTypeReplyTo = ApiMessageReplyTo | ApiStoryReplyTo;
|
export type ApiReplyInfo = ApiMessageReplyInfo | ApiStoryReplyInfo;
|
||||||
|
|
||||||
export interface ApiMessageReplyTo {
|
export interface ApiMessageReplyInfo {
|
||||||
replyingTo: number;
|
type: 'message';
|
||||||
replyingToTopId?: number;
|
replyToMsgId?: number;
|
||||||
|
replyToPeerId?: string;
|
||||||
|
replyFrom?: ApiMessageForwardInfo;
|
||||||
|
replyMedia?: MediaContent;
|
||||||
|
replyToTopId?: number;
|
||||||
|
isForumTopic?: true;
|
||||||
|
isQuote?: true;
|
||||||
|
quoteText?: ApiFormattedText;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiStoryReplyTo {
|
export interface ApiStoryReplyInfo {
|
||||||
|
type: 'story';
|
||||||
userId: string;
|
userId: string;
|
||||||
storyId: number;
|
storyId: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ApiInputMessageReplyInfo {
|
||||||
|
type: 'message';
|
||||||
|
replyToMsgId: number;
|
||||||
|
replyToTopId?: number;
|
||||||
|
replyToPeerId?: string;
|
||||||
|
quoteText?: ApiFormattedText;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ApiInputStoryReplyInfo {
|
||||||
|
type: 'story';
|
||||||
|
userId: string;
|
||||||
|
storyId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ApiInputReplyInfo = ApiInputMessageReplyInfo | ApiInputStoryReplyInfo;
|
||||||
|
|
||||||
export interface ApiMessageForwardInfo {
|
export interface ApiMessageForwardInfo {
|
||||||
date: number;
|
date: number;
|
||||||
isImported?: boolean;
|
isImported?: boolean;
|
||||||
@ -391,36 +415,33 @@ export interface ApiFormattedText {
|
|||||||
entities?: ApiMessageEntity[];
|
entities?: ApiMessageEntity[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type MediaContent = {
|
||||||
|
text?: ApiFormattedText;
|
||||||
|
photo?: ApiPhoto;
|
||||||
|
video?: ApiVideo;
|
||||||
|
altVideo?: ApiVideo;
|
||||||
|
document?: ApiDocument;
|
||||||
|
sticker?: ApiSticker;
|
||||||
|
contact?: ApiContact;
|
||||||
|
poll?: ApiPoll;
|
||||||
|
action?: ApiAction;
|
||||||
|
webPage?: ApiWebPage;
|
||||||
|
audio?: ApiAudio;
|
||||||
|
voice?: ApiVoice;
|
||||||
|
invoice?: ApiInvoice;
|
||||||
|
location?: ApiLocation;
|
||||||
|
game?: ApiGame;
|
||||||
|
storyData?: ApiMessageStoryData;
|
||||||
|
};
|
||||||
|
|
||||||
export interface ApiMessage {
|
export interface ApiMessage {
|
||||||
id: number;
|
id: number;
|
||||||
chatId: string;
|
chatId: string;
|
||||||
content: {
|
content: MediaContent;
|
||||||
text?: ApiFormattedText;
|
|
||||||
photo?: ApiPhoto;
|
|
||||||
video?: ApiVideo;
|
|
||||||
altVideo?: ApiVideo;
|
|
||||||
document?: ApiDocument;
|
|
||||||
sticker?: ApiSticker;
|
|
||||||
contact?: ApiContact;
|
|
||||||
poll?: ApiPoll;
|
|
||||||
action?: ApiAction;
|
|
||||||
webPage?: ApiWebPage;
|
|
||||||
audio?: ApiAudio;
|
|
||||||
voice?: ApiVoice;
|
|
||||||
invoice?: ApiInvoice;
|
|
||||||
location?: ApiLocation;
|
|
||||||
game?: ApiGame;
|
|
||||||
storyData?: ApiMessageStoryData;
|
|
||||||
};
|
|
||||||
date: number;
|
date: number;
|
||||||
isOutgoing: boolean;
|
isOutgoing: boolean;
|
||||||
senderId?: string;
|
senderId?: string;
|
||||||
replyToChatId?: string;
|
replyInfo?: ApiReplyInfo;
|
||||||
replyToMessageId?: number;
|
|
||||||
replyToTopMessageId?: number;
|
|
||||||
isTopicReply?: true;
|
|
||||||
replyToStoryUserId?: string;
|
|
||||||
replyToStoryId?: number;
|
|
||||||
sendingState?: 'messageSendingStatePending' | 'messageSendingStateFailed';
|
sendingState?: 'messageSendingStatePending' | 'messageSendingStateFailed';
|
||||||
forwardInfo?: ApiMessageForwardInfo;
|
forwardInfo?: ApiMessageForwardInfo;
|
||||||
isDeleting?: boolean;
|
isDeleting?: boolean;
|
||||||
@ -657,6 +678,12 @@ export type ApiThemeParameters = {
|
|||||||
button_color: string;
|
button_color: string;
|
||||||
button_text_color: string;
|
button_text_color: string;
|
||||||
secondary_bg_color: string;
|
secondary_bg_color: string;
|
||||||
|
header_bg_color: string;
|
||||||
|
accent_text_color: string;
|
||||||
|
section_bg_color: string;
|
||||||
|
section_header_text_color: string;
|
||||||
|
subtitle_text_color: string;
|
||||||
|
destructive_text_color: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ApiBotApp = {
|
export type ApiBotApp = {
|
||||||
|
|||||||
@ -195,6 +195,8 @@ export interface ApiAppConfig {
|
|||||||
storyExpirePeriod: number;
|
storyExpirePeriod: number;
|
||||||
storyViewersExpirePeriod: number;
|
storyViewersExpirePeriod: number;
|
||||||
storyChangelogUserId: string;
|
storyChangelogUserId: string;
|
||||||
|
peerColors: Record<string, string[]>;
|
||||||
|
darkPeerColors: Record<string, string[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiConfig {
|
export interface ApiConfig {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import type { ApiPrivacySettings } from '../../types';
|
import type { ApiPrivacySettings } from '../../types';
|
||||||
import type {
|
import type {
|
||||||
ApiGeoPoint, ApiMessage, ApiReaction, ApiReactionCount,
|
ApiGeoPoint, ApiReaction, ApiReactionCount, MediaContent,
|
||||||
} from './messages';
|
} from './messages';
|
||||||
import type { StatisticsOverviewPercentage } from './statistics';
|
import type { StatisticsOverviewPercentage } from './statistics';
|
||||||
|
|
||||||
@ -10,7 +10,7 @@ export interface ApiStory {
|
|||||||
peerId: string;
|
peerId: string;
|
||||||
date: number;
|
date: number;
|
||||||
expireDate: number;
|
expireDate: number;
|
||||||
content: ApiMessage['content'];
|
content: MediaContent;
|
||||||
isPinned?: boolean;
|
isPinned?: boolean;
|
||||||
isEdited?: boolean;
|
isEdited?: boolean;
|
||||||
isForCloseFriends?: boolean;
|
isForCloseFriends?: boolean;
|
||||||
@ -110,26 +110,6 @@ export type ApiMediaAreaSuggestedReaction = {
|
|||||||
|
|
||||||
export type ApiMediaArea = ApiMediaAreaVenue | ApiMediaAreaGeoPoint | ApiMediaAreaSuggestedReaction;
|
export type ApiMediaArea = ApiMediaAreaVenue | ApiMediaAreaGeoPoint | ApiMediaAreaSuggestedReaction;
|
||||||
|
|
||||||
export type ApiApplyBoostOk = {
|
|
||||||
type: 'ok';
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ApiApplyBoostReplace = {
|
|
||||||
type: 'replace';
|
|
||||||
boostedChatId: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ApiApplyBoostWait = {
|
|
||||||
type: 'wait';
|
|
||||||
waitUntil: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ApiApplyBoostAlready = {
|
|
||||||
type: 'already';
|
|
||||||
};
|
|
||||||
|
|
||||||
export type ApiApplyBoostInfo = ApiApplyBoostOk | ApiApplyBoostReplace | ApiApplyBoostWait | ApiApplyBoostAlready;
|
|
||||||
|
|
||||||
export type ApiBoostsStatus = {
|
export type ApiBoostsStatus = {
|
||||||
level: number;
|
level: number;
|
||||||
currentLevelBoosts: number;
|
currentLevelBoosts: number;
|
||||||
@ -139,3 +119,11 @@ export type ApiBoostsStatus = {
|
|||||||
boostUrl: string;
|
boostUrl: string;
|
||||||
premiumSubscribers?: StatisticsOverviewPercentage;
|
premiumSubscribers?: StatisticsOverviewPercentage;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ApiMyBoost = {
|
||||||
|
slot: number;
|
||||||
|
chatId?: string;
|
||||||
|
date: number;
|
||||||
|
expires: number;
|
||||||
|
cooldownUntil?: number;
|
||||||
|
};
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import type { ApiDraft } from '../../global/types';
|
||||||
import type {
|
import type {
|
||||||
GroupCallConnectionData,
|
GroupCallConnectionData,
|
||||||
GroupCallConnectionState,
|
GroupCallConnectionState,
|
||||||
@ -27,6 +28,7 @@ import type {
|
|||||||
ApiReactions,
|
ApiReactions,
|
||||||
ApiStickerSet,
|
ApiStickerSet,
|
||||||
ApiThreadInfo,
|
ApiThreadInfo,
|
||||||
|
MediaContent,
|
||||||
} from './messages';
|
} from './messages';
|
||||||
import type {
|
import type {
|
||||||
ApiEmojiInteraction, ApiError, ApiInviteInfo, ApiNotifyException, ApiSessionData,
|
ApiEmojiInteraction, ApiError, ApiInviteInfo, ApiNotifyException, ApiSessionData,
|
||||||
@ -312,9 +314,7 @@ export type ApiUpdateDraftMessage = {
|
|||||||
'@type': 'draftMessage';
|
'@type': 'draftMessage';
|
||||||
chatId: string;
|
chatId: string;
|
||||||
threadId?: number;
|
threadId?: number;
|
||||||
formattedText?: ApiFormattedText;
|
draft?: ApiDraft;
|
||||||
date?: number;
|
|
||||||
replyingToId?: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ApiUpdateMessageReactions = {
|
export type ApiUpdateMessageReactions = {
|
||||||
@ -328,7 +328,7 @@ export type ApiUpdateMessageExtendedMedia = {
|
|||||||
'@type': 'updateMessageExtendedMedia';
|
'@type': 'updateMessageExtendedMedia';
|
||||||
id: number;
|
id: number;
|
||||||
chatId: string;
|
chatId: string;
|
||||||
media?: ApiMessage['content'];
|
media?: MediaContent;
|
||||||
preview?: ApiMessageExtendedMediaPreview;
|
preview?: ApiMessageExtendedMediaPreview;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -35,6 +35,8 @@ export interface ApiUser {
|
|||||||
hasStories?: boolean;
|
hasStories?: boolean;
|
||||||
hasUnreadStories?: boolean;
|
hasUnreadStories?: boolean;
|
||||||
maxStoryId?: number;
|
maxStoryId?: number;
|
||||||
|
color?: number;
|
||||||
|
backgroundEmojiId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiUserFullInfo {
|
export interface ApiUserFullInfo {
|
||||||
@ -81,7 +83,6 @@ type ApiAttachBotForMenu = {
|
|||||||
|
|
||||||
type ApiAttachBotBase = {
|
type ApiAttachBotBase = {
|
||||||
id: string;
|
id: string;
|
||||||
hasSettings?: boolean;
|
|
||||||
shouldRequestWriteAccess?: boolean;
|
shouldRequestWriteAccess?: boolean;
|
||||||
shortName: string;
|
shortName: string;
|
||||||
isForSideMenu?: true;
|
isForSideMenu?: true;
|
||||||
|
|||||||
1
src/assets/font-icons/quote.svg
Normal file
1
src/assets/font-icons/quote.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M.36 10.786a6.951 6.951 0 0 1 13.902 0v2.029c0 3.777-.88 7.502-2.568 10.88l-1.274 2.548a3.476 3.476 0 0 1-6.218-3.109l1.274-2.548c.465-.928.843-1.894 1.133-2.884a6.952 6.952 0 0 1-6.25-6.916Zm17.378 0a6.951 6.951 0 0 1 13.902 0v2.029c0 3.777-.88 7.502-2.568 10.88l-1.274 2.548a3.476 3.476 0 0 1-6.218-3.109l1.274-2.548c.465-.928.843-1.894 1.133-2.884a6.952 6.952 0 0 1-6.25-6.916z"/></svg>
|
||||||
|
After Width: | Height: | Size: 461 B |
@ -1,4 +1,4 @@
|
|||||||
@import "../../../styles/mixins";
|
@use "../../../styles/mixins";
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
--group-call-panel-color: #212121;
|
--group-call-panel-color: #212121;
|
||||||
@ -74,7 +74,7 @@
|
|||||||
border-bottom: 0.0625rem solid transparent;
|
border-bottom: 0.0625rem solid transparent;
|
||||||
|
|
||||||
padding: 0.375rem 0.875rem;
|
padding: 0.375rem 0.875rem;
|
||||||
@include adapt-padding-to-scrollbar(0.875rem);
|
@include mixins.adapt-padding-to-scrollbar(0.875rem);
|
||||||
|
|
||||||
user-select: none;
|
user-select: none;
|
||||||
z-index: 1;
|
z-index: 1;
|
||||||
@ -139,7 +139,7 @@
|
|||||||
.participants {
|
.participants {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin: 0.125rem 0.5rem 0;
|
margin: 0.125rem 0.5rem 0;
|
||||||
@include adapt-margin-to-scrollbar(0.5rem);
|
@include mixins.adapt-margin-to-scrollbar(0.5rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.participantVideos {
|
.participantVideos {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import '../../../styles/mixins';
|
@use '../../../styles/mixins';
|
||||||
|
|
||||||
.participant-menu {
|
.participant-menu {
|
||||||
--color-text: white;
|
--color-text: white;
|
||||||
@ -84,7 +84,7 @@
|
|||||||
transition: 0.25s ease-in-out background-color, 0.25s ease-in-out box-shadow;
|
transition: 0.25s ease-in-out background-color, 0.25s ease-in-out box-shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include reset-range();
|
@include mixins.reset-range();
|
||||||
|
|
||||||
// Apply custom styles
|
// Apply custom styles
|
||||||
input[type="range"] {
|
input[type="range"] {
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import { IS_TEST } from '../../config';
|
|||||||
import {
|
import {
|
||||||
getChatAvatarHash,
|
getChatAvatarHash,
|
||||||
getChatTitle,
|
getChatTitle,
|
||||||
getPeerColorKey,
|
|
||||||
getPeerStoryHtmlId,
|
getPeerStoryHtmlId,
|
||||||
getUserFullName,
|
getUserFullName,
|
||||||
isChatWithRepliesBot,
|
isChatWithRepliesBot,
|
||||||
@ -23,6 +22,7 @@ import {
|
|||||||
} from '../../global/helpers';
|
} from '../../global/helpers';
|
||||||
import buildClassName, { createClassNameBuilder } from '../../util/buildClassName';
|
import buildClassName, { createClassNameBuilder } from '../../util/buildClassName';
|
||||||
import { getFirstLetters } from '../../util/textFormat';
|
import { getFirstLetters } from '../../util/textFormat';
|
||||||
|
import { getPeerColorClass } from './helpers/peerColor';
|
||||||
import renderText from './helpers/renderText';
|
import renderText from './helpers/renderText';
|
||||||
|
|
||||||
import { useFastClick } from '../../hooks/useFastClick';
|
import { useFastClick } from '../../hooks/useFastClick';
|
||||||
@ -210,7 +210,7 @@ const Avatar: FC<OwnProps> = ({
|
|||||||
const fullClassName = buildClassName(
|
const fullClassName = buildClassName(
|
||||||
`Avatar size-${size}`,
|
`Avatar size-${size}`,
|
||||||
className,
|
className,
|
||||||
`color-bg-${getPeerColorKey(peer)}`,
|
getPeerColorClass(peer),
|
||||||
isSavedMessages && 'saved-messages',
|
isSavedMessages && 'saved-messages',
|
||||||
isDeleted && 'deleted-account',
|
isDeleted && 'deleted-account',
|
||||||
isReplies && 'replies-bot-account',
|
isReplies && 'replies-bot-account',
|
||||||
|
|||||||
@ -70,7 +70,6 @@ import {
|
|||||||
selectIsRightColumnShown,
|
selectIsRightColumnShown,
|
||||||
selectNewestMessageWithBotKeyboardButtons,
|
selectNewestMessageWithBotKeyboardButtons,
|
||||||
selectPeerStory,
|
selectPeerStory,
|
||||||
selectReplyingToId,
|
|
||||||
selectRequestedDraftFiles,
|
selectRequestedDraftFiles,
|
||||||
selectRequestedDraftText,
|
selectRequestedDraftText,
|
||||||
selectScheduledIds,
|
selectScheduledIds,
|
||||||
@ -188,7 +187,6 @@ type StateProps =
|
|||||||
isChatWithBot?: boolean;
|
isChatWithBot?: boolean;
|
||||||
isChatWithSelf?: boolean;
|
isChatWithSelf?: boolean;
|
||||||
isChannel?: boolean;
|
isChannel?: boolean;
|
||||||
replyingToId?: number;
|
|
||||||
isForCurrentMessageList: boolean;
|
isForCurrentMessageList: boolean;
|
||||||
isRightColumnShown?: boolean;
|
isRightColumnShown?: boolean;
|
||||||
isSelectModeActive?: boolean;
|
isSelectModeActive?: boolean;
|
||||||
@ -318,7 +316,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
sendAsChat,
|
sendAsChat,
|
||||||
sendAsId,
|
sendAsId,
|
||||||
editingDraft,
|
editingDraft,
|
||||||
replyingToId,
|
|
||||||
requestedDraftText,
|
requestedDraftText,
|
||||||
requestedDraftFiles,
|
requestedDraftFiles,
|
||||||
botMenuButton,
|
botMenuButton,
|
||||||
@ -695,7 +692,6 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
messageListType,
|
messageListType,
|
||||||
draft,
|
draft,
|
||||||
editingDraft,
|
editingDraft,
|
||||||
replyingToId,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle chat change (should be placed after `useDraft` and `useEditing`)
|
// Handle chat change (should be placed after `useDraft` and `useEditing`)
|
||||||
@ -890,7 +886,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
lastMessageSendTimeSeconds.current = getServerTime();
|
lastMessageSendTimeSeconds.current = getServerTime();
|
||||||
|
|
||||||
clearDraft({ chatId, localOnly: true });
|
clearDraft({ chatId, isLocalOnly: true });
|
||||||
|
|
||||||
// Wait until message animation starts
|
// Wait until message animation starts
|
||||||
requestMeasure(() => {
|
requestMeasure(() => {
|
||||||
@ -971,7 +967,7 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
lastMessageSendTimeSeconds.current = getServerTime();
|
lastMessageSendTimeSeconds.current = getServerTime();
|
||||||
|
|
||||||
clearDraft({ chatId, localOnly: true });
|
clearDraft({ chatId, isLocalOnly: true });
|
||||||
|
|
||||||
if (IS_IOS && messageInput && messageInput === document.activeElement) {
|
if (IS_IOS && messageInput && messageInput === document.activeElement) {
|
||||||
applyIosAutoCapitalizationFix(messageInput);
|
applyIosAutoCapitalizationFix(messageInput);
|
||||||
@ -1158,14 +1154,14 @@ const Composer: FC<OwnProps & StateProps> = ({
|
|||||||
applyIosAutoCapitalizationFix(messageInput);
|
applyIosAutoCapitalizationFix(messageInput);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearDraft({ chatId, localOnly: true });
|
clearDraft({ chatId, isLocalOnly: true });
|
||||||
requestMeasure(() => {
|
requestMeasure(() => {
|
||||||
resetComposer();
|
resetComposer();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleBotCommandSelect = useLastCallback(() => {
|
const handleBotCommandSelect = useLastCallback(() => {
|
||||||
clearDraft({ chatId, localOnly: true });
|
clearDraft({ chatId, isLocalOnly: true });
|
||||||
requestMeasure(() => {
|
requestMeasure(() => {
|
||||||
resetComposer();
|
resetComposer();
|
||||||
});
|
});
|
||||||
@ -1930,8 +1926,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
? selectEditingScheduledDraft(global, chatId)
|
? selectEditingScheduledDraft(global, chatId)
|
||||||
: selectEditingDraft(global, chatId, threadId);
|
: selectEditingDraft(global, chatId, threadId);
|
||||||
|
|
||||||
const replyingToId = selectReplyingToId(global, chatId, threadId);
|
|
||||||
|
|
||||||
const story = storyId && selectPeerStory(global, chatId, storyId);
|
const story = storyId && selectPeerStory(global, chatId, storyId);
|
||||||
const sentStoryReaction = story && 'sentReaction' in story ? story.sentReaction : undefined;
|
const sentStoryReaction = story && 'sentReaction' in story ? story.sentReaction : undefined;
|
||||||
|
|
||||||
@ -1940,7 +1934,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
topReactions: type === 'story' ? global.topReactions : undefined,
|
topReactions: type === 'story' ? global.topReactions : undefined,
|
||||||
isOnActiveTab: !tabState.isBlurred,
|
isOnActiveTab: !tabState.isBlurred,
|
||||||
editingMessage: selectEditingMessage(global, chatId, threadId, messageListType),
|
editingMessage: selectEditingMessage(global, chatId, threadId, messageListType),
|
||||||
replyingToId,
|
|
||||||
draft: selectDraft(global, chatId, threadId),
|
draft: selectDraft(global, chatId, threadId),
|
||||||
chat,
|
chat,
|
||||||
isChatWithBot,
|
isChatWithBot,
|
||||||
|
|||||||
@ -1,173 +0,0 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
|
||||||
import React, { useRef } from '../../lib/teact/teact';
|
|
||||||
|
|
||||||
import type {
|
|
||||||
ApiMessage, ApiPeer,
|
|
||||||
} from '../../api/types';
|
|
||||||
import type { ChatTranslatedMessages } from '../../global/types';
|
|
||||||
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
|
||||||
|
|
||||||
import {
|
|
||||||
getMessageIsSpoiler,
|
|
||||||
getMessageMediaHash,
|
|
||||||
getMessageRoundVideo,
|
|
||||||
getPeerColorKey,
|
|
||||||
getSenderTitle,
|
|
||||||
isActionMessage,
|
|
||||||
isMessageTranslatable,
|
|
||||||
} from '../../global/helpers';
|
|
||||||
import buildClassName from '../../util/buildClassName';
|
|
||||||
import { getPictogramDimensions } from './helpers/mediaDimensions';
|
|
||||||
import renderText from './helpers/renderText';
|
|
||||||
|
|
||||||
import { useFastClick } from '../../hooks/useFastClick';
|
|
||||||
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
|
||||||
import useLang from '../../hooks/useLang';
|
|
||||||
import useMedia from '../../hooks/useMedia';
|
|
||||||
import useThumbnail from '../../hooks/useThumbnail';
|
|
||||||
import useMessageTranslation from '../middle/message/hooks/useMessageTranslation';
|
|
||||||
|
|
||||||
import ActionMessage from '../middle/ActionMessage';
|
|
||||||
import Icon from './Icon';
|
|
||||||
import MediaSpoiler from './MediaSpoiler';
|
|
||||||
import MessageSummary from './MessageSummary';
|
|
||||||
|
|
||||||
import './EmbeddedMessage.scss';
|
|
||||||
|
|
||||||
type OwnProps = {
|
|
||||||
className?: string;
|
|
||||||
message?: ApiMessage;
|
|
||||||
sender?: ApiPeer;
|
|
||||||
forwardSender?: ApiPeer;
|
|
||||||
title?: string;
|
|
||||||
customText?: string;
|
|
||||||
noUserColors?: boolean;
|
|
||||||
isProtected?: boolean;
|
|
||||||
hasContextMenu?: boolean;
|
|
||||||
chatTranslations?: ChatTranslatedMessages;
|
|
||||||
requestedChatTranslationLanguage?: string;
|
|
||||||
observeIntersectionForLoading?: ObserveFn;
|
|
||||||
observeIntersectionForPlaying?: ObserveFn;
|
|
||||||
onClick: NoneToVoidFunction;
|
|
||||||
};
|
|
||||||
|
|
||||||
const NBSP = '\u00A0';
|
|
||||||
|
|
||||||
const EmbeddedMessage: FC<OwnProps> = ({
|
|
||||||
className,
|
|
||||||
message,
|
|
||||||
sender,
|
|
||||||
forwardSender,
|
|
||||||
title,
|
|
||||||
customText,
|
|
||||||
isProtected,
|
|
||||||
noUserColors,
|
|
||||||
hasContextMenu,
|
|
||||||
chatTranslations,
|
|
||||||
requestedChatTranslationLanguage,
|
|
||||||
observeIntersectionForLoading,
|
|
||||||
observeIntersectionForPlaying,
|
|
||||||
onClick,
|
|
||||||
}) => {
|
|
||||||
// eslint-disable-next-line no-null/no-null
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
|
||||||
const isIntersecting = useIsIntersecting(ref, observeIntersectionForLoading);
|
|
||||||
|
|
||||||
const mediaBlobUrl = useMedia(message && getMessageMediaHash(message, 'pictogram'), !isIntersecting);
|
|
||||||
const mediaThumbnail = useThumbnail(message);
|
|
||||||
const isRoundVideo = Boolean(message && getMessageRoundVideo(message));
|
|
||||||
const isSpoiler = Boolean(message && getMessageIsSpoiler(message));
|
|
||||||
|
|
||||||
const shouldTranslate = message && isMessageTranslatable(message);
|
|
||||||
const { translatedText } = useMessageTranslation(
|
|
||||||
chatTranslations, message?.chatId, shouldTranslate ? message?.id : undefined, requestedChatTranslationLanguage,
|
|
||||||
);
|
|
||||||
|
|
||||||
const lang = useLang();
|
|
||||||
|
|
||||||
const senderTitle = sender ? getSenderTitle(lang, sender) : message?.forwardInfo?.hiddenUserName;
|
|
||||||
const forwardSenderTitle = forwardSender ? getSenderTitle(lang, forwardSender)
|
|
||||||
: message?.forwardInfo?.hiddenUserName;
|
|
||||||
const areSendersSame = sender?.id === forwardSender?.id;
|
|
||||||
|
|
||||||
const { handleClick, handleMouseDown } = useFastClick(onClick);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
ref={ref}
|
|
||||||
className={buildClassName(
|
|
||||||
'EmbeddedMessage',
|
|
||||||
className,
|
|
||||||
sender && !noUserColors && `color-${getPeerColorKey(sender)}`,
|
|
||||||
)}
|
|
||||||
onClick={message && handleClick}
|
|
||||||
onMouseDown={message && handleMouseDown}
|
|
||||||
>
|
|
||||||
{mediaThumbnail && renderPictogram(mediaThumbnail, mediaBlobUrl, isRoundVideo, isProtected, isSpoiler)}
|
|
||||||
<div className="message-text">
|
|
||||||
<p dir="auto">
|
|
||||||
{!message ? (
|
|
||||||
customText || NBSP
|
|
||||||
) : isActionMessage(message) ? (
|
|
||||||
<ActionMessage
|
|
||||||
message={message}
|
|
||||||
isEmbedded
|
|
||||||
observeIntersectionForLoading={observeIntersectionForLoading}
|
|
||||||
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<MessageSummary
|
|
||||||
lang={lang}
|
|
||||||
message={message}
|
|
||||||
noEmoji={Boolean(mediaThumbnail)}
|
|
||||||
translatedText={translatedText}
|
|
||||||
observeIntersectionForLoading={observeIntersectionForLoading}
|
|
||||||
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</p>
|
|
||||||
<div className="message-title" dir="auto">
|
|
||||||
{renderText(senderTitle || title || NBSP)}
|
|
||||||
{forwardSenderTitle && !areSendersSame && (
|
|
||||||
<>
|
|
||||||
<Icon name={forwardSender ? 'share-filled' : 'forward'} className="embedded-origin-icon" />
|
|
||||||
{renderText(forwardSenderTitle)}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{hasContextMenu && <Icon name="more" className="embedded-more" />}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
function renderPictogram(
|
|
||||||
thumbDataUri: string,
|
|
||||||
blobUrl?: string,
|
|
||||||
isRoundVideo?: boolean,
|
|
||||||
isProtected?: boolean,
|
|
||||||
isSpoiler?: boolean,
|
|
||||||
) {
|
|
||||||
const { width, height } = getPictogramDimensions();
|
|
||||||
|
|
||||||
const srcUrl = blobUrl || thumbDataUri;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={buildClassName('embedded-thumb', isRoundVideo && 'round')}>
|
|
||||||
{!isSpoiler && (
|
|
||||||
<img
|
|
||||||
src={srcUrl}
|
|
||||||
width={width}
|
|
||||||
height={height}
|
|
||||||
alt=""
|
|
||||||
className="pictogram"
|
|
||||||
draggable={false}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<MediaSpoiler thumbDataUri={srcUrl} isVisible={Boolean(isSpoiler)} width={width} height={height} />
|
|
||||||
{isProtected && <span className="protector" />}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default EmbeddedMessage;
|
|
||||||
@ -1,10 +1,10 @@
|
|||||||
@import "../../styles/mixins";
|
@use "../../styles/mixins";
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
padding: 1.5rem 1.5rem 0;
|
padding: 1.5rem 1.5rem 0;
|
||||||
margin-bottom: 0.625rem;
|
margin-bottom: 0.625rem;
|
||||||
|
|
||||||
@include side-panel-section;
|
@include mixins.side-panel-section;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import type { ApiChat, ApiPhoto, ApiUser } from '../../api/types';
|
|||||||
import {
|
import {
|
||||||
getChatAvatarHash,
|
getChatAvatarHash,
|
||||||
getChatTitle,
|
getChatTitle,
|
||||||
getPeerColorKey,
|
|
||||||
getUserFullName,
|
getUserFullName,
|
||||||
getVideoAvatarMediaHash,
|
getVideoAvatarMediaHash,
|
||||||
isChatWithRepliesBot,
|
isChatWithRepliesBot,
|
||||||
@ -16,6 +15,7 @@ import {
|
|||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import { getFirstLetters } from '../../util/textFormat';
|
import { getFirstLetters } from '../../util/textFormat';
|
||||||
import { IS_CANVAS_FILTER_SUPPORTED } from '../../util/windowEnvironment';
|
import { IS_CANVAS_FILTER_SUPPORTED } from '../../util/windowEnvironment';
|
||||||
|
import { getPeerColorClass } from './helpers/peerColor';
|
||||||
import renderText from './helpers/renderText';
|
import renderText from './helpers/renderText';
|
||||||
|
|
||||||
import useAppLayout from '../../hooks/useAppLayout';
|
import useAppLayout from '../../hooks/useAppLayout';
|
||||||
@ -55,11 +55,11 @@ const ProfilePhoto: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const isDeleted = user && isDeletedUser(user);
|
const isDeleted = user && isDeletedUser(user);
|
||||||
const isRepliesChat = chat && isChatWithRepliesBot(chat.id);
|
const isRepliesChat = chat && isChatWithRepliesBot(chat.id);
|
||||||
const userOrChat = user || chat;
|
const peer = user || chat;
|
||||||
const canHaveMedia = userOrChat && !isSavedMessages && !isDeleted && !isRepliesChat;
|
const canHaveMedia = peer && !isSavedMessages && !isDeleted && !isRepliesChat;
|
||||||
const { isVideo } = photo || {};
|
const { isVideo } = photo || {};
|
||||||
|
|
||||||
const avatarHash = canHaveMedia && getChatAvatarHash(userOrChat, 'normal');
|
const avatarHash = canHaveMedia && getChatAvatarHash(peer, 'normal');
|
||||||
const avatarBlobUrl = useMedia(avatarHash);
|
const avatarBlobUrl = useMedia(avatarHash);
|
||||||
|
|
||||||
const photoHash = canHaveMedia && photo && !isVideo && `photo${photo.id}?size=c`;
|
const photoHash = canHaveMedia && photo && !isVideo && `photo${photo.id}?size=c`;
|
||||||
@ -139,7 +139,7 @@ const ProfilePhoto: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const fullClassName = buildClassName(
|
const fullClassName = buildClassName(
|
||||||
'ProfilePhoto',
|
'ProfilePhoto',
|
||||||
`color-bg-${getPeerColorKey(user || chat)}`,
|
getPeerColorClass(peer),
|
||||||
isSavedMessages && 'saved-messages',
|
isSavedMessages && 'saved-messages',
|
||||||
isDeleted && 'deleted-account',
|
isDeleted && 'deleted-account',
|
||||||
isRepliesChat && 'replies-bot-account',
|
isRepliesChat && 'replies-bot-account',
|
||||||
|
|||||||
@ -1,7 +1,3 @@
|
|||||||
:root {
|
|
||||||
--thumbs-background: var(--color-background);
|
|
||||||
}
|
|
||||||
|
|
||||||
.thumb {
|
.thumb {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -13,7 +9,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.thumb-opaque {
|
.thumb-opaque {
|
||||||
background: var(--thumbs-background);
|
|
||||||
transition-delay: 0s;
|
transition-delay: 0s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,21 +1,22 @@
|
|||||||
|
@use "sass:map";
|
||||||
|
@use "../../../styles/mixins";
|
||||||
|
@use "../../../styles/icons";
|
||||||
|
|
||||||
.EmbeddedMessage {
|
.EmbeddedMessage {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: calc(var(--message-text-size, 1rem) - 0.125rem);
|
font-size: calc(var(--message-text-size, 1rem) - 0.125rem);
|
||||||
line-height: 1.125rem;
|
line-height: 1.125rem;
|
||||||
margin: 0 -0.25rem 0.0625rem;
|
margin-bottom: 0.0625rem;
|
||||||
padding: 0.1875rem 0.25rem 0.1875rem 0.4375rem;
|
padding: 0.1875rem 0.375rem 0.1875rem 0.1875rem;
|
||||||
border-radius: var(--border-radius-messages-small);
|
border-radius: var(--border-radius-messages-small);
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
cursor: var(--custom-cursor, pointer);
|
cursor: var(--custom-cursor, pointer);
|
||||||
direction: ltr;
|
|
||||||
|
|
||||||
@for $i from 1 through 8 {
|
background-color: var(--accent-background-color);
|
||||||
&.color-#{$i} {
|
|
||||||
--accent-color: var(--color-user-#{$i});
|
transition: background-color 0.2s ease-in;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
body.no-page-transitions & {
|
body.no-page-transitions & {
|
||||||
.ripple-container {
|
.ripple-container {
|
||||||
@ -25,17 +26,10 @@
|
|||||||
|
|
||||||
.custom-shape & {
|
.custom-shape & {
|
||||||
max-width: 15rem;
|
max-width: 15rem;
|
||||||
padding: 0.5rem;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
background-color: var(--background-color);
|
background-color: var(--color-reply-active);
|
||||||
box-shadow: 0 1px 2px var(--color-default-shadow);
|
box-shadow: 0 1px 2px var(--color-default-shadow);
|
||||||
|
|
||||||
&::before {
|
|
||||||
left: 0.625rem;
|
|
||||||
top: 0.625rem;
|
|
||||||
bottom: 0.625rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embedded-thumb {
|
.embedded-thumb {
|
||||||
margin-inline-start: 0.5rem;
|
margin-inline-start: 0.5rem;
|
||||||
}
|
}
|
||||||
@ -49,38 +43,62 @@
|
|||||||
content: "";
|
content: "";
|
||||||
display: block;
|
display: block;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0.3125rem;
|
top: 0;
|
||||||
bottom: 0.3125rem;
|
bottom: 0;
|
||||||
left: 0.375rem;
|
inset-inline-start: 0;
|
||||||
width: 2px;
|
width: 3px;
|
||||||
background: var(--accent-color);
|
background: var(--bar-gradient, var(--accent-color));
|
||||||
border-radius: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--hover-color);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:active {
|
&:active {
|
||||||
background-color: var(--active-color);
|
background-color: var(--background-active-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-quote {
|
||||||
|
.message-title {
|
||||||
|
padding-inline-end: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-text .embedded-text-wrapper {
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
@include icons.icon;
|
||||||
|
content: map.get(icons.$icons-map, "quote");
|
||||||
|
|
||||||
|
color: var(--accent-color);
|
||||||
|
position: absolute;
|
||||||
|
top: 0.25rem;
|
||||||
|
inset-inline-end: 0.25rem;
|
||||||
|
|
||||||
|
font-size: 0.625rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.with-thumb {
|
||||||
|
.message-title {
|
||||||
|
padding-inline-start: 2.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.embedded-text-wrapper {
|
||||||
|
text-indent: 2.25rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-title {
|
.message-title {
|
||||||
font-size: calc(var(--message-text-size, 1rem) - 0.125rem);
|
font-size: calc(var(--message-text-size, 1rem) - 0.125rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.embedded-more {
|
.embedded-origin-icon {
|
||||||
font-size: 1.125rem;
|
margin-inline: 0.125rem;
|
||||||
margin-inline-end: 0.125rem;
|
vertical-align: middle;
|
||||||
line-height: 0.9375rem;
|
line-height: 1.25;
|
||||||
vertical-align: -0.1875rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.embedded-origin-icon {
|
.embedded-chat-icon {
|
||||||
display: inline-block;
|
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
margin-inline: 0.125rem;
|
vertical-align: middle;
|
||||||
transform: translateY(1px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-text {
|
.message-text {
|
||||||
@ -90,15 +108,20 @@
|
|||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
|
|
||||||
.message-title {
|
.message-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
flex: 1;
|
||||||
|
column-gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-title, .embedded-sender {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
margin-bottom: 0.125rem;
|
|
||||||
flex: 1;
|
|
||||||
display: block;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
p {
|
.embedded-text-wrapper {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@ -139,7 +162,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.embedded-thumb {
|
.embedded-thumb {
|
||||||
position: relative;
|
position: absolute;
|
||||||
|
top: 0.375rem;
|
||||||
|
inset-inline-start: 0.375rem;
|
||||||
width: 2rem;
|
width: 2rem;
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
@ -159,16 +184,13 @@
|
|||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--background-icons {
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
&.inside-input {
|
&.inside-input {
|
||||||
padding-inline-start: 0.5625rem;
|
padding-inline-start: 0.5625rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
--accent-color: var(--color-primary);
|
|
||||||
--hover-color: var(--color-interactive-element-hover);
|
|
||||||
--active-color: var(--color-reply-active);
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
bottom: 0.3125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.embedded-thumb {
|
.embedded-thumb {
|
||||||
margin-left: 0.125rem;
|
margin-left: 0.125rem;
|
||||||
@ -183,11 +205,5 @@
|
|||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--accent-color);
|
color: var(--accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.embedded-more {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
opacity: 0.8;
|
|
||||||
color: var(--color-text-secondary);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
249
src/components/common/embedded/EmbeddedMessage.tsx
Normal file
249
src/components/common/embedded/EmbeddedMessage.tsx
Normal file
@ -0,0 +1,249 @@
|
|||||||
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
|
import React, { useMemo, useRef } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ApiChat,
|
||||||
|
ApiMessage, ApiPeer, ApiReplyInfo,
|
||||||
|
} from '../../../api/types';
|
||||||
|
import type { ChatTranslatedMessages } from '../../../global/types';
|
||||||
|
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||||
|
import type { IconName } from '../../../types/icons';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getMessageIsSpoiler,
|
||||||
|
getMessageMediaHash,
|
||||||
|
getMessageRoundVideo,
|
||||||
|
getSenderTitle,
|
||||||
|
isActionMessage,
|
||||||
|
isChatChannel,
|
||||||
|
isChatGroup,
|
||||||
|
isMessageTranslatable,
|
||||||
|
} from '../../../global/helpers';
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import { getPictogramDimensions } from '../helpers/mediaDimensions';
|
||||||
|
import { getPeerColorClass } from '../helpers/peerColor';
|
||||||
|
import renderText from '../helpers/renderText';
|
||||||
|
import { renderTextWithEntities } from '../helpers/renderTextWithEntities';
|
||||||
|
|
||||||
|
import { useFastClick } from '../../../hooks/useFastClick';
|
||||||
|
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
|
||||||
|
import useLang from '../../../hooks/useLang';
|
||||||
|
import useMedia from '../../../hooks/useMedia';
|
||||||
|
import useThumbnail from '../../../hooks/useThumbnail';
|
||||||
|
import useMessageTranslation from '../../middle/message/hooks/useMessageTranslation';
|
||||||
|
|
||||||
|
import ActionMessage from '../../middle/ActionMessage';
|
||||||
|
import Icon from '../Icon';
|
||||||
|
import MediaSpoiler from '../MediaSpoiler';
|
||||||
|
import MessageSummary from '../MessageSummary';
|
||||||
|
import EmojiIconBackground from './EmojiIconBackground';
|
||||||
|
|
||||||
|
import './EmbeddedMessage.scss';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
className?: string;
|
||||||
|
replyInfo?: ApiReplyInfo;
|
||||||
|
message?: ApiMessage;
|
||||||
|
sender?: ApiPeer;
|
||||||
|
senderChat?: ApiChat;
|
||||||
|
forwardSender?: ApiPeer;
|
||||||
|
title?: string;
|
||||||
|
customText?: string;
|
||||||
|
noUserColors?: boolean;
|
||||||
|
isProtected?: boolean;
|
||||||
|
chatTranslations?: ChatTranslatedMessages;
|
||||||
|
requestedChatTranslationLanguage?: string;
|
||||||
|
observeIntersectionForLoading?: ObserveFn;
|
||||||
|
observeIntersectionForPlaying?: ObserveFn;
|
||||||
|
onClick: NoneToVoidFunction;
|
||||||
|
};
|
||||||
|
|
||||||
|
const NBSP = '\u00A0';
|
||||||
|
|
||||||
|
const EmbeddedMessage: FC<OwnProps> = ({
|
||||||
|
className,
|
||||||
|
message,
|
||||||
|
replyInfo,
|
||||||
|
sender,
|
||||||
|
senderChat,
|
||||||
|
forwardSender,
|
||||||
|
title,
|
||||||
|
customText,
|
||||||
|
isProtected,
|
||||||
|
noUserColors,
|
||||||
|
chatTranslations,
|
||||||
|
requestedChatTranslationLanguage,
|
||||||
|
observeIntersectionForLoading,
|
||||||
|
observeIntersectionForPlaying,
|
||||||
|
onClick,
|
||||||
|
}) => {
|
||||||
|
// eslint-disable-next-line no-null/no-null
|
||||||
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
const isIntersecting = useIsIntersecting(ref, observeIntersectionForLoading);
|
||||||
|
|
||||||
|
const wrappedMedia = useMemo(() => {
|
||||||
|
const replyMedia = replyInfo?.type === 'message' && replyInfo.replyMedia;
|
||||||
|
if (!replyMedia) return undefined;
|
||||||
|
return {
|
||||||
|
content: replyMedia,
|
||||||
|
};
|
||||||
|
}, [replyInfo]);
|
||||||
|
|
||||||
|
const mediaBlobUrl = useMedia(message && getMessageMediaHash(message, 'pictogram'), !isIntersecting);
|
||||||
|
const mediaThumbnail = useThumbnail(message || wrappedMedia);
|
||||||
|
const isRoundVideo = Boolean(message && getMessageRoundVideo(message));
|
||||||
|
const isSpoiler = Boolean(message && getMessageIsSpoiler(message));
|
||||||
|
const isQuote = Boolean(replyInfo?.type === 'message' && replyInfo.isQuote);
|
||||||
|
const replyForwardInfo = replyInfo?.type === 'message' ? replyInfo.replyFrom : undefined;
|
||||||
|
|
||||||
|
const shouldTranslate = message && isMessageTranslatable(message);
|
||||||
|
const { translatedText } = useMessageTranslation(
|
||||||
|
chatTranslations, message?.chatId, shouldTranslate ? message?.id : undefined, requestedChatTranslationLanguage,
|
||||||
|
);
|
||||||
|
|
||||||
|
const lang = useLang();
|
||||||
|
|
||||||
|
const senderTitle = sender ? getSenderTitle(lang, sender)
|
||||||
|
: (replyForwardInfo?.hiddenUserName || message?.forwardInfo?.hiddenUserName);
|
||||||
|
const senderChatTitle = senderChat ? getSenderTitle(lang, senderChat) : message?.forwardInfo?.hiddenUserName;
|
||||||
|
const forwardSenderTitle = forwardSender ? getSenderTitle(lang, forwardSender)
|
||||||
|
: message?.forwardInfo?.hiddenUserName;
|
||||||
|
const areSendersSame = sender?.id === forwardSender?.id;
|
||||||
|
|
||||||
|
const { handleClick, handleMouseDown } = useFastClick(onClick);
|
||||||
|
|
||||||
|
function renderTextContent() {
|
||||||
|
if (replyInfo?.type === 'message' && replyInfo.quoteText) {
|
||||||
|
return renderTextWithEntities({
|
||||||
|
text: replyInfo.quoteText.text,
|
||||||
|
entities: replyInfo.quoteText.entities,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!message) {
|
||||||
|
return customText || NBSP;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isActionMessage(message)) {
|
||||||
|
return (
|
||||||
|
<ActionMessage
|
||||||
|
message={message}
|
||||||
|
isEmbedded
|
||||||
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||||
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MessageSummary
|
||||||
|
lang={lang}
|
||||||
|
message={message}
|
||||||
|
noEmoji={Boolean(mediaThumbnail)}
|
||||||
|
translatedText={translatedText}
|
||||||
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||||
|
observeIntersectionForPlaying={observeIntersectionForPlaying}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderSender() {
|
||||||
|
if (forwardSenderTitle && !areSendersSame) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Icon name={forwardSender ? 'share-filled' : 'forward'} className="embedded-origin-icon" />
|
||||||
|
{renderText(forwardSenderTitle)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (title) {
|
||||||
|
return renderText(title);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!senderTitle) {
|
||||||
|
return NBSP;
|
||||||
|
}
|
||||||
|
|
||||||
|
let shouldIgnoreSender = false;
|
||||||
|
let icon: IconName | undefined;
|
||||||
|
if (senderChat) {
|
||||||
|
if (isChatChannel(senderChat)) {
|
||||||
|
shouldIgnoreSender = true;
|
||||||
|
icon = 'channel-filled';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isChatGroup(senderChat)) {
|
||||||
|
icon = 'group-filled';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!shouldIgnoreSender && <span className="embedded-sender">{renderText(senderTitle)}</span>}
|
||||||
|
{icon && <Icon name={icon} className="embedded-chat-icon" />}
|
||||||
|
{senderChatTitle && renderText(senderChatTitle)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={ref}
|
||||||
|
className={buildClassName(
|
||||||
|
'EmbeddedMessage',
|
||||||
|
className,
|
||||||
|
getPeerColorClass(sender, noUserColors, true),
|
||||||
|
isQuote && 'is-quote',
|
||||||
|
mediaThumbnail && 'with-thumb',
|
||||||
|
)}
|
||||||
|
dir={lang.isRtl ? 'rtl' : undefined}
|
||||||
|
onClick={handleClick}
|
||||||
|
onMouseDown={handleMouseDown}
|
||||||
|
>
|
||||||
|
{mediaThumbnail && renderPictogram(mediaThumbnail, mediaBlobUrl, isRoundVideo, isProtected, isSpoiler)}
|
||||||
|
{sender?.backgroundEmojiId && (
|
||||||
|
<EmojiIconBackground emojiDocumentId={sender.backgroundEmojiId} className="EmbeddedMessage--background-icons" />
|
||||||
|
)}
|
||||||
|
<div className="message-text">
|
||||||
|
<p className="embedded-text-wrapper">
|
||||||
|
{renderTextContent()}
|
||||||
|
</p>
|
||||||
|
<div className="message-title">
|
||||||
|
{renderSender()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function renderPictogram(
|
||||||
|
thumbDataUri: string,
|
||||||
|
blobUrl?: string,
|
||||||
|
isRoundVideo?: boolean,
|
||||||
|
isProtected?: boolean,
|
||||||
|
isSpoiler?: boolean,
|
||||||
|
) {
|
||||||
|
const { width, height } = getPictogramDimensions();
|
||||||
|
|
||||||
|
const srcUrl = blobUrl || thumbDataUri;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={buildClassName('embedded-thumb', isRoundVideo && 'round')}>
|
||||||
|
{!isSpoiler && (
|
||||||
|
<img
|
||||||
|
src={srcUrl}
|
||||||
|
width={width}
|
||||||
|
height={height}
|
||||||
|
alt=""
|
||||||
|
className="pictogram"
|
||||||
|
draggable={false}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<MediaSpoiler thumbDataUri={srcUrl} isVisible={Boolean(isSpoiler)} width={width} height={height} />
|
||||||
|
{isProtected && <span className="protector" />}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default EmbeddedMessage;
|
||||||
@ -1,24 +1,26 @@
|
|||||||
import type { FC } from '../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, { useRef } from '../../lib/teact/teact';
|
import React, { useRef } from '../../../lib/teact/teact';
|
||||||
import { getActions } from '../../global';
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
import type { ApiPeer, ApiTypeStory } from '../../api/types';
|
import type { ApiPeer, ApiTypeStory } from '../../../api/types';
|
||||||
import type { ObserveFn } from '../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../../hooks/useIntersectionObserver';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getPeerColorKey,
|
|
||||||
getSenderTitle,
|
getSenderTitle,
|
||||||
getStoryMediaHash,
|
getStoryMediaHash,
|
||||||
} from '../../global/helpers';
|
} from '../../../global/helpers';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import { getPictogramDimensions } from './helpers/mediaDimensions';
|
import { getPictogramDimensions } from '../helpers/mediaDimensions';
|
||||||
import renderText from './helpers/renderText';
|
import { getPeerColorClass } from '../helpers/peerColor';
|
||||||
|
import renderText from '../helpers/renderText';
|
||||||
|
|
||||||
import { useFastClick } from '../../hooks/useFastClick';
|
import { useFastClick } from '../../../hooks/useFastClick';
|
||||||
import { useIsIntersecting } from '../../hooks/useIntersectionObserver';
|
import { useIsIntersecting } from '../../../hooks/useIntersectionObserver';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useLastCallback from '../../hooks/useLastCallback';
|
import useLastCallback from '../../../hooks/useLastCallback';
|
||||||
import useMedia from '../../hooks/useMedia';
|
import useMedia from '../../../hooks/useMedia';
|
||||||
|
|
||||||
|
import Icon from '../Icon';
|
||||||
|
|
||||||
import './EmbeddedMessage.scss';
|
import './EmbeddedMessage.scss';
|
||||||
|
|
||||||
@ -75,23 +77,24 @@ const EmbeddedStory: FC<OwnProps> = ({
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
className={buildClassName(
|
className={buildClassName(
|
||||||
'EmbeddedMessage',
|
'EmbeddedMessage',
|
||||||
sender && !noUserColors && `color-${getPeerColorKey(sender)}`,
|
getPeerColorClass(sender, noUserColors, true),
|
||||||
|
pictogramUrl && 'with-thumb',
|
||||||
)}
|
)}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
onMouseDown={handleMouseDown}
|
onMouseDown={handleMouseDown}
|
||||||
>
|
>
|
||||||
{pictogramUrl && renderPictogram(pictogramUrl, isProtected)}
|
{pictogramUrl && renderPictogram(pictogramUrl, isProtected)}
|
||||||
<div className="message-text with-message-color">
|
<div className="message-text with-message-color">
|
||||||
<p dir="auto">
|
<p className="embedded-text-wrapper">
|
||||||
{isExpiredStory && (
|
{isExpiredStory && (
|
||||||
<i className="icon icon-story-expired" aria-hidden />
|
<Icon name="story-expired" className="embedded-origin-icon" />
|
||||||
)}
|
)}
|
||||||
{isFullStory && (
|
{isFullStory && (
|
||||||
<i className="icon icon-story-reply" aria-hidden />
|
<Icon name="story-reply" className="embedded-origin-icon" />
|
||||||
)}
|
)}
|
||||||
{lang(title)}
|
{lang(title)}
|
||||||
</p>
|
</p>
|
||||||
<div className="message-title" dir="auto">{renderText(senderTitle || NBSP)}</div>
|
<div className="message-title">{renderText(senderTitle || NBSP)}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
.root {
|
||||||
|
--custom-emoji-border-radius: 0.25rem;
|
||||||
|
--custom-emoji-size: 1.25rem;
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
86
src/components/common/embedded/EmojiIconBackground.tsx
Normal file
86
src/components/common/embedded/EmojiIconBackground.tsx
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
import React, { memo } from '../../../lib/teact/teact';
|
||||||
|
|
||||||
|
import buildClassName from '../../../util/buildClassName';
|
||||||
|
import buildStyle from '../../../util/buildStyle';
|
||||||
|
|
||||||
|
import CustomEmoji from '../CustomEmoji';
|
||||||
|
|
||||||
|
import styles from './EmojiIconBackground.module.scss';
|
||||||
|
|
||||||
|
type IconPosition = {
|
||||||
|
inline: number;
|
||||||
|
block: number;
|
||||||
|
opacity: number;
|
||||||
|
scale: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ICON_POSITIONS: IconPosition[] = [
|
||||||
|
{
|
||||||
|
inline: 5, block: 15, opacity: 0.35, scale: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inline: 10, block: 45, opacity: 0.3, scale: 0.9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inline: 20, block: 75, opacity: 0.3, scale: 0.75,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inline: 40, block: 20, opacity: 0.25, scale: 0.8,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inline: 60, block: 50, opacity: 0.25, scale: 0.85,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inline: 55, block: -5, opacity: 0.20, scale: 0.75,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inline: 80, block: 15, opacity: 0.15, scale: 0.95,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inline: 100, block: 70, opacity: 0.15, scale: 0.9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inline: 120, block: 25, opacity: 0.10, scale: 0.65,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
inline: 140, block: 0, opacity: 0.10, scale: 0.75,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
emojiDocumentId: string;
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const EmojiIconBackground = ({
|
||||||
|
emojiDocumentId,
|
||||||
|
className,
|
||||||
|
}: OwnProps) => {
|
||||||
|
return (
|
||||||
|
<div className={buildClassName(styles.root, className)}>
|
||||||
|
{ICON_POSITIONS.map((position) => {
|
||||||
|
const {
|
||||||
|
inline, block, opacity, scale,
|
||||||
|
} = position;
|
||||||
|
|
||||||
|
const style = buildStyle(
|
||||||
|
`inset-inline-end: ${inline}px`,
|
||||||
|
`inset-block-start: ${block}px`,
|
||||||
|
`opacity: ${opacity}`,
|
||||||
|
`transform: scale(${scale})`,
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CustomEmoji
|
||||||
|
documentId={emojiDocumentId}
|
||||||
|
className={styles.emoji}
|
||||||
|
noPlay
|
||||||
|
style={style}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(EmojiIconBackground);
|
||||||
11
src/components/common/helpers/peerColor.ts
Normal file
11
src/components/common/helpers/peerColor.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import type { ApiPeer } from '../../../api/types';
|
||||||
|
|
||||||
|
import { getPeerColorCount, getPeerColorKey } from '../../../global/helpers';
|
||||||
|
|
||||||
|
export function getPeerColorClass(peer?: ApiPeer, noUserColors?: boolean, shouldReset?: boolean) {
|
||||||
|
if (!peer) {
|
||||||
|
if (!shouldReset) return undefined;
|
||||||
|
return noUserColors ? 'peer-color-count-0' : 'peer-color-0';
|
||||||
|
}
|
||||||
|
return noUserColors ? `peer-color-count-${getPeerColorCount(peer)}` : `peer-color-${getPeerColorKey(peer)}`;
|
||||||
|
}
|
||||||
@ -391,7 +391,13 @@ function processEntity({
|
|||||||
case ApiMessageEntityTypes.Bold:
|
case ApiMessageEntityTypes.Bold:
|
||||||
return <strong data-entity-type={entity.type}>{renderNestedMessagePart()}</strong>;
|
return <strong data-entity-type={entity.type}>{renderNestedMessagePart()}</strong>;
|
||||||
case ApiMessageEntityTypes.Blockquote:
|
case ApiMessageEntityTypes.Blockquote:
|
||||||
return <blockquote data-entity-type={entity.type}>{renderNestedMessagePart()}</blockquote>;
|
return (
|
||||||
|
<div className="message-entity-blockquote-wrapper">
|
||||||
|
<blockquote data-entity-type={entity.type}>
|
||||||
|
{renderNestedMessagePart()}
|
||||||
|
</blockquote>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
case ApiMessageEntityTypes.BotCommand:
|
case ApiMessageEntityTypes.BotCommand:
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { getActions, withGlobal } from '../../../global';
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiChat,
|
ApiChat,
|
||||||
ApiFormattedText,
|
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiMessageOutgoingStatus,
|
ApiMessageOutgoingStatus,
|
||||||
ApiPeer,
|
ApiPeer,
|
||||||
@ -13,6 +12,7 @@ import type {
|
|||||||
ApiUser,
|
ApiUser,
|
||||||
ApiUserStatus,
|
ApiUserStatus,
|
||||||
} from '../../../api/types';
|
} from '../../../api/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 { MAIN_THREAD_ID } from '../../../api/types';
|
import { MAIN_THREAD_ID } from '../../../api/types';
|
||||||
@ -25,6 +25,7 @@ import {
|
|||||||
isUserOnline,
|
isUserOnline,
|
||||||
selectIsChatMuted,
|
selectIsChatMuted,
|
||||||
} from '../../../global/helpers';
|
} from '../../../global/helpers';
|
||||||
|
import { getMessageReplyInfo } from '../../../global/helpers/replies';
|
||||||
import {
|
import {
|
||||||
selectCanAnimateInterface,
|
selectCanAnimateInterface,
|
||||||
selectChat,
|
selectChat,
|
||||||
@ -89,7 +90,7 @@ type StateProps = {
|
|||||||
actionTargetChatId?: string;
|
actionTargetChatId?: string;
|
||||||
lastMessageSender?: ApiPeer;
|
lastMessageSender?: ApiPeer;
|
||||||
lastMessageOutgoingStatus?: ApiMessageOutgoingStatus;
|
lastMessageOutgoingStatus?: ApiMessageOutgoingStatus;
|
||||||
draft?: ApiFormattedText;
|
draft?: ApiDraft;
|
||||||
isSelected?: boolean;
|
isSelected?: boolean;
|
||||||
isSelectedForum?: boolean;
|
isSelectedForum?: boolean;
|
||||||
isForumPanelOpen?: boolean;
|
isForumPanelOpen?: boolean;
|
||||||
@ -344,10 +345,12 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const { senderId, replyToMessageId, isOutgoing } = chat.lastMessage || {};
|
const { lastMessage } = chat;
|
||||||
|
const { senderId, isOutgoing } = lastMessage || {};
|
||||||
|
const replyToMessageId = lastMessage && getMessageReplyInfo(lastMessage)?.replyToMsgId;
|
||||||
const lastMessageSender = senderId
|
const lastMessageSender = senderId
|
||||||
? (selectUser(global, senderId) || selectChat(global, senderId)) : undefined;
|
? (selectUser(global, senderId) || selectChat(global, senderId)) : undefined;
|
||||||
const lastMessageAction = chat.lastMessage ? getMessageAction(chat.lastMessage) : undefined;
|
const lastMessageAction = lastMessage ? getMessageAction(lastMessage) : undefined;
|
||||||
const actionTargetMessage = lastMessageAction && replyToMessageId
|
const actionTargetMessage = lastMessageAction && replyToMessageId
|
||||||
? selectChatMessage(global, chat.id, replyToMessageId)
|
? selectChatMessage(global, chat.id, replyToMessageId)
|
||||||
: undefined;
|
: undefined;
|
||||||
@ -364,7 +367,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
const user = privateChatUserId ? selectUser(global, privateChatUserId) : undefined;
|
const user = privateChatUserId ? selectUser(global, privateChatUserId) : undefined;
|
||||||
const userStatus = privateChatUserId ? selectUserStatus(global, privateChatUserId) : undefined;
|
const userStatus = privateChatUserId ? selectUserStatus(global, privateChatUserId) : undefined;
|
||||||
const lastMessageTopic = chat.lastMessage && selectTopicFromMessage(global, chat.lastMessage);
|
const lastMessageTopic = lastMessage && selectTopicFromMessage(global, lastMessage);
|
||||||
|
|
||||||
const typingStatus = selectThreadParam(global, chatId, MAIN_THREAD_ID, 'typingStatus');
|
const typingStatus = selectThreadParam(global, chatId, MAIN_THREAD_ID, 'typingStatus');
|
||||||
|
|
||||||
@ -381,8 +384,8 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isForumPanelOpen: selectIsForumPanelOpen(global),
|
isForumPanelOpen: selectIsForumPanelOpen(global),
|
||||||
canScrollDown: isSelected && messageListType === 'thread',
|
canScrollDown: isSelected && messageListType === 'thread',
|
||||||
canChangeFolder: (global.chatFolders.orderedIds?.length || 0) > 1,
|
canChangeFolder: (global.chatFolders.orderedIds?.length || 0) > 1,
|
||||||
...(isOutgoing && chat.lastMessage && {
|
...(isOutgoing && lastMessage && {
|
||||||
lastMessageOutgoingStatus: selectOutgoingStatus(global, chat.lastMessage),
|
lastMessageOutgoingStatus: selectOutgoingStatus(global, lastMessage),
|
||||||
}),
|
}),
|
||||||
user,
|
user,
|
||||||
userStatus,
|
userStatus,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import "../../../styles/mixins";
|
@use "../../../styles/mixins";
|
||||||
|
|
||||||
#LeftMainHeader {
|
#LeftMainHeader {
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -123,7 +123,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @optimization
|
// @optimization
|
||||||
@include while-transition() {
|
@include mixins.while-transition() {
|
||||||
.Menu .bubble {
|
.Menu .bubble {
|
||||||
transition: none !important;
|
transition: none !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -158,6 +158,7 @@ const LeftSideMenuItems = ({
|
|||||||
bot={bot}
|
bot={bot}
|
||||||
theme={theme}
|
theme={theme}
|
||||||
isInSideMenu
|
isInSideMenu
|
||||||
|
canShowNew
|
||||||
onMenuOpened={onBotMenuOpened}
|
onMenuOpened={onBotMenuOpened}
|
||||||
onMenuClosed={onBotMenuClosed}
|
onMenuClosed={onBotMenuClosed}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@ -3,13 +3,15 @@ import React, { memo } from '../../../lib/teact/teact';
|
|||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiChat, ApiFormattedText, ApiMessage, ApiMessageOutgoingStatus,
|
ApiChat, ApiMessage, ApiMessageOutgoingStatus,
|
||||||
ApiPeer, ApiTopic, ApiTypingStatus,
|
ApiPeer, ApiTopic, ApiTypingStatus,
|
||||||
} from '../../../api/types';
|
} from '../../../api/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 } from '../../../global/helpers';
|
||||||
|
import { getMessageReplyInfo } from '../../../global/helpers/replies';
|
||||||
import {
|
import {
|
||||||
selectCanAnimateInterface,
|
selectCanAnimateInterface,
|
||||||
selectCanDeleteTopic,
|
selectCanDeleteTopic,
|
||||||
@ -48,7 +50,6 @@ type OwnProps = {
|
|||||||
isSelected: boolean;
|
isSelected: boolean;
|
||||||
style: string;
|
style: string;
|
||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
|
|
||||||
orderDiff: number;
|
orderDiff: number;
|
||||||
animationType: ChatAnimationTypes;
|
animationType: ChatAnimationTypes;
|
||||||
};
|
};
|
||||||
@ -63,7 +64,7 @@ type StateProps = {
|
|||||||
lastMessageSender?: ApiPeer;
|
lastMessageSender?: ApiPeer;
|
||||||
actionTargetChatId?: string;
|
actionTargetChatId?: string;
|
||||||
typingStatus?: ApiTypingStatus;
|
typingStatus?: ApiTypingStatus;
|
||||||
draft?: ApiFormattedText;
|
draft?: ApiDraft;
|
||||||
canScrollDown?: boolean;
|
canScrollDown?: boolean;
|
||||||
wasTopicOpened?: boolean;
|
wasTopicOpened?: boolean;
|
||||||
withInterfaceAnimations?: boolean;
|
withInterfaceAnimations?: boolean;
|
||||||
@ -232,8 +233,9 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
(global, { chatId, topic, isSelected }) => {
|
(global, { chatId, topic, isSelected }) => {
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
|
|
||||||
const lastMessage = selectChatMessage(global, chatId, topic.lastMessageId)!;
|
const lastMessage = selectChatMessage(global, chatId, topic.lastMessageId);
|
||||||
const { senderId, replyToMessageId, isOutgoing } = lastMessage || {};
|
const { senderId, isOutgoing } = lastMessage || {};
|
||||||
|
const replyToMessageId = lastMessage && getMessageReplyInfo(lastMessage)?.replyToMsgId;
|
||||||
const lastMessageSender = senderId
|
const lastMessageSender = senderId
|
||||||
? (selectUser(global, senderId) || selectChat(global, senderId)) : undefined;
|
? (selectUser(global, senderId) || selectChat(global, senderId)) : undefined;
|
||||||
const lastMessageAction = lastMessage ? getMessageAction(lastMessage) : undefined;
|
const lastMessageAction = lastMessage ? getMessageAction(lastMessage) : undefined;
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import { getGlobal } from '../../../../global';
|
|||||||
import type {
|
import type {
|
||||||
ApiChat, ApiMessage, ApiPeer, ApiTopic, ApiTypingStatus, ApiUser,
|
ApiChat, ApiMessage, ApiPeer, ApiTopic, ApiTypingStatus, ApiUser,
|
||||||
} from '../../../../api/types';
|
} from '../../../../api/types';
|
||||||
import type { Thread } from '../../../../global/types';
|
import type { ApiDraft } from '../../../../global/types';
|
||||||
import type { ObserveFn } from '../../../../hooks/useIntersectionObserver';
|
import type { ObserveFn } from '../../../../hooks/useIntersectionObserver';
|
||||||
import type { LangFn } from '../../../../hooks/useLang';
|
import type { LangFn } from '../../../../hooks/useLang';
|
||||||
|
|
||||||
@ -23,6 +23,7 @@ import {
|
|||||||
isActionMessage,
|
isActionMessage,
|
||||||
isChatChannel,
|
isChatChannel,
|
||||||
} from '../../../../global/helpers';
|
} from '../../../../global/helpers';
|
||||||
|
import { getMessageReplyInfo } from '../../../../global/helpers/replies';
|
||||||
import buildClassName from '../../../../util/buildClassName';
|
import buildClassName from '../../../../util/buildClassName';
|
||||||
import { renderActionMessageText } from '../../../common/helpers/renderActionMessageText';
|
import { renderActionMessageText } from '../../../common/helpers/renderActionMessageText';
|
||||||
import renderText from '../../../common/helpers/renderText';
|
import renderText from '../../../common/helpers/renderText';
|
||||||
@ -60,7 +61,7 @@ export default function useChatListEntry({
|
|||||||
lastMessage?: ApiMessage;
|
lastMessage?: ApiMessage;
|
||||||
chatId: string;
|
chatId: string;
|
||||||
typingStatus?: ApiTypingStatus;
|
typingStatus?: ApiTypingStatus;
|
||||||
draft?: Thread['draft'];
|
draft?: ApiDraft;
|
||||||
actionTargetMessage?: ApiMessage;
|
actionTargetMessage?: ApiMessage;
|
||||||
actionTargetUserIds?: string[];
|
actionTargetUserIds?: string[];
|
||||||
lastMessageTopic?: ApiTopic;
|
lastMessageTopic?: ApiTopic;
|
||||||
@ -79,7 +80,8 @@ export default function useChatListEntry({
|
|||||||
|
|
||||||
const isAction = lastMessage && isActionMessage(lastMessage);
|
const isAction = lastMessage && isActionMessage(lastMessage);
|
||||||
|
|
||||||
useEnsureMessage(chatId, isAction ? lastMessage.replyToMessageId : undefined, actionTargetMessage);
|
const replyToMessageId = lastMessage && getMessageReplyInfo(lastMessage)?.replyToMsgId;
|
||||||
|
useEnsureMessage(chatId, isAction ? replyToMessageId : undefined, actionTargetMessage);
|
||||||
|
|
||||||
const mediaThumbnail = lastMessage && !getMessageSticker(lastMessage)
|
const mediaThumbnail = lastMessage && !getMessageSticker(lastMessage)
|
||||||
? getMessageMediaThumbDataUri(lastMessage)
|
? getMessageMediaThumbDataUri(lastMessage)
|
||||||
@ -102,13 +104,15 @@ export default function useChatListEntry({
|
|||||||
return <TypingStatus typingStatus={typingStatus} />;
|
return <TypingStatus typingStatus={typingStatus} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (draft?.text.length && (!chat?.isForum || isTopic)) {
|
const isDraftReplyToTopic = draft && draft.replyInfo?.replyToMsgId === lastMessageTopic?.id;
|
||||||
|
|
||||||
|
if (draft && (!chat?.isForum || (isTopic && !isDraftReplyToTopic))) {
|
||||||
return (
|
return (
|
||||||
<p className="last-message" dir={lang.isRtl ? 'auto' : 'ltr'}>
|
<p className="last-message" dir={lang.isRtl ? 'auto' : 'ltr'}>
|
||||||
<span className="draft">{lang('Draft')}</span>
|
<span className="draft">{lang('Draft')}</span>
|
||||||
{renderTextWithEntities({
|
{renderTextWithEntities({
|
||||||
text: draft.text,
|
text: draft.text?.text || '',
|
||||||
entities: draft.entities,
|
entities: draft.text?.entities,
|
||||||
isSimple: true,
|
isSimple: true,
|
||||||
withTranslucentThumbs: true,
|
withTranslucentThumbs: true,
|
||||||
})}
|
})}
|
||||||
@ -153,7 +157,7 @@ export default function useChatListEntry({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{lastMessage.forwardInfo && (<i className="icon icon-share-filled chat-prefix-icon" />)}
|
{lastMessage.forwardInfo && (<i className="icon icon-share-filled chat-prefix-icon" />)}
|
||||||
{Boolean(lastMessage.replyToStoryId) && (<i className="icon icon-story-reply chat-prefix-icon" />)}
|
{lastMessage.replyInfo?.type === 'story' && (<i className="icon icon-story-reply chat-prefix-icon" />)}
|
||||||
{renderSummary(lang, lastMessage, observeIntersection, mediaBlobUrl || mediaThumbnail, isRoundVideo)}
|
{renderSummary(lang, lastMessage, observeIntersection, mediaBlobUrl || mediaThumbnail, isRoundVideo)}
|
||||||
</p>
|
</p>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import "../../../styles/mixins";
|
@use "../../../styles/mixins";
|
||||||
|
|
||||||
#Settings {
|
#Settings {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -82,7 +82,7 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
margin-bottom: 0.625rem;
|
margin-bottom: 0.625rem;
|
||||||
|
|
||||||
@include side-panel-section;
|
@include mixins.side-panel-section;
|
||||||
|
|
||||||
&.no-border {
|
&.no-border {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
@ -109,12 +109,12 @@
|
|||||||
|
|
||||||
.settings-main-menu {
|
.settings-main-menu {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
@include side-panel-section;
|
@include mixins.side-panel-section;
|
||||||
|
|
||||||
> .ChatExtra {
|
> .ChatExtra {
|
||||||
padding: 0 0.5rem 0.3125rem;
|
padding: 0 0.5rem 0.3125rem;
|
||||||
margin: 0 -0.5rem 0.625rem;
|
margin: 0 -0.5rem 0.625rem;
|
||||||
@include side-panel-section;
|
@include mixins.side-panel-section;
|
||||||
|
|
||||||
.ListItem.narrow {
|
.ListItem.narrow {
|
||||||
margin-bottom: 0.25rem;
|
margin-bottom: 0.25rem;
|
||||||
@ -125,7 +125,7 @@
|
|||||||
.settings-item-simple,
|
.settings-item-simple,
|
||||||
.settings-item {
|
.settings-item {
|
||||||
padding: 1.5rem 1.5rem 1rem;
|
padding: 1.5rem 1.5rem 1rem;
|
||||||
@include side-panel-section;
|
@include mixins.side-panel-section;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-item {
|
.settings-item {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import "../../../styles/mixins";
|
@use "../../../styles/mixins";
|
||||||
|
|
||||||
.SettingsGeneralBackground {
|
.SettingsGeneralBackground {
|
||||||
.settings-wallpapers {
|
.settings-wallpapers {
|
||||||
@ -6,7 +6,7 @@
|
|||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
grid-auto-rows: 1fr;
|
grid-auto-rows: 1fr;
|
||||||
grid-gap: 0.0625rem;
|
grid-gap: 0.0625rem;
|
||||||
@include side-panel-section;
|
@include mixins.side-panel-section;
|
||||||
}
|
}
|
||||||
|
|
||||||
.Loading {
|
.Loading {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import "../../../styles/mixins";
|
@use "../../../styles/mixins";
|
||||||
|
|
||||||
.SettingsGeneralBackgroundColor {
|
.SettingsGeneralBackgroundColor {
|
||||||
&:not(.is-dragging) .handle {
|
&:not(.is-dragging) .handle {
|
||||||
@ -71,7 +71,7 @@
|
|||||||
grid-template-columns: repeat(3, 1fr);
|
grid-template-columns: repeat(3, 1fr);
|
||||||
grid-auto-rows: 1fr;
|
grid-auto-rows: 1fr;
|
||||||
grid-gap: 0.0625rem;
|
grid-gap: 0.0625rem;
|
||||||
@include side-panel-section;
|
@include mixins.side-panel-section;
|
||||||
}
|
}
|
||||||
|
|
||||||
.predefined-color {
|
.predefined-color {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import '../../../styles/mixins';
|
@use '../../../styles/mixins';
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
--premium-gradient: linear-gradient(88.39deg, #6C93FF -2.56%, #976FFF 51.27%, #DF69D1 107.39%);
|
--premium-gradient: linear-gradient(88.39deg, #6C93FF -2.56%, #976FFF 51.27%, #DF69D1 107.39%);
|
||||||
@ -39,7 +39,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
@include adapt-padding-to-scrollbar(0.5rem);
|
@include mixins.adapt-padding-to-scrollbar(0.5rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import type { FocusDirection } from '../../types';
|
|||||||
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
|
import type { PinnedIntersectionChangedCallback } from './hooks/usePinnedMessage';
|
||||||
|
|
||||||
import { getMessageHtmlId, isChatChannel } from '../../global/helpers';
|
import { getMessageHtmlId, isChatChannel } from '../../global/helpers';
|
||||||
|
import { getMessageReplyInfo } from '../../global/helpers/replies';
|
||||||
import {
|
import {
|
||||||
selectCanPlayAnimatedEmojis,
|
selectCanPlayAnimatedEmojis,
|
||||||
selectChat,
|
selectChat,
|
||||||
@ -102,7 +103,11 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useOnIntersect(ref, observeIntersectionForReading);
|
useOnIntersect(ref, observeIntersectionForReading);
|
||||||
useEnsureMessage(message.chatId, message.replyToMessageId, targetMessage);
|
useEnsureMessage(
|
||||||
|
message.chatId,
|
||||||
|
message.replyInfo?.type === 'message' ? message.replyInfo.replyToMsgId : undefined,
|
||||||
|
targetMessage,
|
||||||
|
);
|
||||||
useFocusMessage(ref, message.chatId, isFocused, focusDirection, noFocusHighlight, isJustAdded);
|
useFocusMessage(ref, message.chatId, isFocused, focusDirection, noFocusHighlight, isJustAdded);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -263,12 +268,12 @@ const ActionMessage: FC<OwnProps & StateProps> = ({
|
|||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { message, threadId }): StateProps => {
|
(global, { message, threadId }): StateProps => {
|
||||||
const {
|
const {
|
||||||
chatId, senderId, replyToMessageId, content,
|
chatId, senderId, content,
|
||||||
} = message;
|
} = message;
|
||||||
|
|
||||||
const userId = senderId;
|
const userId = senderId;
|
||||||
const { targetUserIds, targetChatId } = content.action || {};
|
const { targetUserIds, targetChatId } = content.action || {};
|
||||||
const targetMessageId = replyToMessageId;
|
const targetMessageId = getMessageReplyInfo(message)?.replyToMsgId;
|
||||||
const targetMessage = targetMessageId
|
const targetMessage = targetMessageId
|
||||||
? selectChatMessage(global, chatId, targetMessageId)
|
? selectChatMessage(global, chatId, targetMessageId)
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import "../../styles/mixins";
|
@use "../../styles/mixins";
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -247,7 +247,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
@include header-mobile();
|
@include mixins.header-mobile();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import React, {
|
|||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../global';
|
import { getActions, withGlobal } from '../../global';
|
||||||
|
|
||||||
import type { ApiChat, ApiChatBannedRights } from '../../api/types';
|
import type { ApiChat, ApiChatBannedRights, ApiInputMessageReplyInfo } from '../../api/types';
|
||||||
import type {
|
import type {
|
||||||
ActiveEmojiInteraction,
|
ActiveEmojiInteraction,
|
||||||
MessageListType,
|
MessageListType,
|
||||||
@ -44,12 +44,12 @@ import {
|
|||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectCurrentTextSearch,
|
selectCurrentTextSearch,
|
||||||
|
selectDraft,
|
||||||
selectIsChatBotNotStarted,
|
selectIsChatBotNotStarted,
|
||||||
selectIsInSelectMode,
|
selectIsInSelectMode,
|
||||||
selectIsRightColumnShown,
|
selectIsRightColumnShown,
|
||||||
selectIsUserBlocked,
|
selectIsUserBlocked,
|
||||||
selectPinnedIds,
|
selectPinnedIds,
|
||||||
selectReplyingToId,
|
|
||||||
selectTabState,
|
selectTabState,
|
||||||
selectTheme,
|
selectTheme,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
@ -105,7 +105,7 @@ type StateProps = {
|
|||||||
threadId?: number;
|
threadId?: number;
|
||||||
messageListType?: MessageListType;
|
messageListType?: MessageListType;
|
||||||
chat?: ApiChat;
|
chat?: ApiChat;
|
||||||
replyingToId?: number;
|
draftReplyInfo?: ApiInputMessageReplyInfo;
|
||||||
isPrivate?: boolean;
|
isPrivate?: boolean;
|
||||||
isPinnedMessageList?: boolean;
|
isPinnedMessageList?: boolean;
|
||||||
canPost?: boolean;
|
canPost?: boolean;
|
||||||
@ -160,7 +160,7 @@ function MiddleColumn({
|
|||||||
messageListType,
|
messageListType,
|
||||||
isMobile,
|
isMobile,
|
||||||
chat,
|
chat,
|
||||||
replyingToId,
|
draftReplyInfo,
|
||||||
isPrivate,
|
isPrivate,
|
||||||
isPinnedMessageList,
|
isPinnedMessageList,
|
||||||
canPost,
|
canPost,
|
||||||
@ -433,7 +433,7 @@ function MiddleColumn({
|
|||||||
const messageSendingRestrictionReason = getMessageSendingRestrictionReason(
|
const messageSendingRestrictionReason = getMessageSendingRestrictionReason(
|
||||||
lang, currentUserBannedRights, defaultBannedRights,
|
lang, currentUserBannedRights, defaultBannedRights,
|
||||||
);
|
);
|
||||||
const forumComposerPlaceholder = getForumComposerPlaceholder(lang, chat, threadId, Boolean(replyingToId));
|
const forumComposerPlaceholder = getForumComposerPlaceholder(lang, chat, threadId, Boolean(draftReplyInfo));
|
||||||
|
|
||||||
const composerRestrictionMessage = messageSendingRestrictionReason || forumComposerPlaceholder;
|
const composerRestrictionMessage = messageSendingRestrictionReason || forumComposerPlaceholder;
|
||||||
|
|
||||||
@ -752,9 +752,9 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const shouldLoadFullChat = Boolean(
|
const shouldLoadFullChat = Boolean(
|
||||||
chat && isChatGroup(chat) && !selectChatFullInfo(global, chat.id),
|
chat && isChatGroup(chat) && !selectChatFullInfo(global, chat.id),
|
||||||
);
|
);
|
||||||
const replyingToId = selectReplyingToId(global, chatId, threadId);
|
const draftReplyInfo = selectDraft(global, chatId, threadId)?.replyInfo;
|
||||||
const shouldBlockSendInForum = chat?.isForum
|
const shouldBlockSendInForum = chat?.isForum
|
||||||
? threadId === MAIN_THREAD_ID && !replyingToId && (chat.topics?.[GENERAL_TOPIC_ID]?.isClosed)
|
? threadId === MAIN_THREAD_ID && !draftReplyInfo && (chat.topics?.[GENERAL_TOPIC_ID]?.isClosed)
|
||||||
: false;
|
: false;
|
||||||
const audioMessage = audioChatId && audioMessageId
|
const audioMessage = audioChatId && audioMessageId
|
||||||
? selectChatMessage(global, audioChatId, audioMessageId)
|
? selectChatMessage(global, audioChatId, audioMessageId)
|
||||||
@ -776,7 +776,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
threadId,
|
threadId,
|
||||||
messageListType,
|
messageListType,
|
||||||
chat,
|
chat,
|
||||||
replyingToId,
|
draftReplyInfo,
|
||||||
isPrivate,
|
isPrivate,
|
||||||
areChatSettingsLoaded: Boolean(chat?.settings),
|
areChatSettingsLoaded: Boolean(chat?.settings),
|
||||||
canPost: !isPinnedMessageList
|
canPost: !isPinnedMessageList
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
@import "../../styles/mixins";
|
@use "../../styles/mixins";
|
||||||
|
|
||||||
@mixin mobile-header-styles() {
|
@mixin mobile-header-styles() {
|
||||||
.AudioPlayer {
|
.AudioPlayer {
|
||||||
@include header-mobile;
|
@include mixins.header-mobile;
|
||||||
|
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
|||||||
@ -21,6 +21,7 @@ type OwnProps = {
|
|||||||
isInSideMenu?: true;
|
isInSideMenu?: true;
|
||||||
chatId?: string;
|
chatId?: string;
|
||||||
threadId?: number;
|
threadId?: number;
|
||||||
|
canShowNew?: boolean;
|
||||||
onMenuOpened: VoidFunction;
|
onMenuOpened: VoidFunction;
|
||||||
onMenuClosed: VoidFunction;
|
onMenuClosed: VoidFunction;
|
||||||
};
|
};
|
||||||
@ -31,6 +32,7 @@ const AttachBotItem: FC<OwnProps> = ({
|
|||||||
chatId,
|
chatId,
|
||||||
threadId,
|
threadId,
|
||||||
isInSideMenu,
|
isInSideMenu,
|
||||||
|
canShowNew,
|
||||||
onMenuOpened,
|
onMenuOpened,
|
||||||
onMenuClosed,
|
onMenuClosed,
|
||||||
}) => {
|
}) => {
|
||||||
@ -93,6 +95,7 @@ const AttachBotItem: FC<OwnProps> = ({
|
|||||||
onContextMenu={handleContextMenu}
|
onContextMenu={handleContextMenu}
|
||||||
>
|
>
|
||||||
{bot.shortName}
|
{bot.shortName}
|
||||||
|
{canShowNew && bot.isDisclaimerNeeded && <span className="menu-item-badge">{lang('New')}</span>}
|
||||||
{menuPosition && (
|
{menuPosition && (
|
||||||
<Menu
|
<Menu
|
||||||
isOpen={isMenuOpen}
|
isOpen={isMenuOpen}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import "../../../styles/mixins";
|
@use "../../../styles/mixins";
|
||||||
|
|
||||||
.BotKeyboardMenu {
|
.BotKeyboardMenu {
|
||||||
.bubble {
|
.bubble {
|
||||||
@ -13,7 +13,7 @@
|
|||||||
padding: 0.1875rem 0.625rem;
|
padding: 0.1875rem 0.625rem;
|
||||||
max-height: 75vh;
|
max-height: 75vh;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
@include adapt-padding-to-scrollbar(0.625rem);
|
@include mixins.adapt-padding-to-scrollbar(0.625rem);
|
||||||
|
|
||||||
.row {
|
.row {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
.ComposerEmbeddedMessage {
|
.ComposerEmbeddedMessage {
|
||||||
|
--accent-color: var(--color-primary);
|
||||||
height: 2.625rem;
|
height: 2.625rem;
|
||||||
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
/* stylelint-disable-next-line plugin/no-low-performance-animation-properties */
|
||||||
transition: height 150ms ease-out, opacity 150ms ease-out;
|
transition: height 150ms ease-out, opacity 150ms ease-out;
|
||||||
@ -32,7 +33,7 @@
|
|||||||
display: grid;
|
display: grid;
|
||||||
place-content: center;
|
place-content: center;
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
color: var(--color-primary);
|
color: var(--accent-color);
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
width: 2.875rem;
|
width: 2.875rem;
|
||||||
@ -47,6 +48,7 @@
|
|||||||
margin: 0 -0.0625rem 0 0.75rem;
|
margin: 0 -0.0625rem 0 0.75rem;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
align-self: center;
|
align-self: center;
|
||||||
|
color: var(--accent-color, var(--color-primary));
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
width: 1.75rem;
|
width: 1.75rem;
|
||||||
|
|||||||
@ -4,13 +4,14 @@ import React, {
|
|||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiMessage, ApiPeer } from '../../../api/types';
|
import type { ApiInputMessageReplyInfo, ApiMessage, ApiPeer } from '../../../api/types';
|
||||||
|
|
||||||
import { stripCustomEmoji } from '../../../global/helpers';
|
import { stripCustomEmoji } from '../../../global/helpers';
|
||||||
import {
|
import {
|
||||||
selectCanAnimateInterface,
|
selectCanAnimateInterface,
|
||||||
selectChatMessage,
|
selectChatMessage,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
|
selectDraft,
|
||||||
selectEditingId,
|
selectEditingId,
|
||||||
selectEditingMessage,
|
selectEditingMessage,
|
||||||
selectEditingScheduledId,
|
selectEditingScheduledId,
|
||||||
@ -18,12 +19,12 @@ import {
|
|||||||
selectIsChatWithSelf,
|
selectIsChatWithSelf,
|
||||||
selectIsCurrentUserPremium,
|
selectIsCurrentUserPremium,
|
||||||
selectPeer,
|
selectPeer,
|
||||||
selectReplyingToId,
|
|
||||||
selectSender,
|
selectSender,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
} from '../../../global/selectors';
|
} from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
import captureEscKeyListener from '../../../util/captureEscKeyListener';
|
||||||
|
import { getPeerColorClass } from '../../common/helpers/peerColor';
|
||||||
|
|
||||||
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
import useContextMenuHandlers from '../../../hooks/useContextMenuHandlers';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
@ -32,7 +33,7 @@ import useMenuPosition from '../../../hooks/useMenuPosition';
|
|||||||
import useShowTransition from '../../../hooks/useShowTransition';
|
import useShowTransition from '../../../hooks/useShowTransition';
|
||||||
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
import useAsyncRendering from '../../right/hooks/useAsyncRendering';
|
||||||
|
|
||||||
import EmbeddedMessage from '../../common/EmbeddedMessage';
|
import EmbeddedMessage from '../../common/embedded/EmbeddedMessage';
|
||||||
import Button from '../../ui/Button';
|
import Button from '../../ui/Button';
|
||||||
import Menu from '../../ui/Menu';
|
import Menu from '../../ui/Menu';
|
||||||
import MenuItem from '../../ui/MenuItem';
|
import MenuItem from '../../ui/MenuItem';
|
||||||
@ -41,7 +42,7 @@ import MenuSeparator from '../../ui/MenuSeparator';
|
|||||||
import './ComposerEmbeddedMessage.scss';
|
import './ComposerEmbeddedMessage.scss';
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
replyingToId?: number;
|
replyInfo?: ApiInputMessageReplyInfo;
|
||||||
editingId?: number;
|
editingId?: number;
|
||||||
message?: ApiMessage;
|
message?: ApiMessage;
|
||||||
sender?: ApiPeer;
|
sender?: ApiPeer;
|
||||||
@ -62,7 +63,7 @@ type OwnProps = {
|
|||||||
const FORWARD_RENDERING_DELAY = 300;
|
const FORWARD_RENDERING_DELAY = 300;
|
||||||
|
|
||||||
const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
||||||
replyingToId,
|
replyInfo,
|
||||||
editingId,
|
editingId,
|
||||||
message,
|
message,
|
||||||
sender,
|
sender,
|
||||||
@ -77,7 +78,7 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
|||||||
onClear,
|
onClear,
|
||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
setReplyingToId,
|
resetDraftReplyInfo,
|
||||||
setEditingId,
|
setEditingId,
|
||||||
focusMessage,
|
focusMessage,
|
||||||
changeForwardRecipient,
|
changeForwardRecipient,
|
||||||
@ -89,23 +90,31 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
|||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
|
|
||||||
|
const isReplyToTopicStart = message?.content.action?.type === 'topicCreate';
|
||||||
|
|
||||||
const isForwarding = Boolean(forwardedMessagesCount);
|
const isForwarding = Boolean(forwardedMessagesCount);
|
||||||
const isShown = Boolean(
|
const isShown = Boolean(
|
||||||
((replyingToId || editingId) && message)
|
((replyInfo || editingId) && message)
|
||||||
|| (sender && forwardedMessagesCount),
|
|| (sender && forwardedMessagesCount),
|
||||||
);
|
);
|
||||||
const canAnimate = useAsyncRendering(
|
const canAnimate = useAsyncRendering(
|
||||||
[forwardedMessagesCount],
|
[isShown],
|
||||||
forwardedMessagesCount ? FORWARD_RENDERING_DELAY : undefined,
|
isShown ? FORWARD_RENDERING_DELAY : undefined,
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
shouldRender, transitionClassNames,
|
shouldRender, transitionClassNames,
|
||||||
} = useShowTransition(canAnimate && isShown, undefined, !shouldAnimate, undefined, !shouldAnimate);
|
} = useShowTransition(
|
||||||
|
canAnimate && isShown && !isReplyToTopicStart,
|
||||||
|
undefined,
|
||||||
|
!shouldAnimate,
|
||||||
|
undefined,
|
||||||
|
!shouldAnimate,
|
||||||
|
);
|
||||||
|
|
||||||
const clearEmbedded = useLastCallback(() => {
|
const clearEmbedded = useLastCallback(() => {
|
||||||
if (replyingToId && !shouldForceShowEditing) {
|
if (replyInfo && !shouldForceShowEditing) {
|
||||||
setReplyingToId({ messageId: undefined });
|
resetDraftReplyInfo();
|
||||||
} else if (editingId) {
|
} else if (editingId) {
|
||||||
setEditingId({ messageId: undefined });
|
setEditingId({ messageId: undefined });
|
||||||
} else if (forwardedMessagesCount) {
|
} else if (forwardedMessagesCount) {
|
||||||
@ -153,9 +162,13 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
|||||||
}, [handleContextMenuClose, shouldRender]);
|
}, [handleContextMenuClose, shouldRender]);
|
||||||
|
|
||||||
const className = buildClassName('ComposerEmbeddedMessage', transitionClassNames);
|
const className = buildClassName('ComposerEmbeddedMessage', transitionClassNames);
|
||||||
|
const innerClassName = buildClassName(
|
||||||
|
'ComposerEmbeddedMessage_inner',
|
||||||
|
getPeerColorClass(sender),
|
||||||
|
);
|
||||||
|
|
||||||
const leftIcon = useMemo(() => {
|
const leftIcon = useMemo(() => {
|
||||||
if (replyingToId && !shouldForceShowEditing) {
|
if (replyInfo && !shouldForceShowEditing) {
|
||||||
return 'icon-reply';
|
return 'icon-reply';
|
||||||
}
|
}
|
||||||
if (editingId) {
|
if (editingId) {
|
||||||
@ -166,7 +179,7 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}, [editingId, isForwarding, replyingToId, shouldForceShowEditing]);
|
}, [editingId, isForwarding, replyInfo, shouldForceShowEditing]);
|
||||||
|
|
||||||
const customText = forwardedMessagesCount && forwardedMessagesCount > 1
|
const customText = forwardedMessagesCount && forwardedMessagesCount > 1
|
||||||
? lang('ForwardedMessageCount', forwardedMessagesCount)
|
? lang('ForwardedMessageCount', forwardedMessagesCount)
|
||||||
@ -191,18 +204,18 @@ const ComposerEmbeddedMessage: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={className} ref={ref} onContextMenu={handleContextMenu} onClick={handleContextMenu}>
|
<div className={className} ref={ref} onContextMenu={handleContextMenu} onClick={handleContextMenu}>
|
||||||
<div className="ComposerEmbeddedMessage_inner">
|
<div className={innerClassName}>
|
||||||
<div className="embedded-left-icon">
|
<div className="embedded-left-icon">
|
||||||
<i className={buildClassName('icon', leftIcon)} />
|
<i className={buildClassName('icon', leftIcon)} />
|
||||||
</div>
|
</div>
|
||||||
<EmbeddedMessage
|
<EmbeddedMessage
|
||||||
className="inside-input"
|
className="inside-input"
|
||||||
|
replyInfo={replyInfo}
|
||||||
message={strippedMessage}
|
message={strippedMessage}
|
||||||
sender={!noAuthors ? sender : undefined}
|
sender={!noAuthors ? sender : undefined}
|
||||||
customText={customText}
|
customText={customText}
|
||||||
title={editingId ? lang('EditMessage') : noAuthors ? lang('HiddenSendersNameDescription') : undefined}
|
title={editingId ? lang('EditMessage') : noAuthors ? lang('HiddenSendersNameDescription') : undefined}
|
||||||
onClick={handleMessageClick}
|
onClick={handleMessageClick}
|
||||||
hasContextMenu={isForwarding && !isContextMenuDisabled}
|
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className="embedded-cancel"
|
className="embedded-cancel"
|
||||||
@ -295,7 +308,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
},
|
},
|
||||||
} = selectTabState(global);
|
} = selectTabState(global);
|
||||||
|
|
||||||
const replyingToId = selectReplyingToId(global, chatId, threadId);
|
|
||||||
const editingId = messageListType === 'scheduled'
|
const editingId = messageListType === 'scheduled'
|
||||||
? selectEditingScheduledId(global, chatId)
|
? selectEditingScheduledId(global, chatId)
|
||||||
: selectEditingId(global, chatId, threadId);
|
: selectEditingId(global, chatId, threadId);
|
||||||
@ -303,9 +315,11 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const isForwarding = toChatId === chatId;
|
const isForwarding = toChatId === chatId;
|
||||||
const forwardedMessages = forwardMessageIds?.map((id) => selectChatMessage(global, fromChatId!, id)!);
|
const forwardedMessages = forwardMessageIds?.map((id) => selectChatMessage(global, fromChatId!, id)!);
|
||||||
|
|
||||||
|
const draft = selectDraft(global, chatId, threadId);
|
||||||
|
const replyInfo = draft?.replyInfo;
|
||||||
let message: ApiMessage | undefined;
|
let message: ApiMessage | undefined;
|
||||||
if (replyingToId && !shouldForceShowEditing) {
|
if (replyInfo && !shouldForceShowEditing) {
|
||||||
message = selectChatMessage(global, chatId, replyingToId);
|
message = selectChatMessage(global, replyInfo.replyToPeerId || chatId, replyInfo.replyToMsgId);
|
||||||
} else if (editingId) {
|
} else if (editingId) {
|
||||||
message = selectEditingMessage(global, chatId, threadId, messageListType);
|
message = selectEditingMessage(global, chatId, threadId, messageListType);
|
||||||
} else if (isForwarding && forwardMessageIds!.length === 1) {
|
} else if (isForwarding && forwardMessageIds!.length === 1) {
|
||||||
@ -313,7 +327,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let sender: ApiPeer | undefined;
|
let sender: ApiPeer | undefined;
|
||||||
if (replyingToId && message && !shouldForceShowEditing) {
|
if (replyInfo && message && !shouldForceShowEditing) {
|
||||||
const { forwardInfo } = message;
|
const { forwardInfo } = message;
|
||||||
const isChatWithSelf = selectIsChatWithSelf(global, chatId);
|
const isChatWithSelf = selectIsChatWithSelf(global, chatId);
|
||||||
if (forwardInfo && (forwardInfo.isChannelPost || isChatWithSelf)) {
|
if (forwardInfo && (forwardInfo.isChannelPost || isChatWithSelf)) {
|
||||||
@ -343,7 +357,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
&& Boolean(message?.content.storyData);
|
&& Boolean(message?.content.storyData);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
replyingToId,
|
replyInfo,
|
||||||
editingId,
|
editingId,
|
||||||
message,
|
message,
|
||||||
sender,
|
sender,
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import "../../../styles/mixins";
|
@use "../../../styles/mixins";
|
||||||
|
|
||||||
.EmojiPicker {
|
.EmojiPicker {
|
||||||
--emoji-size: 2.25rem;
|
--emoji-size: 2.25rem;
|
||||||
@ -10,11 +10,11 @@
|
|||||||
height: calc(100% - 3rem);
|
height: calc(100% - 3rem);
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
@include adapt-padding-to-scrollbar(0.75rem);
|
@include mixins.adapt-padding-to-scrollbar(0.75rem);
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
padding: 0.5rem 0.25rem;
|
padding: 0.5rem 0.25rem;
|
||||||
@include adapt-padding-to-scrollbar(0.25rem);
|
@include mixins.adapt-padding-to-scrollbar(0.25rem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,12 +6,13 @@ import React, {
|
|||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
|
import type { ApiInputMessageReplyInfo } from '../../../api/types';
|
||||||
import type { IAnchorPosition, ISettings } from '../../../types';
|
import type { IAnchorPosition, ISettings } from '../../../types';
|
||||||
import type { Signal } from '../../../util/signals';
|
import type { Signal } from '../../../util/signals';
|
||||||
|
|
||||||
import { EDITABLE_INPUT_ID } from '../../../config';
|
import { EDITABLE_INPUT_ID } from '../../../config';
|
||||||
import { requestForcedReflow, requestMutation } from '../../../lib/fasterdom/fasterdom';
|
import { requestForcedReflow, requestMutation } from '../../../lib/fasterdom/fasterdom';
|
||||||
import { selectCanPlayAnimatedEmojis, selectIsInSelectMode, selectReplyingToId } from '../../../global/selectors';
|
import { selectCanPlayAnimatedEmojis, selectDraft, selectIsInSelectMode } from '../../../global/selectors';
|
||||||
import buildClassName from '../../../util/buildClassName';
|
import buildClassName from '../../../util/buildClassName';
|
||||||
import captureKeyboardListeners from '../../../util/captureKeyboardListeners';
|
import captureKeyboardListeners from '../../../util/captureKeyboardListeners';
|
||||||
import { getIsDirectTextInputDisabled } from '../../../util/directInputManager';
|
import { getIsDirectTextInputDisabled } from '../../../util/directInputManager';
|
||||||
@ -74,7 +75,7 @@ type OwnProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
replyingToId?: number;
|
replyInfo?: ApiInputMessageReplyInfo;
|
||||||
isSelectModeActive?: boolean;
|
isSelectModeActive?: boolean;
|
||||||
messageSendKeyCombo?: ISettings['messageSendKeyCombo'];
|
messageSendKeyCombo?: ISettings['messageSendKeyCombo'];
|
||||||
canPlayAnimatedEmojis: boolean;
|
canPlayAnimatedEmojis: boolean;
|
||||||
@ -126,7 +127,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
|||||||
noFocusInterception,
|
noFocusInterception,
|
||||||
shouldSuppressFocus,
|
shouldSuppressFocus,
|
||||||
shouldSuppressTextFormatter,
|
shouldSuppressTextFormatter,
|
||||||
replyingToId,
|
replyInfo,
|
||||||
isSelectModeActive,
|
isSelectModeActive,
|
||||||
canPlayAnimatedEmojis,
|
canPlayAnimatedEmojis,
|
||||||
messageSendKeyCombo,
|
messageSendKeyCombo,
|
||||||
@ -461,7 +462,7 @@ const MessageInput: FC<OwnProps & StateProps> = ({
|
|||||||
if (canAutoFocus) {
|
if (canAutoFocus) {
|
||||||
focusInput();
|
focusInput();
|
||||||
}
|
}
|
||||||
}, [chatId, focusInput, replyingToId, canAutoFocus]);
|
}, [chatId, focusInput, replyInfo, canAutoFocus]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@ -626,7 +627,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
messageSendKeyCombo,
|
messageSendKeyCombo,
|
||||||
replyingToId: chatId && threadId ? selectReplyingToId(global, chatId, threadId) : undefined,
|
replyInfo: chatId && threadId ? selectDraft(global, chatId, threadId)?.replyInfo : undefined,
|
||||||
isSelectModeActive: selectIsInSelectMode(global),
|
isSelectModeActive: selectIsInSelectMode(global),
|
||||||
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
|
canPlayAnimatedEmojis: selectCanPlayAnimatedEmojis(global),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import "../../../styles/mixins";
|
@use "../../../styles/mixins";
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
--color-primary: var(--color-text);
|
--color-primary: var(--color-text);
|
||||||
@ -17,11 +17,11 @@
|
|||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
|
|
||||||
padding: 0.5rem 0.25rem;
|
padding: 0.5rem 0.25rem;
|
||||||
@include adapt-padding-to-scrollbar(0.25rem);
|
@include mixins.adapt-padding-to-scrollbar(0.25rem);
|
||||||
|
|
||||||
&_customEmoji {
|
&_customEmoji {
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
@include adapt-padding-to-scrollbar(0.75rem);
|
@include mixins.adapt-padding-to-scrollbar(0.75rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.bubble) {
|
:global(.bubble) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import "../../../styles/mixins";
|
@use "../../../styles/mixins";
|
||||||
|
|
||||||
.SymbolMenu {
|
.SymbolMenu {
|
||||||
&.attachment-modal-symbol-menu {
|
&.attachment-modal-symbol-menu {
|
||||||
@ -277,7 +277,7 @@
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@include while-transition() {
|
@include mixins.while-transition() {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -56,7 +56,7 @@ const useDraft = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const html = getHtml();
|
const html = getHtml();
|
||||||
const isLocalDraft = draft?.isLocal !== undefined;
|
const isLocalDraft = draft?.isLocal !== undefined;
|
||||||
if (getTextWithEntitiesAsHtml(draft) === html && !isLocalDraft) {
|
if (getTextWithEntitiesAsHtml(draft?.text) === html && !isLocalDraft) {
|
||||||
isTouchedRef.current = false;
|
isTouchedRef.current = false;
|
||||||
} else {
|
} else {
|
||||||
isTouchedRef.current = true;
|
isTouchedRef.current = true;
|
||||||
@ -77,12 +77,13 @@ const useDraft = ({
|
|||||||
saveDraft({
|
saveDraft({
|
||||||
chatId: prevState.chatId ?? chatId,
|
chatId: prevState.chatId ?? chatId,
|
||||||
threadId: prevState.threadId ?? threadId,
|
threadId: prevState.threadId ?? threadId,
|
||||||
draft: parseMessageInput(html),
|
text: parseMessageInput(html),
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
clearDraft({
|
clearDraft({
|
||||||
chatId: prevState.chatId ?? chatId,
|
chatId: prevState.chatId ?? chatId,
|
||||||
threadId: prevState.threadId ?? threadId,
|
threadId: prevState.threadId ?? threadId,
|
||||||
|
shouldKeepReply: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -109,9 +110,9 @@ const useDraft = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setHtml(getTextWithEntitiesAsHtml(draft));
|
setHtml(getTextWithEntitiesAsHtml(draft.text));
|
||||||
|
|
||||||
const customEmojiIds = draft.entities
|
const customEmojiIds = draft.text?.entities
|
||||||
?.map((entity) => entity.type === ApiMessageEntityTypes.CustomEmoji && entity.documentId)
|
?.map((entity) => entity.type === ApiMessageEntityTypes.CustomEmoji && entity.documentId)
|
||||||
.filter(Boolean) || [];
|
.filter(Boolean) || [];
|
||||||
if (customEmojiIds.length) loadCustomEmojis({ ids: customEmojiIds });
|
if (customEmojiIds.length) loadCustomEmojis({ ids: customEmojiIds });
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { useEffect, useState } from '../../../../lib/teact/teact';
|
|||||||
import { getActions } from '../../../../global';
|
import { getActions } from '../../../../global';
|
||||||
|
|
||||||
import type { ApiFormattedText, ApiMessage } from '../../../../api/types';
|
import type { ApiFormattedText, ApiMessage } from '../../../../api/types';
|
||||||
import type { MessageListType } from '../../../../global/types';
|
import type { ApiDraft, MessageListType } from '../../../../global/types';
|
||||||
import type { Signal } from '../../../../util/signals';
|
import type { Signal } from '../../../../util/signals';
|
||||||
import { ApiMessageEntityTypes } from '../../../../api/types';
|
import { ApiMessageEntityTypes } from '../../../../api/types';
|
||||||
|
|
||||||
@ -32,13 +32,14 @@ const useEditing = (
|
|||||||
chatId: string,
|
chatId: string,
|
||||||
threadId: number,
|
threadId: number,
|
||||||
type: MessageListType,
|
type: MessageListType,
|
||||||
draft?: ApiFormattedText,
|
draft?: ApiDraft,
|
||||||
editingDraft?: ApiFormattedText,
|
editingDraft?: ApiFormattedText,
|
||||||
replyingToId?: number,
|
|
||||||
): [VoidFunction, VoidFunction, boolean] => {
|
): [VoidFunction, VoidFunction, boolean] => {
|
||||||
const { editMessage, setEditingDraft, toggleMessageWebPage } = getActions();
|
const { editMessage, setEditingDraft, toggleMessageWebPage } = getActions();
|
||||||
const [shouldForceShowEditing, setShouldForceShowEditing] = useState(false);
|
const [shouldForceShowEditing, setShouldForceShowEditing] = useState(false);
|
||||||
|
|
||||||
|
const replyingToId = draft?.replyInfo?.replyToMsgId;
|
||||||
|
|
||||||
useEffectWithPrevDeps(([prevEditedMessage, prevReplyingToId]) => {
|
useEffectWithPrevDeps(([prevEditedMessage, prevReplyingToId]) => {
|
||||||
if (!editedMessage) {
|
if (!editedMessage) {
|
||||||
return;
|
return;
|
||||||
@ -125,7 +126,7 @@ const useEditing = (
|
|||||||
|
|
||||||
// Run one frame after editing draft reset
|
// Run one frame after editing draft reset
|
||||||
requestMeasure(() => {
|
requestMeasure(() => {
|
||||||
setHtml(getTextWithEntitiesAsHtml(draft));
|
setHtml(getTextWithEntitiesAsHtml(draft.text));
|
||||||
|
|
||||||
// Wait one more frame until new HTML is rendered
|
// Wait one more frame until new HTML is rendered
|
||||||
requestNextMutation(() => {
|
requestNextMutation(() => {
|
||||||
|
|||||||
@ -167,7 +167,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const {
|
const {
|
||||||
openChat,
|
openChat,
|
||||||
setReplyingToId,
|
updateDraftReplyInfo,
|
||||||
setEditingId,
|
setEditingId,
|
||||||
pinMessage,
|
pinMessage,
|
||||||
openForwardMenu,
|
openForwardMenu,
|
||||||
@ -296,7 +296,7 @@ const ContextMenuContainer: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleReply = useLastCallback(() => {
|
const handleReply = useLastCallback(() => {
|
||||||
setReplyingToId({ messageId: message.id });
|
updateDraftReplyInfo({ replyToMsgId: message.id });
|
||||||
closeMenu();
|
closeMenu();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import "message-content";
|
@use "message-content";
|
||||||
|
|
||||||
// General styles
|
// General styles
|
||||||
.Message {
|
.Message {
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { getActions, withGlobal } from '../../../global';
|
|||||||
|
|
||||||
import type {
|
import type {
|
||||||
ApiAvailableReaction,
|
ApiAvailableReaction,
|
||||||
|
ApiChat,
|
||||||
ApiChatMember,
|
ApiChatMember,
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiMessageOutgoingStatus,
|
ApiMessageOutgoingStatus,
|
||||||
@ -35,23 +36,23 @@ import {
|
|||||||
getMessageCustomShape,
|
getMessageCustomShape,
|
||||||
getMessageHtmlId,
|
getMessageHtmlId,
|
||||||
getMessageKey,
|
getMessageKey,
|
||||||
getMessageLocation,
|
|
||||||
getMessageSingleCustomEmoji,
|
getMessageSingleCustomEmoji,
|
||||||
getMessageSingleRegularEmoji,
|
getMessageSingleRegularEmoji,
|
||||||
getPeerColorKey,
|
|
||||||
getSenderTitle,
|
getSenderTitle,
|
||||||
hasMessageText,
|
hasMessageText,
|
||||||
isAnonymousOwnMessage,
|
isAnonymousOwnMessage,
|
||||||
isChatChannel,
|
isChatChannel,
|
||||||
isChatGroup,
|
isChatGroup,
|
||||||
|
isChatPublic,
|
||||||
isChatWithRepliesBot,
|
isChatWithRepliesBot,
|
||||||
isGeoLiveExpired,
|
isGeoLiveExpired,
|
||||||
isMessageLocal,
|
isMessageLocal,
|
||||||
isMessageTranslatable,
|
isMessageTranslatable,
|
||||||
isOwnMessage,
|
isOwnMessage,
|
||||||
isReplyMessage,
|
isReplyToMessage,
|
||||||
isUserId,
|
isUserId,
|
||||||
} from '../../../global/helpers';
|
} from '../../../global/helpers';
|
||||||
|
import { getMessageReplyInfo, getStoryReplyInfo } from '../../../global/helpers/replies';
|
||||||
import {
|
import {
|
||||||
selectAllowedMessageActions,
|
selectAllowedMessageActions,
|
||||||
selectAnimatedEmoji,
|
selectAnimatedEmoji,
|
||||||
@ -81,6 +82,7 @@ import {
|
|||||||
selectRequestedChatTranslationLanguage,
|
selectRequestedChatTranslationLanguage,
|
||||||
selectRequestedMessageTranslationLanguage,
|
selectRequestedMessageTranslationLanguage,
|
||||||
selectSender,
|
selectSender,
|
||||||
|
selectSenderFromHeader,
|
||||||
selectShouldDetectChatLanguage,
|
selectShouldDetectChatLanguage,
|
||||||
selectShouldLoopStickers,
|
selectShouldLoopStickers,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
@ -101,6 +103,7 @@ import {
|
|||||||
REM,
|
REM,
|
||||||
ROUND_VIDEO_DIMENSIONS_PX,
|
ROUND_VIDEO_DIMENSIONS_PX,
|
||||||
} from '../../common/helpers/mediaDimensions';
|
} from '../../common/helpers/mediaDimensions';
|
||||||
|
import { getPeerColorClass } from '../../common/helpers/peerColor';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
import { getCustomEmojiSize } from '../composer/helpers/customEmoji';
|
import { getCustomEmojiSize } from '../composer/helpers/customEmoji';
|
||||||
import { buildContentClassName } from './helpers/buildContentClassName';
|
import { buildContentClassName } from './helpers/buildContentClassName';
|
||||||
@ -133,9 +136,10 @@ import Avatar from '../../common/Avatar';
|
|||||||
import CustomEmoji from '../../common/CustomEmoji';
|
import CustomEmoji from '../../common/CustomEmoji';
|
||||||
import Document from '../../common/Document';
|
import Document from '../../common/Document';
|
||||||
import DotAnimation from '../../common/DotAnimation';
|
import DotAnimation from '../../common/DotAnimation';
|
||||||
import EmbeddedMessage from '../../common/EmbeddedMessage';
|
import EmbeddedMessage from '../../common/embedded/EmbeddedMessage';
|
||||||
import EmbeddedStory from '../../common/EmbeddedStory';
|
import EmbeddedStory from '../../common/embedded/EmbeddedStory';
|
||||||
import FakeIcon from '../../common/FakeIcon';
|
import FakeIcon from '../../common/FakeIcon';
|
||||||
|
import Icon from '../../common/Icon';
|
||||||
import MessageText from '../../common/MessageText';
|
import MessageText from '../../common/MessageText';
|
||||||
import PremiumIcon from '../../common/PremiumIcon';
|
import PremiumIcon from '../../common/PremiumIcon';
|
||||||
import ReactionStaticEmoji from '../../common/ReactionStaticEmoji';
|
import ReactionStaticEmoji from '../../common/ReactionStaticEmoji';
|
||||||
@ -208,6 +212,8 @@ type StateProps = {
|
|||||||
replyMessage?: ApiMessage;
|
replyMessage?: ApiMessage;
|
||||||
replyMessageSender?: ApiPeer;
|
replyMessageSender?: ApiPeer;
|
||||||
replyMessageForwardSender?: ApiPeer;
|
replyMessageForwardSender?: ApiPeer;
|
||||||
|
replyMessageChat?: ApiChat;
|
||||||
|
isReplyPrivate?: boolean;
|
||||||
replyStory?: ApiTypeStory;
|
replyStory?: ApiTypeStory;
|
||||||
storySender?: ApiUser;
|
storySender?: ApiUser;
|
||||||
outgoingStatus?: ApiMessageOutgoingStatus;
|
outgoingStatus?: ApiMessageOutgoingStatus;
|
||||||
@ -318,7 +324,9 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
replyMessage,
|
replyMessage,
|
||||||
replyMessageSender,
|
replyMessageSender,
|
||||||
replyMessageForwardSender,
|
replyMessageForwardSender,
|
||||||
|
replyMessageChat,
|
||||||
replyStory,
|
replyStory,
|
||||||
|
isReplyPrivate,
|
||||||
storySender,
|
storySender,
|
||||||
outgoingStatus,
|
outgoingStatus,
|
||||||
uploadProgress,
|
uploadProgress,
|
||||||
@ -454,8 +462,12 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
const isLocal = isMessageLocal(message);
|
const isLocal = isMessageLocal(message);
|
||||||
const isOwn = isOwnMessage(message);
|
const isOwn = isOwnMessage(message);
|
||||||
const isScheduled = messageListType === 'scheduled' || message.isScheduled;
|
const isScheduled = messageListType === 'scheduled' || message.isScheduled;
|
||||||
const hasReply = isReplyMessage(message) && !shouldHideReply;
|
const hasMessageReply = isReplyToMessage(message) && !shouldHideReply;
|
||||||
const hasStoryReply = Boolean(message.replyToStoryId);
|
|
||||||
|
const messageReplyInfo = getMessageReplyInfo(message);
|
||||||
|
const storyReplyInfo = getStoryReplyInfo(message);
|
||||||
|
|
||||||
|
const hasStoryReply = Boolean(storyReplyInfo);
|
||||||
const hasThread = Boolean(repliesThreadInfo) && messageListType === 'thread';
|
const hasThread = Boolean(repliesThreadInfo) && messageListType === 'thread';
|
||||||
const isCustomShape = getMessageCustomShape(message);
|
const isCustomShape = getMessageCustomShape(message);
|
||||||
const hasAnimatedEmoji = isCustomShape && (animatedEmoji || animatedCustomEmoji);
|
const hasAnimatedEmoji = isCustomShape && (animatedEmoji || animatedCustomEmoji);
|
||||||
@ -485,7 +497,9 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
&& forwardInfo.fromMessageId
|
&& forwardInfo.fromMessageId
|
||||||
));
|
));
|
||||||
|
|
||||||
const hasSubheader = hasTopicChip || hasReply || hasStoryReply;
|
const noUserColors = isOwn && !isCustomShape;
|
||||||
|
|
||||||
|
const hasSubheader = hasTopicChip || hasMessageReply || hasStoryReply;
|
||||||
|
|
||||||
const selectMessage = useLastCallback((e?: React.MouseEvent<HTMLDivElement, MouseEvent>, groupedId?: string) => {
|
const selectMessage = useLastCallback((e?: React.MouseEvent<HTMLDivElement, MouseEvent>, groupedId?: string) => {
|
||||||
toggleMessageSelection({
|
toggleMessageSelection({
|
||||||
@ -501,6 +515,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const shouldPreferOriginSender = forwardInfo && (isChatWithSelf || isRepliesChat || !messageSender);
|
const shouldPreferOriginSender = forwardInfo && (isChatWithSelf || isRepliesChat || !messageSender);
|
||||||
const avatarPeer = shouldPreferOriginSender ? originSender : messageSender;
|
const avatarPeer = shouldPreferOriginSender ? originSender : messageSender;
|
||||||
|
const messageColorPeer = originSender || sender;
|
||||||
const senderPeer = (forwardInfo || message.content.storyData) ? originSender : messageSender;
|
const senderPeer = (forwardInfo || message.content.storyData) ? originSender : messageSender;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@ -561,7 +576,6 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
isInDocumentGroup,
|
isInDocumentGroup,
|
||||||
asForwarded,
|
asForwarded,
|
||||||
isScheduled,
|
isScheduled,
|
||||||
isRepliesChat,
|
|
||||||
album,
|
album,
|
||||||
avatarPeer,
|
avatarPeer,
|
||||||
senderPeer,
|
senderPeer,
|
||||||
@ -569,6 +583,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
messageTopic,
|
messageTopic,
|
||||||
Boolean(requestedChatTranslationLanguage),
|
Boolean(requestedChatTranslationLanguage),
|
||||||
replyStory && 'content' in replyStory ? replyStory : undefined,
|
replyStory && 'content' in replyStory ? replyStory : undefined,
|
||||||
|
isReplyPrivate,
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -592,7 +607,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
isOwn && 'own',
|
isOwn && 'own',
|
||||||
Boolean(message.views) && 'has-views',
|
Boolean(message.views) && 'has-views',
|
||||||
message.isEdited && 'was-edited',
|
message.isEdited && 'was-edited',
|
||||||
hasReply && 'has-reply',
|
hasMessageReply && 'has-reply',
|
||||||
isContextMenuOpen && 'has-menu-open',
|
isContextMenuOpen && 'has-menu-open',
|
||||||
isFocused && !noFocusHighlight && 'focused',
|
isFocused && !noFocusHighlight && 'focused',
|
||||||
isForwarding && 'is-forwarding',
|
isForwarding && 'is-forwarding',
|
||||||
@ -618,6 +633,9 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
action, game, storyData,
|
action, game, storyData,
|
||||||
} = getMessageContent(message);
|
} = getMessageContent(message);
|
||||||
|
|
||||||
|
const { replyToMsgId, replyToPeerId, isQuote } = messageReplyInfo || {};
|
||||||
|
const { userId: storyReplyUserId, storyId: storyReplyId } = storyReplyInfo || {};
|
||||||
|
|
||||||
const detectedLanguage = useTextLanguage(
|
const detectedLanguage = useTextLanguage(
|
||||||
text?.text,
|
text?.text,
|
||||||
!(areTranslationsEnabled || shouldDetectChatLanguage),
|
!(areTranslationsEnabled || shouldDetectChatLanguage),
|
||||||
@ -657,6 +675,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
hasReactions,
|
hasReactions,
|
||||||
isGeoLiveActive: location?.type === 'geoLive' && !isGeoLiveExpired(message),
|
isGeoLiveActive: location?.type === 'geoLive' && !isGeoLiveExpired(message),
|
||||||
withVoiceTranscription,
|
withVoiceTranscription,
|
||||||
|
peerColorClass: getPeerColorClass(messageColorPeer, noUserColors),
|
||||||
});
|
});
|
||||||
|
|
||||||
const withAppendix = contentClassName.includes('has-appendix');
|
const withAppendix = contentClassName.includes('has-appendix');
|
||||||
@ -676,7 +695,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
let reactionsPosition!: ReactionsPosition;
|
let reactionsPosition!: ReactionsPosition;
|
||||||
if (hasReactions) {
|
if (hasReactions) {
|
||||||
if (isCustomShape || ((photo || video || storyData || (location && location.type === 'geo')) && !hasText)) {
|
if (isCustomShape || ((photo || video || storyData || (location?.type === 'geo')) && !hasText)) {
|
||||||
reactionsPosition = 'outside';
|
reactionsPosition = 'outside';
|
||||||
} else if (asForwarded) {
|
} else if (asForwarded) {
|
||||||
metaPosition = 'standalone';
|
metaPosition = 'standalone';
|
||||||
@ -691,15 +710,16 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
const quickReactionPosition: QuickReactionPosition = isCustomShape ? 'in-meta' : 'in-content';
|
const quickReactionPosition: QuickReactionPosition = isCustomShape ? 'in-meta' : 'in-content';
|
||||||
|
|
||||||
useEnsureMessage(
|
useEnsureMessage(
|
||||||
isRepliesChat && message.replyToChatId ? message.replyToChatId : chatId,
|
replyToPeerId || chatId,
|
||||||
hasReply ? message.replyToMessageId : undefined,
|
replyToMsgId,
|
||||||
replyMessage,
|
replyMessage,
|
||||||
message.id,
|
message.id,
|
||||||
|
isQuote || isReplyPrivate,
|
||||||
);
|
);
|
||||||
|
|
||||||
useEnsureStory(
|
useEnsureStory(
|
||||||
message.replyToStoryUserId ? message.replyToStoryUserId : chatId,
|
storyReplyUserId || chatId,
|
||||||
message.replyToStoryId,
|
storyReplyId,
|
||||||
replyStory,
|
replyStory,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -939,12 +959,14 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
className="message-topic"
|
className="message-topic"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{hasReply && (
|
{hasMessageReply && (
|
||||||
<EmbeddedMessage
|
<EmbeddedMessage
|
||||||
message={replyMessage}
|
message={replyMessage}
|
||||||
noUserColors={isOwn || isChannel}
|
replyInfo={messageReplyInfo}
|
||||||
|
noUserColors={noUserColors}
|
||||||
isProtected={isProtected}
|
isProtected={isProtected}
|
||||||
sender={replyMessageSender}
|
sender={replyMessageSender}
|
||||||
|
senderChat={replyMessageChat}
|
||||||
forwardSender={replyMessageForwardSender}
|
forwardSender={replyMessageForwardSender}
|
||||||
chatTranslations={chatTranslations}
|
chatTranslations={chatTranslations}
|
||||||
requestedChatTranslationLanguage={requestedChatTranslationLanguage}
|
requestedChatTranslationLanguage={requestedChatTranslationLanguage}
|
||||||
@ -957,7 +979,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
<EmbeddedStory
|
<EmbeddedStory
|
||||||
story={replyStory}
|
story={replyStory}
|
||||||
sender={storySender}
|
sender={storySender}
|
||||||
noUserColors={isOwn || isChannel}
|
noUserColors={noUserColors}
|
||||||
isProtected={isProtected}
|
isProtected={isProtected}
|
||||||
observeIntersectionForLoading={observeIntersectionForLoading}
|
observeIntersectionForLoading={observeIntersectionForLoading}
|
||||||
onClick={handleStoryClick}
|
onClick={handleStoryClick}
|
||||||
@ -1169,6 +1191,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
theme={theme}
|
theme={theme}
|
||||||
story={webPageStory}
|
story={webPageStory}
|
||||||
isConnected={isConnected}
|
isConnected={isConnected}
|
||||||
|
noUserColors={isOwn}
|
||||||
onMediaClick={handleMediaClick}
|
onMediaClick={handleMediaClick}
|
||||||
onCancelMediaTransfer={handleCancelUpload}
|
onCancelMediaTransfer={handleCancelUpload}
|
||||||
/>
|
/>
|
||||||
@ -1200,7 +1223,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
const media = photo || video || location;
|
const media = photo || video || location;
|
||||||
const shouldRender = !(isCustomShape && !viaBotId) && (
|
const shouldRender = !(isCustomShape && !viaBotId) && (
|
||||||
(withSenderName && (!media || hasTopicChip)) || asForwarded || viaBotId || forceSenderName
|
(withSenderName && (!media || hasTopicChip)) || asForwarded || viaBotId || forceSenderName
|
||||||
) && !isInDocumentGroupNotFirst && !(hasReply && isCustomShape);
|
) && !isInDocumentGroupNotFirst && !(hasMessageReply && isCustomShape);
|
||||||
|
|
||||||
if (!shouldRender) {
|
if (!shouldRender) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -1210,10 +1233,6 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
let senderColor;
|
let senderColor;
|
||||||
if (senderPeer && !(isCustomShape && viaBotId)) {
|
if (senderPeer && !(isCustomShape && viaBotId)) {
|
||||||
senderTitle = getSenderTitle(lang, senderPeer);
|
senderTitle = getSenderTitle(lang, senderPeer);
|
||||||
|
|
||||||
if (!asForwarded && !isOwn) {
|
|
||||||
senderColor = `color-${getPeerColorKey(senderPeer)}`;
|
|
||||||
}
|
|
||||||
} else if (forwardInfo?.hiddenUserName) {
|
} else if (forwardInfo?.hiddenUserName) {
|
||||||
senderTitle = forwardInfo.hiddenUserName;
|
senderTitle = forwardInfo.hiddenUserName;
|
||||||
} else if (storyData && originSender) {
|
} else if (storyData && originSender) {
|
||||||
@ -1235,8 +1254,9 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
dir="ltr"
|
dir="ltr"
|
||||||
>
|
>
|
||||||
{asForwarded && (
|
{asForwarded && (
|
||||||
<i className={`icon ${forwardInfo?.hiddenUserName ? 'icon-forward' : 'icon-share-filled'}`} />
|
<Icon name={forwardInfo?.hiddenUserName ? 'forward' : 'share-filled'} />
|
||||||
)}
|
)}
|
||||||
|
{storyData && <Icon name="play-story" />}
|
||||||
{senderTitle ? renderText(senderTitle) : (asForwarded ? NBSP : undefined)}
|
{senderTitle ? renderText(senderTitle) : (asForwarded ? NBSP : undefined)}
|
||||||
{!asForwarded && senderEmojiStatus && (
|
{!asForwarded && senderEmojiStatus && (
|
||||||
<CustomEmoji
|
<CustomEmoji
|
||||||
@ -1431,8 +1451,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
message, album, withSenderName, withAvatar, threadId, messageListType, isLastInDocumentGroup, isFirstInGroup,
|
message, album, withSenderName, withAvatar, threadId, messageListType, isLastInDocumentGroup, isFirstInGroup,
|
||||||
} = ownProps;
|
} = ownProps;
|
||||||
const {
|
const {
|
||||||
id, chatId, viaBotId, replyToChatId, replyToMessageId, isOutgoing, forwardInfo,
|
id, chatId, viaBotId, isOutgoing, forwardInfo, transcriptionId, isPinned, repliesThreadInfo,
|
||||||
transcriptionId, isPinned, replyToStoryUserId, replyToStoryId, repliesThreadInfo,
|
|
||||||
} = message;
|
} = message;
|
||||||
|
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
@ -1459,17 +1478,25 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const threadTopMessageId = threadId ? selectThreadTopMessageId(global, chatId, threadId) : undefined;
|
const threadTopMessageId = threadId ? selectThreadTopMessageId(global, chatId, threadId) : undefined;
|
||||||
const isThreadTop = message.id === threadTopMessageId;
|
const isThreadTop = message.id === threadTopMessageId;
|
||||||
|
|
||||||
const shouldHideReply = replyToMessageId === threadTopMessageId;
|
const { replyToMsgId, replyToPeerId, replyFrom } = getMessageReplyInfo(message) || {};
|
||||||
const replyMessage = replyToMessageId && !shouldHideReply
|
const { userId: storyReplyUserId, storyId: storyReplyId } = getStoryReplyInfo(message) || {};
|
||||||
? selectChatMessage(global, isRepliesChat && replyToChatId ? replyToChatId : chatId, replyToMessageId)
|
|
||||||
|
const shouldHideReply = replyToMsgId && replyToMsgId === threadTopMessageId;
|
||||||
|
const replyMessage = replyToMsgId && !shouldHideReply
|
||||||
|
? selectChatMessage(global, replyToPeerId || chatId, replyToMsgId)
|
||||||
: undefined;
|
: undefined;
|
||||||
const replyMessageSender = replyMessage && selectReplySender(global, replyMessage, Boolean(forwardInfo));
|
const forwardHeader = forwardInfo || replyFrom;
|
||||||
|
const replyMessageSender = replyMessage ? selectReplySender(global, replyMessage) : forwardHeader
|
||||||
|
? selectSenderFromHeader(global, forwardHeader) : undefined;
|
||||||
const replyMessageForwardSender = replyMessage && selectForwardedSender(global, replyMessage);
|
const replyMessageForwardSender = replyMessage && selectForwardedSender(global, replyMessage);
|
||||||
|
const replyMessageChat = replyToPeerId ? selectChat(global, replyToPeerId) : undefined;
|
||||||
|
const isReplyPrivate = replyMessageChat && !isChatPublic(replyMessageChat)
|
||||||
|
&& (replyMessageChat.isNotJoined || replyMessageChat.isRestricted);
|
||||||
const isReplyToTopicStart = replyMessage?.content.action?.type === 'topicCreate';
|
const isReplyToTopicStart = replyMessage?.content.action?.type === 'topicCreate';
|
||||||
const replyStory = replyToStoryId && replyToStoryUserId
|
const replyStory = storyReplyId && storyReplyUserId
|
||||||
? selectPeerStory(global, replyToStoryUserId, replyToStoryId)
|
? selectPeerStory(global, storyReplyUserId, storyReplyId)
|
||||||
: undefined;
|
: undefined;
|
||||||
const storySender = replyToStoryUserId ? selectUser(global, replyToStoryUserId) : undefined;
|
const storySender = storyReplyUserId ? selectUser(global, storyReplyUserId) : undefined;
|
||||||
|
|
||||||
const uploadProgress = selectUploadProgress(global, message);
|
const uploadProgress = selectUploadProgress(global, message);
|
||||||
const isFocused = messageListType === 'thread' && (
|
const isFocused = messageListType === 'thread' && (
|
||||||
@ -1515,7 +1542,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const messageTopic = hasTopicChip ? (selectTopicFromMessage(global, message) || chat?.topics?.[GENERAL_TOPIC_ID])
|
const messageTopic = hasTopicChip ? (selectTopicFromMessage(global, message) || chat?.topics?.[GENERAL_TOPIC_ID])
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
const isLocation = Boolean(getMessageLocation(message));
|
|
||||||
const chatTranslations = selectChatTranslations(global, chatId);
|
const chatTranslations = selectChatTranslations(global, chatId);
|
||||||
|
|
||||||
const requestedTranslationLanguage = selectRequestedMessageTranslationLanguage(global, chatId, message.id);
|
const requestedTranslationLanguage = selectRequestedMessageTranslationLanguage(global, chatId, message.id);
|
||||||
@ -1531,6 +1557,7 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
return {
|
return {
|
||||||
theme: selectTheme(global),
|
theme: selectTheme(global),
|
||||||
forceSenderName,
|
forceSenderName,
|
||||||
|
sender,
|
||||||
canShowSender,
|
canShowSender,
|
||||||
originSender,
|
originSender,
|
||||||
botSender,
|
botSender,
|
||||||
@ -1539,7 +1566,9 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
replyMessage,
|
replyMessage,
|
||||||
replyMessageSender,
|
replyMessageSender,
|
||||||
replyMessageForwardSender,
|
replyMessageForwardSender,
|
||||||
|
replyMessageChat,
|
||||||
replyStory,
|
replyStory,
|
||||||
|
isReplyPrivate,
|
||||||
storySender,
|
storySender,
|
||||||
isInDocumentGroup,
|
isInDocumentGroup,
|
||||||
isProtected: selectIsMessageProtected(global, message),
|
isProtected: selectIsMessageProtected(global, message),
|
||||||
@ -1593,7 +1622,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
webPageStory,
|
webPageStory,
|
||||||
isConnected,
|
isConnected,
|
||||||
shouldWarnAboutSvg: global.settings.byKey.shouldWarnAboutSvg,
|
shouldWarnAboutSvg: global.settings.byKey.shouldWarnAboutSvg,
|
||||||
...((canShowSender || isLocation) && { sender }),
|
|
||||||
...(isOutgoing && { outgoingStatus: selectOutgoingStatus(global, message, messageListType === 'scheduled') }),
|
...(isOutgoing && { outgoingStatus: selectOutgoingStatus(global, message, messageListType === 'scheduled') }),
|
||||||
...(typeof uploadProgress === 'number' && { uploadProgress }),
|
...(typeof uploadProgress === 'number' && { uploadProgress }),
|
||||||
...(isFocused && {
|
...(isFocused && {
|
||||||
|
|||||||
@ -1,26 +1,29 @@
|
|||||||
.WebPage {
|
.WebPage {
|
||||||
margin-top: 0.25rem;
|
margin-top: 0.25rem;
|
||||||
margin-bottom: 0.125rem;
|
margin-bottom: 0.125rem;
|
||||||
|
padding: 0.375rem 0.375rem 0.375rem 0.625rem;
|
||||||
font-size: calc(var(--message-text-size, 1rem) - 0.125rem);
|
font-size: calc(var(--message-text-size, 1rem) - 0.125rem);
|
||||||
line-height: 1.125rem;
|
line-height: 1.125rem;
|
||||||
max-width: 29rem;
|
max-width: 29rem;
|
||||||
|
background-color: var(--accent-background-color);
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
inset-inline-start: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 3px;
|
||||||
|
background: var(--bar-gradient, var(--accent-color));
|
||||||
|
}
|
||||||
|
|
||||||
.WebPage--content {
|
.WebPage--content {
|
||||||
padding-left: 0.625rem;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&::before {
|
|
||||||
content: "";
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 0.125rem;
|
|
||||||
background: var(--accent-color);
|
|
||||||
border-radius: 0.125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-story {
|
&.is-story {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
@ -28,10 +31,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&--quick-button {
|
&--quick-button {
|
||||||
margin-top: 0.375rem;
|
--riple-color: var(var(--accent-background-active-color));
|
||||||
|
|
||||||
.theme-dark .Message.own &:hover {
|
margin-top: 0.375rem;
|
||||||
color: var(--background-color);
|
margin-bottom: -0.375rem;
|
||||||
|
border-top: 1px solid var(--accent-background-active-color, var(--active-color));
|
||||||
|
|
||||||
|
color: var(--accent-color) !important;
|
||||||
|
|
||||||
|
transition: opacity 0.2s ease-in;
|
||||||
|
|
||||||
|
&:hover, &:active {
|
||||||
|
background-color: transparent !important;
|
||||||
|
opacity: 0.85;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,9 +83,7 @@
|
|||||||
margin-bottom: 1rem !important;
|
margin-bottom: 1rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-content:not(.has-reactions) &.no-article:last-child,
|
.message-content:not(.has-reactions) & {
|
||||||
.message-content:not(.has-reactions) &.with-quick-button,
|
|
||||||
.message-content:not(.has-reactions) &.with-square-photo {
|
|
||||||
margin-bottom: 1rem !important;
|
margin-bottom: 1rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,15 +160,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&:dir(rtl) {
|
|
||||||
padding-inline-start: 0.625rem;
|
|
||||||
|
|
||||||
&::before {
|
|
||||||
left: auto;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1921px) {
|
@media (min-width: 1921px) {
|
||||||
@supports (aspect-ratio: 1) {
|
@supports (aspect-ratio: 1) {
|
||||||
&:not(.in-preview) {
|
&:not(.in-preview) {
|
||||||
|
|||||||
@ -40,6 +40,7 @@ type OwnProps = {
|
|||||||
isDownloading?: boolean;
|
isDownloading?: boolean;
|
||||||
isProtected?: boolean;
|
isProtected?: boolean;
|
||||||
isConnected?: boolean;
|
isConnected?: boolean;
|
||||||
|
noUserColors?: boolean;
|
||||||
theme: ISettings['theme'];
|
theme: ISettings['theme'];
|
||||||
story?: ApiTypeStory;
|
story?: ApiTypeStory;
|
||||||
onMediaClick?: () => void;
|
onMediaClick?: () => void;
|
||||||
@ -124,7 +125,8 @@ const WebPage: FC<OwnProps> = ({
|
|||||||
<Button
|
<Button
|
||||||
className="WebPage--quick-button"
|
className="WebPage--quick-button"
|
||||||
size="tiny"
|
size="tiny"
|
||||||
color="translucent-bordered"
|
color="translucent"
|
||||||
|
isRectangular
|
||||||
onClick={handleQuickButtonClick}
|
onClick={handleQuickButtonClick}
|
||||||
>
|
>
|
||||||
{lang(langKey)}
|
{lang(langKey)}
|
||||||
|
|||||||
@ -1,3 +1,6 @@
|
|||||||
|
@use "sass:map";
|
||||||
|
@use "../../../styles/icons";
|
||||||
|
|
||||||
.message-content {
|
.message-content {
|
||||||
position: relative;
|
position: relative;
|
||||||
max-width: var(--max-width);
|
max-width: var(--max-width);
|
||||||
@ -280,24 +283,6 @@
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@for $i from 1 through 8 {
|
|
||||||
& > .color-#{$i} {
|
|
||||||
color: var(--color-user-#{$i});
|
|
||||||
|
|
||||||
.custom-emoji {
|
|
||||||
color: var(--color-user-#{$i});
|
|
||||||
}
|
|
||||||
|
|
||||||
.PremiumIcon {
|
|
||||||
--color-fill: var(--color-user-#{$i});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-dark .Message.own & > .color-1 {
|
|
||||||
color: var(--accent-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
& + .File {
|
& + .File {
|
||||||
margin-top: 0.25rem;
|
margin-top: 0.25rem;
|
||||||
}
|
}
|
||||||
@ -313,9 +298,11 @@
|
|||||||
|
|
||||||
.custom-emoji {
|
.custom-emoji {
|
||||||
margin-left: 0.25rem;
|
margin-left: 0.25rem;
|
||||||
|
color: var(--accent-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.PremiumIcon {
|
.PremiumIcon {
|
||||||
|
--color-fill: var(--accent-color);
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
margin-left: 0.25rem;
|
margin-left: 0.25rem;
|
||||||
@ -709,8 +696,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.EmbeddedMessage {
|
.EmbeddedMessage {
|
||||||
border-radius: var(--border-radius-messages);
|
|
||||||
|
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
max-width: calc(90vw - 13rem);
|
max-width: calc(90vw - 13rem);
|
||||||
}
|
}
|
||||||
@ -950,6 +935,38 @@
|
|||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
padding-inline: 0.5rem 1rem;
|
||||||
|
margin-block: 0.125rem;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: var(--accent-background-color);
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
inset-inline-start: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 3px;
|
||||||
|
background: var(--bar-gradient, var(--accent-color));
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
@include icons.icon;
|
||||||
|
content: map.get(icons.$icons-map, "quote");
|
||||||
|
|
||||||
|
color: var(--accent-color);
|
||||||
|
position: absolute;
|
||||||
|
top: 0.25rem;
|
||||||
|
inset-inline-end: 0.25rem;
|
||||||
|
|
||||||
|
font-size: 0.625rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes text-loading {
|
@keyframes text-loading {
|
||||||
0% {
|
0% {
|
||||||
background-position-x: 0;
|
background-position-x: 0;
|
||||||
|
|||||||
@ -17,6 +17,7 @@ export function buildContentClassName(
|
|||||||
hasReactions,
|
hasReactions,
|
||||||
isGeoLiveActive,
|
isGeoLiveActive,
|
||||||
withVoiceTranscription,
|
withVoiceTranscription,
|
||||||
|
peerColorClass,
|
||||||
}: {
|
}: {
|
||||||
hasSubheader?: boolean;
|
hasSubheader?: boolean;
|
||||||
isCustomShape?: boolean | number;
|
isCustomShape?: boolean | number;
|
||||||
@ -29,6 +30,7 @@ export function buildContentClassName(
|
|||||||
hasReactions?: boolean;
|
hasReactions?: boolean;
|
||||||
isGeoLiveActive?: boolean;
|
isGeoLiveActive?: boolean;
|
||||||
withVoiceTranscription?: boolean;
|
withVoiceTranscription?: boolean;
|
||||||
|
peerColorClass?: string;
|
||||||
} = {},
|
} = {},
|
||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
@ -41,6 +43,10 @@ export function buildContentClassName(
|
|||||||
const isMediaWithNoText = isMedia && !hasText;
|
const isMediaWithNoText = isMedia && !hasText;
|
||||||
const isViaBot = Boolean(message.viaBotId);
|
const isViaBot = Boolean(message.viaBotId);
|
||||||
|
|
||||||
|
if (peerColorClass) {
|
||||||
|
classNames.push(peerColorClass);
|
||||||
|
}
|
||||||
|
|
||||||
if (!isMedia && message.emojiOnlyCount) {
|
if (!isMedia && message.emojiOnlyCount) {
|
||||||
classNames.push('emoji-only');
|
classNames.push('emoji-only');
|
||||||
if (message.emojiOnlyCount <= EMOJI_SIZES) {
|
if (message.emojiOnlyCount <= EMOJI_SIZES) {
|
||||||
|
|||||||
@ -9,6 +9,8 @@ import type { IAlbum } from '../../../../types';
|
|||||||
import { MAIN_THREAD_ID } from '../../../../api/types';
|
import { MAIN_THREAD_ID } from '../../../../api/types';
|
||||||
import { MediaViewerOrigin } from '../../../../types';
|
import { MediaViewerOrigin } from '../../../../types';
|
||||||
|
|
||||||
|
import { getMessageReplyInfo } from '../../../../global/helpers/replies';
|
||||||
|
|
||||||
import useLastCallback from '../../../../hooks/useLastCallback';
|
import useLastCallback from '../../../../hooks/useLastCallback';
|
||||||
|
|
||||||
export default function useInnerHandlers(
|
export default function useInnerHandlers(
|
||||||
@ -20,7 +22,6 @@ export default function useInnerHandlers(
|
|||||||
isInDocumentGroup: boolean,
|
isInDocumentGroup: boolean,
|
||||||
asForwarded?: boolean,
|
asForwarded?: boolean,
|
||||||
isScheduled?: boolean,
|
isScheduled?: boolean,
|
||||||
isChatWithRepliesBot?: boolean,
|
|
||||||
album?: IAlbum,
|
album?: IAlbum,
|
||||||
avatarPeer?: ApiPeer,
|
avatarPeer?: ApiPeer,
|
||||||
senderPeer?: ApiPeer,
|
senderPeer?: ApiPeer,
|
||||||
@ -28,6 +29,7 @@ export default function useInnerHandlers(
|
|||||||
messageTopic?: ApiTopic,
|
messageTopic?: ApiTopic,
|
||||||
isTranslatingChat?: boolean,
|
isTranslatingChat?: boolean,
|
||||||
story?: ApiStory,
|
story?: ApiStory,
|
||||||
|
isReplyPrivate?: boolean,
|
||||||
) {
|
) {
|
||||||
const {
|
const {
|
||||||
openChat, showNotification, focusMessage, openMediaViewer, openAudioPlayer,
|
openChat, showNotification, focusMessage, openMediaViewer, openAudioPlayer,
|
||||||
@ -36,9 +38,13 @@ export default function useInnerHandlers(
|
|||||||
} = getActions();
|
} = getActions();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
id: messageId, forwardInfo, replyToMessageId, replyToChatId, replyToTopMessageId, groupedId,
|
id: messageId, forwardInfo, groupedId,
|
||||||
} = message;
|
} = message;
|
||||||
|
|
||||||
|
const {
|
||||||
|
replyToMsgId, replyToPeerId, replyToTopId, isQuote,
|
||||||
|
} = getMessageReplyInfo(message) || {};
|
||||||
|
|
||||||
const handleAvatarClick = useLastCallback(() => {
|
const handleAvatarClick = useLastCallback(() => {
|
||||||
if (!avatarPeer) {
|
if (!avatarPeer) {
|
||||||
return;
|
return;
|
||||||
@ -70,12 +76,19 @@ export default function useInnerHandlers(
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleReplyClick = useLastCallback((): void => {
|
const handleReplyClick = useLastCallback((): void => {
|
||||||
|
if (!replyToMsgId || isReplyPrivate) {
|
||||||
|
showNotification({
|
||||||
|
message: isQuote ? lang('QuotePrivate') : lang('ReplyPrivate'),
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
focusMessage({
|
focusMessage({
|
||||||
chatId: isChatWithRepliesBot && replyToChatId ? replyToChatId : chatId,
|
chatId: replyToPeerId || chatId,
|
||||||
threadId,
|
threadId,
|
||||||
messageId: replyToMessageId!,
|
messageId: replyToMsgId,
|
||||||
replyMessageId: isChatWithRepliesBot && replyToChatId ? undefined : messageId,
|
replyMessageId: replyToPeerId ? undefined : messageId,
|
||||||
noForumTopicPanel: true,
|
noForumTopicPanel: !replyToPeerId ? true : undefined, // Open topic panel for cross-chat replies
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -140,10 +153,10 @@ export default function useInnerHandlers(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isChatWithRepliesBot && replyToChatId) {
|
if (replyToPeerId && replyToTopId) {
|
||||||
focusMessageInComments({
|
focusMessageInComments({
|
||||||
chatId: replyToChatId,
|
chatId: replyToPeerId,
|
||||||
threadId: replyToTopMessageId!,
|
threadId: replyToTopId,
|
||||||
messageId: forwardInfo!.fromMessageId!,
|
messageId: forwardInfo!.fromMessageId!,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@ -175,7 +188,7 @@ export default function useInnerHandlers(
|
|||||||
const handleTopicChipClick = useLastCallback(() => {
|
const handleTopicChipClick = useLastCallback(() => {
|
||||||
if (!messageTopic) return;
|
if (!messageTopic) return;
|
||||||
focusMessage({
|
focusMessage({
|
||||||
chatId: isChatWithRepliesBot && replyToChatId ? replyToChatId : chatId,
|
chatId: replyToPeerId || chatId,
|
||||||
threadId: messageTopic.id,
|
threadId: messageTopic.id,
|
||||||
messageId,
|
messageId,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -38,7 +38,7 @@ export default function useOuterHandlers(
|
|||||||
shouldHandleMouseLeave: boolean,
|
shouldHandleMouseLeave: boolean,
|
||||||
getIsMessageListReady: Signal<boolean>,
|
getIsMessageListReady: Signal<boolean>,
|
||||||
) {
|
) {
|
||||||
const { setReplyingToId, sendDefaultReaction } = getActions();
|
const { updateDraftReplyInfo, sendDefaultReaction } = getActions();
|
||||||
|
|
||||||
const [isQuickReactionVisible, markQuickReactionVisible, unmarkQuickReactionVisible] = useFlag();
|
const [isQuickReactionVisible, markQuickReactionVisible, unmarkQuickReactionVisible] = useFlag();
|
||||||
const [isSwiped, markSwiped, unmarkSwiped] = useFlag();
|
const [isSwiped, markSwiped, unmarkSwiped] = useFlag();
|
||||||
@ -138,7 +138,7 @@ export default function useOuterHandlers(
|
|||||||
function handleContainerDoubleClick() {
|
function handleContainerDoubleClick() {
|
||||||
if (IS_TOUCH_ENV || !canReply) return;
|
if (IS_TOUCH_ENV || !canReply) return;
|
||||||
|
|
||||||
setReplyingToId({ messageId });
|
updateDraftReplyInfo({ replyToMsgId: messageId });
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopPropagation(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
function stopPropagation(e: React.MouseEvent<HTMLDivElement, MouseEvent>) {
|
||||||
@ -172,14 +172,14 @@ export default function useOuterHandlers(
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setReplyingToId({ messageId });
|
updateDraftReplyInfo({ replyToMsgId: messageId });
|
||||||
|
|
||||||
setTimeout(unmarkSwiped, Math.max(0, SWIPE_ANIMATION_DURATION - (Date.now() - startedAt)));
|
setTimeout(unmarkSwiped, Math.max(0, SWIPE_ANIMATION_DURATION - (Date.now() - startedAt)));
|
||||||
startedAt = undefined;
|
startedAt = undefined;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}, [
|
}, [
|
||||||
containerRef, isInSelectMode, messageId, setReplyingToId, markSwiped, unmarkSwiped, canReply, isContextMenuShown,
|
containerRef, isInSelectMode, messageId, markSwiped, unmarkSwiped, canReply, isContextMenuShown,
|
||||||
getIsMessageListReady,
|
getIsMessageListReady,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
import React, { memo, useMemo } from '../../../lib/teact/teact';
|
||||||
import { getActions, withGlobal } from '../../../global';
|
import { getActions, withGlobal } from '../../../global';
|
||||||
|
|
||||||
import type { ApiApplyBoostInfo, ApiChat } from '../../../api/types';
|
import type { ApiChat, ApiMyBoost } from '../../../api/types';
|
||||||
import type { TabState } from '../../../global/types';
|
import type { TabState } from '../../../global/types';
|
||||||
|
|
||||||
import { getChatTitle } from '../../../global/helpers';
|
import { getChatTitle } from '../../../global/helpers';
|
||||||
@ -26,7 +26,7 @@ import Modal from '../../ui/Modal';
|
|||||||
import styles from './BoostModal.module.scss';
|
import styles from './BoostModal.module.scss';
|
||||||
|
|
||||||
type LoadedParams = {
|
type LoadedParams = {
|
||||||
applyInfo?: ApiApplyBoostInfo;
|
boost?: ApiMyBoost;
|
||||||
leftText: string;
|
leftText: string;
|
||||||
rightText?: string;
|
rightText?: string;
|
||||||
value: string;
|
value: string;
|
||||||
@ -93,7 +93,7 @@ const BoostModal = ({
|
|||||||
const {
|
const {
|
||||||
isStatusLoaded,
|
isStatusLoaded,
|
||||||
isBoosted,
|
isBoosted,
|
||||||
applyInfo,
|
boost,
|
||||||
title,
|
title,
|
||||||
leftText,
|
leftText,
|
||||||
rightText,
|
rightText,
|
||||||
@ -112,6 +112,8 @@ const BoostModal = ({
|
|||||||
level, currentLevelBoosts, hasMyBoost,
|
level, currentLevelBoosts, hasMyBoost,
|
||||||
} = info.boostStatus;
|
} = info.boostStatus;
|
||||||
|
|
||||||
|
const firstBoost = info?.myBoosts && getFirstAvailableBoost(info.myBoosts);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
boosts,
|
boosts,
|
||||||
currentLevel,
|
currentLevel,
|
||||||
@ -120,7 +122,7 @@ const BoostModal = ({
|
|||||||
remainingBoosts,
|
remainingBoosts,
|
||||||
} = getBoostProgressInfo(info.boostStatus, true);
|
} = getBoostProgressInfo(info.boostStatus, true);
|
||||||
|
|
||||||
const hasBoost = hasMyBoost || info.applyInfo?.type === 'already';
|
const hasBoost = hasMyBoost;
|
||||||
const isJustUpgraded = boosts === currentLevelBoosts && hasBoost;
|
const isJustUpgraded = boosts === currentLevelBoosts && hasBoost;
|
||||||
|
|
||||||
const left = lang('BoostsLevel', currentLevel);
|
const left = lang('BoostsLevel', currentLevel);
|
||||||
@ -159,16 +161,17 @@ const BoostModal = ({
|
|||||||
progress: levelProgress,
|
progress: levelProgress,
|
||||||
remainingBoosts,
|
remainingBoosts,
|
||||||
descriptionText: description,
|
descriptionText: description,
|
||||||
applyInfo: info.applyInfo,
|
boost: firstBoost,
|
||||||
isBoosted: hasBoost,
|
isBoosted: hasBoost,
|
||||||
};
|
};
|
||||||
}, [chat, chatTitle, info, lang]);
|
}, [chat, chatTitle, info, lang]);
|
||||||
|
|
||||||
const isBoostDisabled = !applyInfo && isCurrentUserPremium;
|
const isBoostDisabled = !boost && isCurrentUserPremium;
|
||||||
|
const isReplacingBoost = boost?.chatId && boost.chatId !== info?.chatId;
|
||||||
|
|
||||||
const handleApplyBoost = useLastCallback(() => {
|
const handleApplyBoost = useLastCallback(() => {
|
||||||
closeReplaceModal();
|
closeReplaceModal();
|
||||||
applyBoost({ chatId: chat!.id });
|
applyBoost({ chatId: chat!.id, slots: [boost!.slot] });
|
||||||
requestConfetti();
|
requestConfetti();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -179,8 +182,11 @@ const BoostModal = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleButtonClick = useLastCallback(() => {
|
const handleButtonClick = useLastCallback(() => {
|
||||||
if (!isCurrentUserPremium) {
|
if (!boost) {
|
||||||
openPremiumDialog();
|
if (!isCurrentUserPremium) {
|
||||||
|
openPremiumDialog();
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,17 +195,17 @@ const BoostModal = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (applyInfo?.type === 'ok') {
|
if (boost.cooldownUntil) {
|
||||||
handleApplyBoost();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (applyInfo?.type === 'replace') {
|
|
||||||
openReplaceModal();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (applyInfo?.type === 'wait') {
|
|
||||||
openWaitDialog();
|
openWaitDialog();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isReplacingBoost) {
|
||||||
|
openReplaceModal();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleApplyBoost();
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleCloseClick = useLastCallback(() => {
|
const handleCloseClick = useLastCallback(() => {
|
||||||
@ -249,7 +255,7 @@ const BoostModal = ({
|
|||||||
onClose={closeBoostModal}
|
onClose={closeBoostModal}
|
||||||
>
|
>
|
||||||
{renderContent()}
|
{renderContent()}
|
||||||
{applyInfo?.type === 'replace' && boostedChatTitle && (
|
{isReplacingBoost && boostedChatTitle && (
|
||||||
<Modal
|
<Modal
|
||||||
isOpen={isReplaceModalOpen}
|
isOpen={isReplaceModalOpen}
|
||||||
className={styles.replaceModal}
|
className={styles.replaceModal}
|
||||||
@ -277,7 +283,7 @@ const BoostModal = ({
|
|||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
{applyInfo?.type === 'wait' && (
|
{boost?.cooldownUntil && (
|
||||||
<ConfirmDialog
|
<ConfirmDialog
|
||||||
isOpen={isWaitDialogOpen}
|
isOpen={isWaitDialogOpen}
|
||||||
isOnlyConfirm
|
isOnlyConfirm
|
||||||
@ -289,7 +295,7 @@ const BoostModal = ({
|
|||||||
{renderText(
|
{renderText(
|
||||||
lang(
|
lang(
|
||||||
'ChannelBoost.Error.BoostTooOftenText',
|
'ChannelBoost.Error.BoostTooOftenText',
|
||||||
formatDateInFuture(lang, getServerTime(), applyInfo.waitUntil),
|
formatDateInFuture(lang, getServerTime(), boost.cooldownUntil),
|
||||||
),
|
),
|
||||||
['simple_markdown', 'emoji'],
|
['simple_markdown', 'emoji'],
|
||||||
)}
|
)}
|
||||||
@ -310,11 +316,15 @@ const BoostModal = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function getFirstAvailableBoost(myBoosts: ApiMyBoost[]) {
|
||||||
|
return myBoosts.find((boost) => !boost.chatId) || myBoosts.sort((a, b) => a.date - b.date)[0];
|
||||||
|
}
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global, { info }): StateProps => {
|
(global, { info }): StateProps => {
|
||||||
const chat = info && selectChat(global, info?.chatId);
|
const chat = info && selectChat(global, info?.chatId);
|
||||||
const boostedChat = info?.applyInfo?.type === 'replace'
|
const firstBoost = info?.myBoosts && getFirstAvailableBoost(info.myBoosts);
|
||||||
? selectChat(global, info.applyInfo.boostedChatId) : undefined;
|
const boostedChat = firstBoost?.chatId ? selectChat(global, firstBoost?.chatId) : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
chat,
|
chat,
|
||||||
|
|||||||
@ -107,6 +107,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
|||||||
} = getActions();
|
} = getActions();
|
||||||
const [mainButton, setMainButton] = useState<WebAppButton | undefined>();
|
const [mainButton, setMainButton] = useState<WebAppButton | undefined>();
|
||||||
const [isBackButtonVisible, setIsBackButtonVisible] = useState(false);
|
const [isBackButtonVisible, setIsBackButtonVisible] = useState(false);
|
||||||
|
const [isSettingsButtonVisible, setIsSettingsButtonVisible] = useState(false);
|
||||||
|
|
||||||
const [backgroundColor, setBackgroundColor] = useState<string>();
|
const [backgroundColor, setBackgroundColor] = useState<string>();
|
||||||
const [headerColor, setHeaderColor] = useState<string>();
|
const [headerColor, setHeaderColor] = useState<string>();
|
||||||
@ -136,7 +137,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
const {
|
const {
|
||||||
url, buttonText, queryId, replyToMessageId, threadId,
|
url, buttonText, queryId, replyInfo,
|
||||||
} = webApp || {};
|
} = webApp || {};
|
||||||
const isOpen = Boolean(url);
|
const isOpen = Boolean(url);
|
||||||
const isSimple = Boolean(buttonText);
|
const isSimple = Boolean(buttonText);
|
||||||
@ -152,8 +153,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
|||||||
botId: bot!.id,
|
botId: bot!.id,
|
||||||
queryId: queryId!,
|
queryId: queryId!,
|
||||||
peerId: chat!.id,
|
peerId: chat!.id,
|
||||||
replyToMessageId,
|
replyInfo,
|
||||||
threadId,
|
|
||||||
});
|
});
|
||||||
}, queryId ? PROLONG_INTERVAL : undefined, true);
|
}, queryId ? PROLONG_INTERVAL : undefined, true);
|
||||||
|
|
||||||
@ -345,6 +345,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
|||||||
setIsRequestingWriteAccess(false);
|
setIsRequestingWriteAccess(false);
|
||||||
setMainButton(undefined);
|
setMainButton(undefined);
|
||||||
setIsBackButtonVisible(false);
|
setIsBackButtonVisible(false);
|
||||||
|
setIsSettingsButtonVisible(false);
|
||||||
setBackgroundColor(themeParams.bg_color);
|
setBackgroundColor(themeParams.bg_color);
|
||||||
setHeaderColor(themeParams.bg_color);
|
setHeaderColor(themeParams.bg_color);
|
||||||
markUnloaded();
|
markUnloaded();
|
||||||
@ -363,6 +364,10 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
|||||||
setIsBackButtonVisible(eventData.is_visible);
|
setIsBackButtonVisible(eventData.is_visible);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (eventType === 'web_app_setup_settings_button') {
|
||||||
|
setIsSettingsButtonVisible(eventData.is_visible);
|
||||||
|
}
|
||||||
|
|
||||||
if (eventType === 'web_app_set_background_color') {
|
if (eventType === 'web_app_set_background_color') {
|
||||||
const themeParams = extractCurrentThemeParams();
|
const themeParams = extractCurrentThemeParams();
|
||||||
const color = validateHexColor(eventData.color) ? eventData.color : themeParams.bg_color;
|
const color = validateHexColor(eventData.color) ? eventData.color : themeParams.bg_color;
|
||||||
@ -520,7 +525,7 @@ const WebAppModal: FC<OwnProps & StateProps> = ({
|
|||||||
<MenuItem icon="bots" onClick={openBotChat}>{lang('BotWebViewOpenBot')}</MenuItem>
|
<MenuItem icon="bots" onClick={openBotChat}>{lang('BotWebViewOpenBot')}</MenuItem>
|
||||||
)}
|
)}
|
||||||
<MenuItem icon="reload" onClick={handleRefreshClick}>{lang('WebApp.ReloadPage')}</MenuItem>
|
<MenuItem icon="reload" onClick={handleRefreshClick}>{lang('WebApp.ReloadPage')}</MenuItem>
|
||||||
{attachBot?.hasSettings && (
|
{isSettingsButtonVisible && (
|
||||||
<MenuItem icon="settings" onClick={handleSettingsButtonClick}>
|
<MenuItem icon="settings" onClick={handleSettingsButtonClick}>
|
||||||
{lang('Settings')}
|
{lang('Settings')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import '../../styles/mixins';
|
@use '../../styles/mixins';
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -19,7 +19,7 @@
|
|||||||
justify-content: center;
|
justify-content: center;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|
||||||
@include side-panel-section;
|
@include mixins.side-panel-section;
|
||||||
}
|
}
|
||||||
|
|
||||||
.general {
|
.general {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import '../../styles/mixins';
|
@use '../../styles/mixins';
|
||||||
|
|
||||||
.Profile {
|
.Profile {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
> .profile-info > .ChatExtra {
|
> .profile-info > .ChatExtra {
|
||||||
padding: 0.875rem 0.5rem 0.5rem;
|
padding: 0.875rem 0.5rem 0.5rem;
|
||||||
@include side-panel-section;
|
@include mixins.side-panel-section;
|
||||||
|
|
||||||
.narrow {
|
.narrow {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import "../../../styles/mixins";
|
@use "../../../styles/mixins";
|
||||||
|
|
||||||
.Management {
|
.Management {
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
.section {
|
.section {
|
||||||
padding: 1rem 1.5rem;
|
padding: 1rem 1.5rem;
|
||||||
@include side-panel-section;
|
@include mixins.side-panel-section;
|
||||||
|
|
||||||
&.wide {
|
&.wide {
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
@ -165,7 +165,7 @@
|
|||||||
padding: 0 1rem 0.25rem 0.75rem;
|
padding: 0 1rem 0.25rem 0.75rem;
|
||||||
margin-bottom: 0.625rem;
|
margin-bottom: 0.625rem;
|
||||||
|
|
||||||
@include side-panel-section;
|
@include mixins.side-panel-section;
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
@ -339,7 +339,7 @@
|
|||||||
margin: 0 -1.5rem;
|
margin: 0 -1.5rem;
|
||||||
padding: 0 1.5rem 1rem;
|
padding: 0 1.5rem 1rem;
|
||||||
|
|
||||||
@include side-panel-section;
|
@include mixins.side-panel-section;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section, .part {
|
.section, .part {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import '../../../styles/mixins';
|
@use '../../../styles/mixins';
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
@ -18,7 +18,7 @@
|
|||||||
|
|
||||||
.section {
|
.section {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
@include side-panel-section;
|
@include mixins.side-panel-section;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user :global(.status) {
|
.user :global(.status) {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import "../../styles/mixins";
|
@use "../../styles/mixins";
|
||||||
|
|
||||||
.root {
|
.root {
|
||||||
--color-story-meta: rgb(242, 242, 242);
|
--color-story-meta: rgb(242, 242, 242);
|
||||||
@ -532,7 +532,7 @@
|
|||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
|
|
||||||
@include adapt-padding-to-scrollbar(2rem);
|
@include mixins.adapt-padding-to-scrollbar(2rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.captionContent {
|
.captionContent {
|
||||||
@ -552,7 +552,7 @@
|
|||||||
.expanded {
|
.expanded {
|
||||||
transition: transform 400ms;
|
transition: transform 400ms;
|
||||||
|
|
||||||
@include gradient-border-top(2rem);
|
@include mixins.gradient-border-top(2rem);
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import "../../styles/mixins";
|
@use "../../styles/mixins";
|
||||||
|
|
||||||
.AvatarEditable {
|
.AvatarEditable {
|
||||||
label {
|
label {
|
||||||
@ -39,7 +39,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// @optimization The weirdest workaround for screen animation lag
|
// @optimization The weirdest workaround for screen animation lag
|
||||||
@include while-transition() {
|
@include mixins.while-transition() {
|
||||||
input,
|
input,
|
||||||
.icon,
|
.icon,
|
||||||
&::after {
|
&::after {
|
||||||
|
|||||||
@ -397,4 +397,8 @@
|
|||||||
&.premium {
|
&.premium {
|
||||||
background: var(--premium-gradient);
|
background: var(--premium-gradient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.rectangular {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,6 +43,7 @@ export type OwnProps = {
|
|||||||
tabIndex?: number;
|
tabIndex?: number;
|
||||||
isRtl?: boolean;
|
isRtl?: boolean;
|
||||||
isShiny?: boolean;
|
isShiny?: boolean;
|
||||||
|
isRectangular?: boolean;
|
||||||
withPremiumGradient?: boolean;
|
withPremiumGradient?: boolean;
|
||||||
noPreventDefault?: boolean;
|
noPreventDefault?: boolean;
|
||||||
shouldStopPropagation?: boolean;
|
shouldStopPropagation?: boolean;
|
||||||
@ -96,6 +97,7 @@ const Button: FC<OwnProps> = ({
|
|||||||
faded,
|
faded,
|
||||||
tabIndex,
|
tabIndex,
|
||||||
isRtl,
|
isRtl,
|
||||||
|
isRectangular,
|
||||||
noPreventDefault,
|
noPreventDefault,
|
||||||
shouldStopPropagation,
|
shouldStopPropagation,
|
||||||
style,
|
style,
|
||||||
@ -126,6 +128,7 @@ const Button: FC<OwnProps> = ({
|
|||||||
backgroundImage && 'with-image',
|
backgroundImage && 'with-image',
|
||||||
isShiny && 'shiny',
|
isShiny && 'shiny',
|
||||||
withPremiumGradient && 'premium',
|
withPremiumGradient && 'premium',
|
||||||
|
isRectangular && 'rectangular',
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleClick = useLastCallback((e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
|
const handleClick = useLastCallback((e: ReactMouseEvent<HTMLButtonElement, MouseEvent>) => {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
@import "../../styles/mixins";
|
@use "../../styles/mixins";
|
||||||
|
|
||||||
@mixin thumb-styles() {
|
@mixin thumb-styles() {
|
||||||
background: var(--slider-color);
|
background: var(--slider-color);
|
||||||
@ -86,7 +86,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reset range input browser styles
|
// Reset range input browser styles
|
||||||
@include reset-range();
|
@include mixins.reset-range();
|
||||||
|
|
||||||
// Apply custom styles
|
// Apply custom styles
|
||||||
input[type="range"] {
|
input[type="range"] {
|
||||||
|
|||||||
@ -52,7 +52,7 @@ export const CUSTOM_EMOJI_PREVIEW_CACHE_DISABLED = false;
|
|||||||
export const CUSTOM_EMOJI_PREVIEW_CACHE_NAME = 'tt-custom-emoji-preview';
|
export const CUSTOM_EMOJI_PREVIEW_CACHE_NAME = 'tt-custom-emoji-preview';
|
||||||
export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB
|
export const MEDIA_CACHE_MAX_BYTES = 512 * 1024; // 512 KB
|
||||||
export const CUSTOM_BG_CACHE_NAME = 'tt-custom-bg';
|
export const CUSTOM_BG_CACHE_NAME = 'tt-custom-bg';
|
||||||
export const LANG_CACHE_NAME = 'tt-lang-packs-v24';
|
export const LANG_CACHE_NAME = 'tt-lang-packs-v25';
|
||||||
export const ASSET_CACHE_NAME = 'tt-assets';
|
export const ASSET_CACHE_NAME = 'tt-assets';
|
||||||
export const AUTODOWNLOAD_FILESIZE_MB_LIMITS = [1, 5, 10, 50, 100, 500];
|
export const AUTODOWNLOAD_FILESIZE_MB_LIMITS = [1, 5, 10, 50, 100, 500];
|
||||||
export const DATA_BROADCAST_CHANNEL_NAME = 'tt-global';
|
export const DATA_BROADCAST_CHANNEL_NAME = 'tt-global';
|
||||||
@ -311,6 +311,9 @@ export const LIGHT_THEME_BG_COLOR = '#99BA92';
|
|||||||
export const DARK_THEME_BG_COLOR = '#0F0F0F';
|
export const DARK_THEME_BG_COLOR = '#0F0F0F';
|
||||||
export const DEFAULT_PATTERN_COLOR = '#4A8E3A8C';
|
export const DEFAULT_PATTERN_COLOR = '#4A8E3A8C';
|
||||||
export const DARK_THEME_PATTERN_COLOR = '#0A0A0A8C';
|
export const DARK_THEME_PATTERN_COLOR = '#0A0A0A8C';
|
||||||
|
export const PEER_COLOR_BG_OPACITY = '1a';
|
||||||
|
export const PEER_COLOR_BG_ACTIVE_OPACITY = '2b';
|
||||||
|
export const PEER_COLOR_GRADIENT_STEP = 5; // px
|
||||||
export const MAX_UPLOAD_FILEPART_SIZE = 524288;
|
export const MAX_UPLOAD_FILEPART_SIZE = 524288;
|
||||||
|
|
||||||
// Group calls
|
// Group calls
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
import type {
|
import type {
|
||||||
ApiChat, ApiChatType, ApiContact, ApiPeer, ApiUrlAuthResult,
|
ApiChat, ApiChatType, ApiContact, ApiInputMessageReplyInfo, ApiPeer, ApiUrlAuthResult,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import type { InlineBotSettings } from '../../../types';
|
import type { InlineBotSettings } from '../../../types';
|
||||||
import type { RequiredGlobalActions } from '../../index';
|
import type { RequiredGlobalActions } from '../../index';
|
||||||
import type { ActionReturnType, GlobalState, TabArgs } from '../../types';
|
import type { ActionReturnType, GlobalState, TabArgs } from '../../types';
|
||||||
import { MAIN_THREAD_ID } from '../../../api/types';
|
|
||||||
|
|
||||||
import { GENERAL_REFETCH_INTERVAL } from '../../../config';
|
import { GENERAL_REFETCH_INTERVAL } from '../../../config';
|
||||||
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
import { getCurrentTabId } from '../../../util/establishMultitabRole';
|
||||||
@ -23,8 +22,8 @@ import { addChats, addUsers, removeBlockedUser } from '../../reducers';
|
|||||||
import { replaceInlineBotSettings, replaceInlineBotsIsLoading } from '../../reducers/bots';
|
import { replaceInlineBotSettings, replaceInlineBotsIsLoading } from '../../reducers/bots';
|
||||||
import { updateTabState } from '../../reducers/tabs';
|
import { updateTabState } from '../../reducers/tabs';
|
||||||
import {
|
import {
|
||||||
selectBot, selectChat, selectChatMessage, selectCurrentChat, selectCurrentMessageList, selectIsTrustedBot,
|
selectBot, selectChat, selectChatMessage, selectCurrentChat, selectCurrentMessageList, selectDraft,
|
||||||
selectReplyingToId, selectSendAs, selectTabState, selectThreadTopMessageId, selectUser, selectUserFullInfo,
|
selectIsTrustedBot, selectMessageReplyInfo, selectSendAs, selectTabState, selectUser, selectUserFullInfo,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
|
|
||||||
const GAMEE_URL = 'https://prizes.gamee.com/';
|
const GAMEE_URL = 'https://prizes.gamee.com/';
|
||||||
@ -185,11 +184,11 @@ addActionHandler('sendBotCommand', (global, actions, payload): ActionReturnType
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { threadId } = currentMessageList;
|
const { threadId } = currentMessageList;
|
||||||
actions.setReplyingToId({ messageId: undefined, tabId });
|
actions.resetDraftReplyInfo({ tabId });
|
||||||
actions.clearWebPagePreview({ tabId });
|
actions.clearWebPagePreview({ tabId });
|
||||||
|
|
||||||
void sendBotCommand(
|
void sendBotCommand(
|
||||||
chat, threadId, command, selectReplyingToId(global, chat.id, threadId), selectSendAs(global, chat.id),
|
chat, command, selectDraft(global, chat.id, threadId)?.replyInfo, selectSendAs(global, chat.id),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -210,7 +209,7 @@ addActionHandler('restartBot', async (global, actions, payload): Promise<void> =
|
|||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = removeBlockedUser(global, bot.id);
|
global = removeBlockedUser(global, bot.id);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
void sendBotCommand(chat, MAIN_THREAD_ID, '/start', undefined, selectSendAs(global, chatId));
|
void sendBotCommand(chat, '/start', undefined, selectSendAs(global, chatId));
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('loadTopInlineBots', async (global): Promise<void> => {
|
addActionHandler('loadTopInlineBots', async (global): Promise<void> => {
|
||||||
@ -339,21 +338,18 @@ addActionHandler('sendInlineBotResult', (global, actions, payload): ActionReturn
|
|||||||
|
|
||||||
const { chatId, threadId } = messageList;
|
const { chatId, threadId } = messageList;
|
||||||
const chat = selectChat(global, chatId)!;
|
const chat = selectChat(global, chatId)!;
|
||||||
const replyingToId = selectReplyingToId(global, chatId, threadId);
|
const draftReplyInfo = selectDraft(global, chatId, threadId)?.replyInfo;
|
||||||
const replyingToMessage = replyingToId ? selectChatMessage(global, chatId, replyingToId) : undefined;
|
|
||||||
const replyingToTopId = (chat.isForum || threadId !== MAIN_THREAD_ID)
|
|
||||||
? selectThreadTopMessageId(global, chatId, threadId)
|
|
||||||
: replyingToMessage?.replyToTopMessageId || replyingToMessage?.replyToMessageId;
|
|
||||||
|
|
||||||
actions.setReplyingToId({ messageId: undefined, tabId });
|
const replyInfo = selectMessageReplyInfo(global, chatId, threadId, draftReplyInfo);
|
||||||
|
|
||||||
|
actions.resetDraftReplyInfo({ tabId });
|
||||||
actions.clearWebPagePreview({ tabId });
|
actions.clearWebPagePreview({ tabId });
|
||||||
|
|
||||||
void callApi('sendInlineBotResult', {
|
void callApi('sendInlineBotResult', {
|
||||||
chat,
|
chat,
|
||||||
resultId: id,
|
resultId: id,
|
||||||
queryId,
|
queryId,
|
||||||
replyingTo: replyingToId || replyingToTopId,
|
replyInfo,
|
||||||
replyingToTopId,
|
|
||||||
sendAs: selectSendAs(global, chatId),
|
sendAs: selectSendAs(global, chatId),
|
||||||
isSilent,
|
isSilent,
|
||||||
scheduleDate: scheduledAt,
|
scheduleDate: scheduledAt,
|
||||||
@ -531,7 +527,9 @@ addActionHandler('requestWebView', async (global, actions, payload): Promise<voi
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { chatId, threadId } = currentMessageList;
|
const { chatId, threadId } = currentMessageList;
|
||||||
const reply = chatId && selectReplyingToId(global, chatId, threadId);
|
const draftReplyInfo = chatId ? selectDraft(global, chatId, threadId)?.replyInfo : undefined;
|
||||||
|
const replyInfo = selectMessageReplyInfo(global, chatId, threadId, draftReplyInfo);
|
||||||
|
|
||||||
const sendAs = selectSendAs(global, chatId);
|
const sendAs = selectSendAs(global, chatId);
|
||||||
const result = await callApi('requestWebView', {
|
const result = await callApi('requestWebView', {
|
||||||
url,
|
url,
|
||||||
@ -539,8 +537,7 @@ addActionHandler('requestWebView', async (global, actions, payload): Promise<voi
|
|||||||
peer,
|
peer,
|
||||||
theme,
|
theme,
|
||||||
isSilent,
|
isSilent,
|
||||||
replyToMessageId: reply || undefined,
|
replyInfo,
|
||||||
threadId,
|
|
||||||
isFromBotMenu,
|
isFromBotMenu,
|
||||||
startParam,
|
startParam,
|
||||||
sendAs,
|
sendAs,
|
||||||
@ -557,8 +554,7 @@ addActionHandler('requestWebView', async (global, actions, payload): Promise<voi
|
|||||||
url: webViewUrl,
|
url: webViewUrl,
|
||||||
botId,
|
botId,
|
||||||
queryId,
|
queryId,
|
||||||
replyToMessageId: reply || undefined,
|
replyInfo,
|
||||||
threadId,
|
|
||||||
buttonText,
|
buttonText,
|
||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
@ -626,8 +622,7 @@ addActionHandler('requestAppWebView', async (global, actions, payload): Promise<
|
|||||||
|
|
||||||
addActionHandler('prolongWebView', async (global, actions, payload): Promise<void> => {
|
addActionHandler('prolongWebView', async (global, actions, payload): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
botId, peerId, isSilent, replyToMessageId, queryId, threadId,
|
botId, peerId, isSilent, replyInfo, queryId, tabId = getCurrentTabId(),
|
||||||
tabId = getCurrentTabId(),
|
|
||||||
} = payload;
|
} = payload;
|
||||||
|
|
||||||
const bot = selectUser(global, botId);
|
const bot = selectUser(global, botId);
|
||||||
@ -641,8 +636,7 @@ addActionHandler('prolongWebView', async (global, actions, payload): Promise<voi
|
|||||||
bot,
|
bot,
|
||||||
peer,
|
peer,
|
||||||
isSilent,
|
isSilent,
|
||||||
replyToMessageId,
|
replyInfo,
|
||||||
threadId,
|
|
||||||
queryId,
|
queryId,
|
||||||
sendAs,
|
sendAs,
|
||||||
});
|
});
|
||||||
@ -769,7 +763,8 @@ addActionHandler('callAttachBot', (global, actions, payload): ActionReturnType =
|
|||||||
|
|
||||||
const isFromBotMenu = !bot;
|
const isFromBotMenu = !bot;
|
||||||
const shouldDisplayDisclaimer = (!isFromBotMenu && !global.attachMenu.bots[bot.id])
|
const shouldDisplayDisclaimer = (!isFromBotMenu && !global.attachMenu.bots[bot.id])
|
||||||
|| (isFromSideMenu && (bot?.isInactive || bot?.isDisclaimerNeeded));
|
|| bot?.isInactive || bot?.isDisclaimerNeeded;
|
||||||
|
|
||||||
if (!isFromConfirm && shouldDisplayDisclaimer) {
|
if (!isFromConfirm && shouldDisplayDisclaimer) {
|
||||||
return updateTabState(global, {
|
return updateTabState(global, {
|
||||||
requestedAttachBotInstall: {
|
requestedAttachBotInstall: {
|
||||||
@ -1068,14 +1063,11 @@ async function searchInlineBot<T extends GlobalState>(global: T, {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function sendBotCommand(
|
async function sendBotCommand(
|
||||||
chat: ApiChat, threadId = MAIN_THREAD_ID, command: string, replyingTo?: number, sendAs?: ApiPeer,
|
chat: ApiChat, command: string, replyInfo?: ApiInputMessageReplyInfo, sendAs?: ApiPeer,
|
||||||
) {
|
) {
|
||||||
await callApi('sendMessage', {
|
await callApi('sendMessage', {
|
||||||
chat,
|
chat,
|
||||||
replyingTo: replyingTo ? {
|
replyInfo,
|
||||||
replyingTo,
|
|
||||||
replyingToTopId: threadId,
|
|
||||||
} : undefined,
|
|
||||||
text: command,
|
text: command,
|
||||||
sendAs,
|
sendAs,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -294,7 +294,7 @@ addActionHandler('loadAllChats', async (global, actions, payload): Promise<void>
|
|||||||
let i = 0;
|
let i = 0;
|
||||||
|
|
||||||
const getOrderDate = (chat: ApiChat) => {
|
const getOrderDate = (chat: ApiChat) => {
|
||||||
return chat.lastMessage?.date || chat.joinDate;
|
return chat.lastMessage?.date || chat.creationDate;
|
||||||
};
|
};
|
||||||
|
|
||||||
while (shouldReplace || !global.chats.isFullyLoaded[listType]) {
|
while (shouldReplace || !global.chats.isFullyLoaded[listType]) {
|
||||||
@ -1831,8 +1831,7 @@ addActionHandler('loadTopics', async (global, actions, payload): Promise<void> =
|
|||||||
global = updateTopics(global, chatId, result.count, result.topics);
|
global = updateTopics(global, chatId, result.count, result.topics);
|
||||||
global = updateListedTopicIds(global, chatId, result.topics.map((topic) => topic.id));
|
global = updateListedTopicIds(global, chatId, result.topics.map((topic) => topic.id));
|
||||||
Object.entries(result.draftsById || {}).forEach(([threadId, draft]) => {
|
Object.entries(result.draftsById || {}).forEach(([threadId, draft]) => {
|
||||||
global = replaceThreadParam(global, chatId, Number(threadId), 'draft', draft?.formattedText);
|
global = replaceThreadParam(global, chatId, Number(threadId), 'draft', draft);
|
||||||
global = replaceThreadParam(global, chatId, Number(threadId), 'replyingToId', draft?.replyingToId);
|
|
||||||
});
|
});
|
||||||
Object.entries(result.readInboxMessageIdByTopicId || {}).forEach(([topicId, messageId]) => {
|
Object.entries(result.readInboxMessageIdByTopicId || {}).forEach(([topicId, messageId]) => {
|
||||||
global = updateThreadInfo(global, chatId, Number(topicId), { lastReadInboxMessageId: messageId });
|
global = updateThreadInfo(global, chatId, Number(topicId), { lastReadInboxMessageId: messageId });
|
||||||
@ -2419,18 +2418,6 @@ async function loadChats(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const idsToUpdateReplyingToId = isFullDraftSync ? result.chatIds : Object.keys(result.replyingToById);
|
|
||||||
idsToUpdateReplyingToId.forEach((chatId) => {
|
|
||||||
const replyingToById = result.replyingToById[chatId];
|
|
||||||
const thread = selectThread(global, chatId, MAIN_THREAD_ID);
|
|
||||||
|
|
||||||
if (!replyingToById && !thread) return;
|
|
||||||
|
|
||||||
global = replaceThreadParam(
|
|
||||||
global, chatId, MAIN_THREAD_ID, 'replyingToId', replyingToById,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (chatIds.length === 0 && !global.chats.isFullyLoaded[listType]) {
|
if (chatIds.length === 0 && !global.chats.isFullyLoaded[listType]) {
|
||||||
global = {
|
global = {
|
||||||
...global,
|
...global,
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
import type {
|
import type {
|
||||||
ApiAttachment,
|
ApiAttachment,
|
||||||
ApiChat,
|
ApiChat,
|
||||||
|
ApiInputMessageReplyInfo,
|
||||||
|
ApiInputReplyInfo,
|
||||||
|
ApiInputStoryReplyInfo,
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiMessageEntity,
|
ApiMessageEntity,
|
||||||
ApiNewPoll,
|
ApiNewPoll,
|
||||||
@ -9,12 +12,11 @@ import type {
|
|||||||
ApiSticker,
|
ApiSticker,
|
||||||
ApiStory,
|
ApiStory,
|
||||||
ApiStorySkipped,
|
ApiStorySkipped,
|
||||||
ApiTypeReplyTo,
|
|
||||||
ApiVideo,
|
ApiVideo,
|
||||||
} from '../../../api/types';
|
} from '../../../api/types';
|
||||||
import type { RequiredGlobalActions } from '../../index';
|
import type { RequiredGlobalActions } from '../../index';
|
||||||
import type {
|
import type {
|
||||||
ActionReturnType, GlobalState, TabArgs,
|
ActionReturnType, ApiDraft, GlobalState, TabArgs,
|
||||||
} from '../../types';
|
} from '../../types';
|
||||||
import { MAIN_THREAD_ID, MESSAGE_DELETED } from '../../../api/types';
|
import { MAIN_THREAD_ID, MESSAGE_DELETED } from '../../../api/types';
|
||||||
import { LoadMoreDirection } from '../../../types';
|
import { LoadMoreDirection } from '../../../types';
|
||||||
@ -93,12 +95,12 @@ import {
|
|||||||
selectIsCurrentUserPremium,
|
selectIsCurrentUserPremium,
|
||||||
selectLanguageCode,
|
selectLanguageCode,
|
||||||
selectListedIds,
|
selectListedIds,
|
||||||
|
selectMessageReplyInfo,
|
||||||
selectNoWebPage,
|
selectNoWebPage,
|
||||||
selectOutlyingListByMessageId,
|
selectOutlyingListByMessageId,
|
||||||
selectPeerStory,
|
selectPeerStory,
|
||||||
selectPinnedIds,
|
selectPinnedIds,
|
||||||
selectRealLastReadId,
|
selectRealLastReadId,
|
||||||
selectReplyingToId,
|
|
||||||
selectScheduledMessage,
|
selectScheduledMessage,
|
||||||
selectSendAs,
|
selectSendAs,
|
||||||
selectSponsoredMessage,
|
selectSponsoredMessage,
|
||||||
@ -268,27 +270,30 @@ addActionHandler('sendMessage', (global, actions, payload): ActionReturnType =>
|
|||||||
}
|
}
|
||||||
|
|
||||||
const chat = selectChat(global, chatId!)!;
|
const chat = selectChat(global, chatId!)!;
|
||||||
const replyingToId = !isStoryReply ? selectReplyingToId(global, chatId!, threadId!) : undefined;
|
const draftReplyInfo = !isStoryReply ? selectDraft(global, chatId!, threadId!)?.replyInfo : undefined;
|
||||||
const replyingToMessage = replyingToId ? selectChatMessage(global, chatId!, replyingToId) : undefined;
|
|
||||||
|
|
||||||
const replyingToTopId = chat.isForum
|
const storyReplyInfo = isStoryReply ? {
|
||||||
? selectThreadTopMessageId(global, chatId!, threadId!)
|
type: 'story',
|
||||||
: replyingToMessage?.replyToTopMessageId || replyingToMessage?.replyToMessageId;
|
userId: storyPeerId!,
|
||||||
const replyingTo: ApiTypeReplyTo | undefined = replyingToId
|
storyId: storyId!,
|
||||||
? { replyingTo: replyingToId, replyingToTopId }
|
} satisfies ApiInputStoryReplyInfo : undefined;
|
||||||
: (isStoryReply ? { userId: storyPeerId!, storyId: storyId! } : undefined);
|
|
||||||
|
const messageReplyInfo = selectMessageReplyInfo(global, chatId!, threadId!, draftReplyInfo);
|
||||||
|
|
||||||
|
const replyInfo = storyReplyInfo || messageReplyInfo;
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
...payload,
|
...payload,
|
||||||
chat,
|
chat,
|
||||||
currentThreadId: threadId!,
|
replyInfo,
|
||||||
replyingTo,
|
|
||||||
noWebPage: selectNoWebPage(global, chatId!, threadId!),
|
noWebPage: selectNoWebPage(global, chatId!, threadId!),
|
||||||
sendAs: selectSendAs(global, chatId!),
|
sendAs: selectSendAs(global, chatId!),
|
||||||
};
|
};
|
||||||
|
|
||||||
actions.setReplyingToId({ messageId: undefined, tabId });
|
if (!isStoryReply) {
|
||||||
actions.clearWebPagePreview({ tabId });
|
actions.resetDraftReplyInfo({ tabId });
|
||||||
|
actions.clearWebPagePreview({ tabId });
|
||||||
|
}
|
||||||
|
|
||||||
const isSingle = !payload.attachments || payload.attachments.length <= 1;
|
const isSingle = !payload.attachments || payload.attachments.length <= 1;
|
||||||
const isGrouped = !isSingle && payload.shouldGroupMessages;
|
const isGrouped = !isSingle && payload.shouldGroupMessages;
|
||||||
@ -332,7 +337,7 @@ addActionHandler('sendMessage', (global, actions, payload): ActionReturnType =>
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
const {
|
const {
|
||||||
text, entities, attachments, replyingTo: replyingToForFirstMessage, ...commonParams
|
text, entities, attachments, replyInfo: replyToForFirstMessage, ...commonParams
|
||||||
} = params;
|
} = params;
|
||||||
|
|
||||||
if (text) {
|
if (text) {
|
||||||
@ -340,7 +345,7 @@ addActionHandler('sendMessage', (global, actions, payload): ActionReturnType =>
|
|||||||
...commonParams,
|
...commonParams,
|
||||||
text,
|
text,
|
||||||
entities,
|
entities,
|
||||||
replyingTo: replyingToForFirstMessage,
|
replyInfo: replyToForFirstMessage,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -393,63 +398,120 @@ addActionHandler('cancelSendingMessage', (global, actions, payload): ActionRetur
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('saveDraft', async (global, actions, payload): Promise<void> => {
|
addActionHandler('saveDraft', (global, actions, payload): ActionReturnType => {
|
||||||
const {
|
const {
|
||||||
chatId, threadId, draft,
|
chatId, threadId, text,
|
||||||
} = payload;
|
} = payload;
|
||||||
if (!draft) {
|
if (!text) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { text, entities } = draft;
|
const currentDraft = selectDraft(global, chatId, threadId);
|
||||||
const chat = selectChat(global, chatId)!;
|
|
||||||
const user = selectUser(global, chatId)!;
|
|
||||||
if (user && isDeletedUser(user)) return;
|
|
||||||
|
|
||||||
draft.isLocal = true;
|
const newDraft: ApiDraft = {
|
||||||
global = replaceThreadParam(global, chatId, threadId, 'draft', draft);
|
text,
|
||||||
global = updateChat(global, chatId, { draftDate: Math.round(Date.now() / 1000) });
|
replyInfo: currentDraft?.replyInfo,
|
||||||
|
};
|
||||||
|
|
||||||
|
saveDraft(global, chatId, threadId, newDraft);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('clearDraft', (global, actions, payload): ActionReturnType => {
|
||||||
|
const {
|
||||||
|
chatId, threadId = MAIN_THREAD_ID, isLocalOnly, shouldKeepReply,
|
||||||
|
} = payload;
|
||||||
|
const currentDraft = selectDraft(global, chatId, threadId);
|
||||||
|
if (!currentDraft) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newDraft: ApiDraft | undefined = shouldKeepReply ? {
|
||||||
|
replyInfo: currentDraft.replyInfo,
|
||||||
|
} : undefined;
|
||||||
|
|
||||||
|
if (!isLocalOnly) {
|
||||||
|
saveDraft(global, chatId, threadId, newDraft);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('updateDraftReplyInfo', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId(), ...update } = payload;
|
||||||
|
const currentMessageList = selectCurrentMessageList(global, tabId);
|
||||||
|
if (!currentMessageList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { chatId, threadId } = currentMessageList;
|
||||||
|
|
||||||
|
const currentDraft = selectDraft(global, chatId, threadId);
|
||||||
|
|
||||||
|
const updatedReplyInfo = {
|
||||||
|
type: 'message',
|
||||||
|
...currentDraft?.replyInfo,
|
||||||
|
...update,
|
||||||
|
} as ApiInputMessageReplyInfo;
|
||||||
|
|
||||||
|
if (!updatedReplyInfo.replyToMsgId) return;
|
||||||
|
|
||||||
|
const newDraft: ApiDraft = {
|
||||||
|
...currentDraft,
|
||||||
|
replyInfo: updatedReplyInfo,
|
||||||
|
};
|
||||||
|
|
||||||
|
saveDraft(global, chatId, threadId, newDraft);
|
||||||
|
});
|
||||||
|
|
||||||
|
addActionHandler('resetDraftReplyInfo', (global, actions, payload): ActionReturnType => {
|
||||||
|
const { tabId = getCurrentTabId() } = payload || {};
|
||||||
|
const currentMessageList = selectCurrentMessageList(global, tabId);
|
||||||
|
if (!currentMessageList) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { chatId, threadId } = currentMessageList;
|
||||||
|
|
||||||
|
const currentDraft = selectDraft(global, chatId, threadId);
|
||||||
|
const newDraft: ApiDraft | undefined = !currentDraft?.text ? undefined : {
|
||||||
|
...currentDraft,
|
||||||
|
replyInfo: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
saveDraft(global, chatId, threadId, newDraft);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function saveDraft<T extends GlobalState>(global: T, chatId: string, threadId: number, draft?: ApiDraft) {
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
|
const user = selectUser(global, chatId);
|
||||||
|
if (!chat || (user && isDeletedUser(user))) return;
|
||||||
|
|
||||||
|
const replyInfo = selectMessageReplyInfo(global, chatId, threadId, draft?.replyInfo);
|
||||||
|
|
||||||
|
const newDraft: ApiDraft | undefined = draft ? {
|
||||||
|
...draft,
|
||||||
|
replyInfo,
|
||||||
|
date: Math.floor(Date.now() / 1000),
|
||||||
|
isLocal: true,
|
||||||
|
} : undefined;
|
||||||
|
|
||||||
|
global = replaceThreadParam(global, chatId, threadId, 'draft', newDraft);
|
||||||
|
global = updateChat(global, chatId, { draftDate: newDraft?.date });
|
||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
const result = await callApi('saveDraft', {
|
const result = await callApi('saveDraft', {
|
||||||
chat,
|
chat,
|
||||||
text,
|
draft: newDraft,
|
||||||
entities,
|
|
||||||
replyToMsgId: selectReplyingToId(global, chatId, threadId),
|
|
||||||
threadId: selectThreadTopMessageId(global, chatId, threadId),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result) {
|
if (result && newDraft) {
|
||||||
draft.isLocal = false;
|
newDraft.isLocal = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
global = replaceThreadParam(global, chatId, threadId, 'draft', draft);
|
global = replaceThreadParam(global, chatId, threadId, 'draft', newDraft);
|
||||||
global = updateChat(global, chatId, { draftDate: Math.round(Date.now() / 1000) });
|
global = updateChat(global, chatId, { draftDate: newDraft?.date });
|
||||||
|
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
});
|
}
|
||||||
|
|
||||||
addActionHandler('clearDraft', (global, actions, payload): ActionReturnType => {
|
|
||||||
const {
|
|
||||||
chatId, threadId = MAIN_THREAD_ID, localOnly,
|
|
||||||
} = payload;
|
|
||||||
if (!selectDraft(global, chatId, threadId)) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const chat = selectChat(global, chatId)!;
|
|
||||||
|
|
||||||
if (!localOnly) {
|
|
||||||
void callApi('clearDraft', chat, selectThreadTopMessageId(global, chatId, threadId));
|
|
||||||
}
|
|
||||||
|
|
||||||
global = replaceThreadParam(global, chatId, threadId, 'draft', undefined);
|
|
||||||
global = updateChat(global, chatId, { draftDate: undefined });
|
|
||||||
|
|
||||||
return global;
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('toggleMessageWebPage', (global, actions, payload): ActionReturnType => {
|
addActionHandler('toggleMessageWebPage', (global, actions, payload): ActionReturnType => {
|
||||||
const { chatId, threadId, noWebPage } = payload!;
|
const { chatId, threadId, noWebPage } = payload!;
|
||||||
@ -837,10 +899,11 @@ addActionHandler('forwardMessages', (global, actions, payload): ActionReturnType
|
|||||||
const { text, entities } = message.content.text || {};
|
const { text, entities } = message.content.text || {};
|
||||||
const { sticker, poll } = message.content;
|
const { sticker, poll } = message.content;
|
||||||
|
|
||||||
|
const replyInfo = selectMessageReplyInfo(global, toChat.id, toThreadId);
|
||||||
|
|
||||||
void sendMessage(global, {
|
void sendMessage(global, {
|
||||||
chat: toChat,
|
chat: toChat,
|
||||||
replyingTo: toThreadId ? { replyingTo: toThreadId, replyingToTopId: toThreadId } : undefined,
|
replyInfo,
|
||||||
currentThreadId: toThreadId || MAIN_THREAD_ID,
|
|
||||||
text,
|
text,
|
||||||
entities,
|
entities,
|
||||||
sticker,
|
sticker,
|
||||||
@ -1103,7 +1166,7 @@ async function loadMessage<T extends GlobalState>(
|
|||||||
const replyMessage = selectChatMessage(global, chat.id, replyOriginForId);
|
const replyMessage = selectChatMessage(global, chat.id, replyOriginForId);
|
||||||
global = updateChatMessage(global, chat.id, replyOriginForId, {
|
global = updateChatMessage(global, chat.id, replyOriginForId, {
|
||||||
...replyMessage,
|
...replyMessage,
|
||||||
replyToMessageId: undefined,
|
replyInfo: undefined,
|
||||||
});
|
});
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
}
|
}
|
||||||
@ -1174,7 +1237,7 @@ async function sendMessage<T extends GlobalState>(global: T, params: {
|
|||||||
chat: ApiChat;
|
chat: ApiChat;
|
||||||
text?: string;
|
text?: string;
|
||||||
entities?: ApiMessageEntity[];
|
entities?: ApiMessageEntity[];
|
||||||
replyingTo?: ApiTypeReplyTo;
|
replyInfo?: ApiInputReplyInfo;
|
||||||
attachment?: ApiAttachment;
|
attachment?: ApiAttachment;
|
||||||
sticker?: ApiSticker;
|
sticker?: ApiSticker;
|
||||||
story?: ApiStory | ApiStorySkipped;
|
story?: ApiStory | ApiStorySkipped;
|
||||||
@ -1183,7 +1246,6 @@ async function sendMessage<T extends GlobalState>(global: T, params: {
|
|||||||
isSilent?: boolean;
|
isSilent?: boolean;
|
||||||
scheduledAt?: number;
|
scheduledAt?: number;
|
||||||
sendAs?: ApiPeer;
|
sendAs?: ApiPeer;
|
||||||
currentThreadId: number;
|
|
||||||
groupedId?: string;
|
groupedId?: string;
|
||||||
}) {
|
}) {
|
||||||
let localId: number | undefined;
|
let localId: number | undefined;
|
||||||
@ -1208,29 +1270,10 @@ async function sendMessage<T extends GlobalState>(global: T, params: {
|
|||||||
} : undefined;
|
} : undefined;
|
||||||
|
|
||||||
// @optimization
|
// @optimization
|
||||||
if (params.replyingTo || IS_IOS) {
|
if (params.replyInfo || IS_IOS) {
|
||||||
await rafPromise();
|
await rafPromise();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.currentThreadId === undefined) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (params.currentThreadId !== MAIN_THREAD_ID) {
|
|
||||||
if (!params.replyingTo || !('replyingTo' in params.replyingTo)) {
|
|
||||||
params.replyingTo = {
|
|
||||||
replyingTo: params.currentThreadId,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!params.replyingTo.replyingTo) {
|
|
||||||
params.replyingTo.replyingTo = params.currentThreadId;
|
|
||||||
}
|
|
||||||
if (params.replyingTo.replyingTo && !params.replyingTo.replyingToTopId) {
|
|
||||||
params.replyingTo.replyingToTopId = params.currentThreadId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await callApi('sendMessage', params, progressCallback);
|
await callApi('sendMessage', params, progressCallback);
|
||||||
|
|
||||||
if (progressCallback && localId) {
|
if (progressCallback && localId) {
|
||||||
@ -1533,7 +1576,6 @@ addActionHandler('forwardStory', (global, actions, payload): ActionReturnType =>
|
|||||||
const { text, entities } = (story as ApiStory).content.text || {};
|
const { text, entities } = (story as ApiStory).content.text || {};
|
||||||
void sendMessage(global, {
|
void sendMessage(global, {
|
||||||
chat: toChat,
|
chat: toChat,
|
||||||
currentThreadId: MAIN_THREAD_ID,
|
|
||||||
text,
|
text,
|
||||||
entities,
|
entities,
|
||||||
story,
|
story,
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { setTimeFormat } from '../../../util/langProvider';
|
|||||||
import { requestPermission, subscribe, unsubscribe } from '../../../util/notifications';
|
import { requestPermission, subscribe, unsubscribe } from '../../../util/notifications';
|
||||||
import requestActionTimeout from '../../../util/requestActionTimeout';
|
import requestActionTimeout from '../../../util/requestActionTimeout';
|
||||||
import { getServerTime } from '../../../util/serverTime';
|
import { getServerTime } from '../../../util/serverTime';
|
||||||
|
import { updatePeerColors } from '../../../util/theme';
|
||||||
import { callApi } from '../../../api/gramjs';
|
import { callApi } from '../../../api/gramjs';
|
||||||
import { buildApiInputPrivacyRules } from '../../helpers';
|
import { buildApiInputPrivacyRules } from '../../helpers';
|
||||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||||
@ -622,6 +623,10 @@ addActionHandler('loadAppConfig', async (global, actions, payload): Promise<void
|
|||||||
appConfig,
|
appConfig,
|
||||||
};
|
};
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
|
if (appConfig.peerColors) {
|
||||||
|
updatePeerColors(appConfig.peerColors, appConfig.darkPeerColors);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('loadConfig', async (global): Promise<void> => {
|
addActionHandler('loadConfig', async (global): Promise<void> => {
|
||||||
|
|||||||
@ -535,23 +535,20 @@ addActionHandler('openBoostModal', async (global, actions, payload): Promise<voi
|
|||||||
}, tabId);
|
}, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
|
|
||||||
const applyInfoResult = await callApi('fetchCanApplyBoost', {
|
const myBoosts = await callApi('fetchMyBoosts');
|
||||||
chat,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!applyInfoResult?.info) return;
|
if (!myBoosts) return;
|
||||||
|
|
||||||
const applyInfo = applyInfoResult.info;
|
|
||||||
|
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
const tabState = selectTabState(global, tabId);
|
const tabState = selectTabState(global, tabId);
|
||||||
if (!tabState.boostModal) return;
|
if (!tabState.boostModal) return;
|
||||||
|
|
||||||
global = addChats(global, buildCollectionByKey(applyInfoResult.chats, 'id'));
|
global = addChats(global, buildCollectionByKey(myBoosts.chats, 'id'));
|
||||||
|
global = addUsers(global, buildCollectionByKey(myBoosts.users, 'id'));
|
||||||
global = updateTabState(global, {
|
global = updateTabState(global, {
|
||||||
boostModal: {
|
boostModal: {
|
||||||
...tabState.boostModal,
|
...tabState.boostModal,
|
||||||
applyInfo,
|
myBoosts: myBoosts.boosts,
|
||||||
},
|
},
|
||||||
}, tabId);
|
}, tabId);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
@ -643,12 +640,13 @@ addActionHandler('loadMoreBoosters', async (global, actions, payload): Promise<v
|
|||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('applyBoost', async (global, actions, payload): Promise<void> => {
|
addActionHandler('applyBoost', async (global, actions, payload): Promise<void> => {
|
||||||
const { chatId, tabId = getCurrentTabId() } = payload;
|
const { chatId, slots, tabId = getCurrentTabId() } = payload;
|
||||||
|
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (!chat) return;
|
if (!chat) return;
|
||||||
|
|
||||||
const result = await callApi('applyBoost', {
|
const result = await callApi('applyBoost', {
|
||||||
|
slots,
|
||||||
chat,
|
chat,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,7 @@ import {
|
|||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectDraft,
|
selectDraft,
|
||||||
selectEditingDraft,
|
selectEditingDraft,
|
||||||
selectEditingId, selectReplyingToId,
|
selectEditingId,
|
||||||
selectTabState,
|
selectTabState,
|
||||||
selectThreadInfo,
|
selectThreadInfo,
|
||||||
} from '../../selectors';
|
} from '../../selectors';
|
||||||
@ -111,7 +111,6 @@ async function loadAndReplaceMessages<T extends GlobalState>(global: T, actions:
|
|||||||
draft: selectDraft(global, chatId, Number(threadId)),
|
draft: selectDraft(global, chatId, Number(threadId)),
|
||||||
editingId: selectEditingId(global, chatId, Number(threadId)),
|
editingId: selectEditingId(global, chatId, Number(threadId)),
|
||||||
editingDraft: selectEditingDraft(global, chatId, Number(threadId)),
|
editingDraft: selectEditingDraft(global, chatId, Number(threadId)),
|
||||||
replyingToId: selectReplyingToId(global, chatId, Number(threadId)),
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return acc2;
|
return acc2;
|
||||||
|
|||||||
@ -379,16 +379,15 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
|
|
||||||
case 'draftMessage': {
|
case 'draftMessage': {
|
||||||
const {
|
const {
|
||||||
chatId, formattedText, date, replyingToId, threadId,
|
chatId, threadId, draft,
|
||||||
} = update;
|
} = update;
|
||||||
const chat = global.chats.byId[chatId];
|
const chat = global.chats.byId[chatId];
|
||||||
if (!chat) {
|
if (!chat) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
global = replaceThreadParam(global, chatId, threadId || MAIN_THREAD_ID, 'draft', formattedText);
|
global = replaceThreadParam(global, chatId, threadId || MAIN_THREAD_ID, 'draft', draft);
|
||||||
global = replaceThreadParam(global, chatId, threadId || MAIN_THREAD_ID, 'replyingToId', replyingToId);
|
global = updateChat(global, chatId, { draftDate: draft?.date });
|
||||||
global = updateChat(global, chatId, { draftDate: date });
|
|
||||||
return global;
|
return global;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,6 +17,7 @@ import {
|
|||||||
checkIfHasUnreadReactions, getMessageContent, getMessageText, isActionMessage,
|
checkIfHasUnreadReactions, getMessageContent, getMessageText, isActionMessage,
|
||||||
isMessageLocal, isUserId,
|
isMessageLocal, isUserId,
|
||||||
} from '../../helpers';
|
} from '../../helpers';
|
||||||
|
import { getMessageReplyInfo } from '../../helpers/replies';
|
||||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||||
import {
|
import {
|
||||||
addViewportId,
|
addViewportId,
|
||||||
@ -84,18 +85,19 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const newMessage = selectChatMessage(global, chatId, id)!;
|
const newMessage = selectChatMessage(global, chatId, id)!;
|
||||||
|
const replyInfo = getMessageReplyInfo(newMessage);
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
if (chat?.isForum
|
if (chat?.isForum
|
||||||
&& newMessage.isTopicReply
|
&& replyInfo?.isForumTopic
|
||||||
&& !selectTopicFromMessage(global, newMessage)
|
&& !selectTopicFromMessage(global, newMessage)
|
||||||
&& newMessage.replyToMessageId) {
|
&& replyInfo.replyToMsgId) {
|
||||||
actions.loadTopicById({ chatId, topicId: newMessage.replyToMessageId });
|
actions.loadTopicById({ chatId, topicId: replyInfo.replyToMsgId });
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
Object.values(global.byTabId).forEach(({ id: tabId }) => {
|
||||||
const isLocal = isMessageLocal(message as ApiMessage);
|
const isLocal = isMessageLocal(message as ApiMessage);
|
||||||
if (selectIsMessageInCurrentMessageList(global, chatId, message as ApiMessage, tabId)) {
|
if (selectIsMessageInCurrentMessageList(global, chatId, message as ApiMessage, tabId)) {
|
||||||
if (isLocal && message.isOutgoing && !(message.content?.action) && !message.replyToStoryId
|
if (isLocal && message.isOutgoing && !(message.content?.action) && !replyInfo?.replyToMsgId
|
||||||
&& !message.content?.storyData) {
|
&& !message.content?.storyData) {
|
||||||
const currentMessageList = selectCurrentMessageList(global, tabId);
|
const currentMessageList = selectCurrentMessageList(global, tabId);
|
||||||
if (currentMessageList) {
|
if (currentMessageList) {
|
||||||
@ -122,7 +124,10 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
global = getGlobal();
|
global = getGlobal();
|
||||||
if (shouldForceReply) {
|
if (shouldForceReply) {
|
||||||
global = replaceThreadParam(global, chatId, MAIN_THREAD_ID, 'replyingToId', id);
|
actions.updateDraftReplyInfo({
|
||||||
|
replyToMsgId: id,
|
||||||
|
tabId,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
global = updateChatLastMessage(global, chatId, newMessage);
|
global = updateChatLastMessage(global, chatId, newMessage);
|
||||||
setGlobal(global);
|
setGlobal(global);
|
||||||
@ -773,16 +778,18 @@ function updateThreadUnread<T extends GlobalState>(
|
|||||||
) {
|
) {
|
||||||
const { chatId } = message;
|
const { chatId } = message;
|
||||||
|
|
||||||
|
const replyInfo = getMessageReplyInfo(message);
|
||||||
|
|
||||||
const { threadInfo } = selectThreadByMessage(global, message) || {};
|
const { threadInfo } = selectThreadByMessage(global, message) || {};
|
||||||
|
|
||||||
if (!threadInfo && message.replyToMessageId) {
|
if (!threadInfo && replyInfo?.replyToMsgId) {
|
||||||
const originMessage = selectChatMessage(global, chatId, message.replyToMessageId);
|
const originMessage = selectChatMessage(global, chatId, replyInfo.replyToMsgId);
|
||||||
if (originMessage) {
|
if (originMessage) {
|
||||||
global = updateThreadUnreadFromForwardedMessage(global, originMessage, chatId, message.id, isDeleting);
|
global = updateThreadUnreadFromForwardedMessage(global, originMessage, chatId, message.id, isDeleting);
|
||||||
} else {
|
} else {
|
||||||
actions.loadMessage({
|
actions.loadMessage({
|
||||||
chatId,
|
chatId,
|
||||||
messageId: message.replyToMessageId,
|
messageId: replyInfo.replyToMsgId,
|
||||||
threadUpdate: {
|
threadUpdate: {
|
||||||
isDeleting,
|
isDeleting,
|
||||||
lastMessageId: message.id,
|
lastMessageId: message.id,
|
||||||
|
|||||||
@ -112,7 +112,7 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
|||||||
case 'updateWebViewResultSent':
|
case 'updateWebViewResultSent':
|
||||||
Object.values(global.byTabId).forEach((tabState) => {
|
Object.values(global.byTabId).forEach((tabState) => {
|
||||||
if (tabState.webApp?.queryId === update.queryId) {
|
if (tabState.webApp?.queryId === update.queryId) {
|
||||||
actions.setReplyingToId({ messageId: undefined, tabId: tabState.id });
|
actions.resetDraftReplyInfo({ tabId: tabState.id });
|
||||||
actions.closeWebApp({ tabId: tabState.id });
|
actions.closeWebApp({ tabId: tabState.id });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@ -42,12 +42,12 @@ import {
|
|||||||
selectChatScheduledMessages,
|
selectChatScheduledMessages,
|
||||||
selectCurrentChat,
|
selectCurrentChat,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
|
selectDraft,
|
||||||
selectForwardedMessageIdsByGroupId,
|
selectForwardedMessageIdsByGroupId,
|
||||||
selectIsRightColumnShown,
|
selectIsRightColumnShown,
|
||||||
selectIsViewportNewest,
|
selectIsViewportNewest,
|
||||||
selectMessageIdsByGroupId,
|
selectMessageIdsByGroupId,
|
||||||
selectPinnedIds,
|
selectPinnedIds,
|
||||||
selectReplyingToId,
|
|
||||||
selectReplyStack,
|
selectReplyStack,
|
||||||
selectRequestedChatTranslationLanguage,
|
selectRequestedChatTranslationLanguage,
|
||||||
selectRequestedMessageTranslationLanguage,
|
selectRequestedMessageTranslationLanguage,
|
||||||
@ -77,17 +77,6 @@ addActionHandler('setScrollOffset', (global, actions, payload): ActionReturnType
|
|||||||
return replaceTabThreadParam(global, chatId, threadId, 'scrollOffset', scrollOffset, tabId);
|
return replaceTabThreadParam(global, chatId, threadId, 'scrollOffset', scrollOffset, tabId);
|
||||||
});
|
});
|
||||||
|
|
||||||
addActionHandler('setReplyingToId', (global, actions, payload): ActionReturnType => {
|
|
||||||
const { messageId, tabId = getCurrentTabId() } = payload;
|
|
||||||
const currentMessageList = selectCurrentMessageList(global, tabId);
|
|
||||||
if (!currentMessageList) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
const { chatId, threadId } = currentMessageList;
|
|
||||||
|
|
||||||
return replaceThreadParam(global, chatId, threadId, 'replyingToId', messageId);
|
|
||||||
});
|
|
||||||
|
|
||||||
addActionHandler('setEditingId', (global, actions, payload): ActionReturnType => {
|
addActionHandler('setEditingId', (global, actions, payload): ActionReturnType => {
|
||||||
const { messageId, tabId = getCurrentTabId() } = payload;
|
const { messageId, tabId = getCurrentTabId() } = payload;
|
||||||
const currentMessageList = selectCurrentMessageList(global, tabId);
|
const currentMessageList = selectCurrentMessageList(global, tabId);
|
||||||
@ -148,12 +137,12 @@ addActionHandler('replyToNextMessage', (global, actions, payload): ActionReturnT
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const replyingToId = selectReplyingToId(global, chatId, threadId);
|
const replyInfo = selectDraft(global, chatId, threadId)?.replyInfo;
|
||||||
const isLatest = selectIsViewportNewest(global, chatId, threadId, tabId);
|
const isLatest = selectIsViewportNewest(global, chatId, threadId, tabId);
|
||||||
|
|
||||||
let messageId: number | undefined;
|
let messageId: number | undefined;
|
||||||
|
|
||||||
if (!isLatest || !replyingToId) {
|
if (!isLatest || !replyInfo) {
|
||||||
if (threadId === MAIN_THREAD_ID) {
|
if (threadId === MAIN_THREAD_ID) {
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
|
|
||||||
@ -165,13 +154,13 @@ addActionHandler('replyToNextMessage', (global, actions, payload): ActionReturnT
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const chatMessageKeys = Object.keys(chatMessages);
|
const chatMessageKeys = Object.keys(chatMessages);
|
||||||
const indexOfCurrent = chatMessageKeys.indexOf(replyingToId.toString());
|
const indexOfCurrent = chatMessageKeys.indexOf(replyInfo.toString());
|
||||||
const newIndex = indexOfCurrent + targetIndexDelta;
|
const newIndex = indexOfCurrent + targetIndexDelta;
|
||||||
messageId = newIndex <= chatMessageKeys.length + 1 && newIndex >= 0
|
messageId = newIndex <= chatMessageKeys.length + 1 && newIndex >= 0
|
||||||
? Number(chatMessageKeys[newIndex])
|
? Number(chatMessageKeys[newIndex])
|
||||||
: undefined;
|
: undefined;
|
||||||
}
|
}
|
||||||
actions.setReplyingToId({ messageId, tabId });
|
actions.updateDraftReplyInfo({ replyToMsgId: messageId, tabId });
|
||||||
actions.focusMessage({
|
actions.focusMessage({
|
||||||
chatId,
|
chatId,
|
||||||
threadId,
|
threadId,
|
||||||
|
|||||||
@ -34,6 +34,7 @@ import {
|
|||||||
selectChatMessages,
|
selectChatMessages,
|
||||||
selectCurrentMessageList,
|
selectCurrentMessageList,
|
||||||
selectThreadOriginChat,
|
selectThreadOriginChat,
|
||||||
|
selectViewportIds,
|
||||||
selectVisibleUsers,
|
selectVisibleUsers,
|
||||||
} from './selectors';
|
} from './selectors';
|
||||||
|
|
||||||
@ -348,15 +349,24 @@ function reduceChats<T extends GlobalState>(global: T): GlobalState['chats'] {
|
|||||||
}),
|
}),
|
||||||
).map(({ chatId }) => chatId);
|
).map(({ chatId }) => chatId);
|
||||||
|
|
||||||
const chatStoriesChannelIds = currentChatIds
|
const messagesChatIds = compact(Object.values(global.byTabId).flatMap(({ id: tabId }) => {
|
||||||
.flatMap((chatId) => Object.values(selectChatMessages(global, chatId) || {}))
|
const messageList = selectCurrentMessageList(global, tabId);
|
||||||
.map((message) => message.content.storyData?.peerId || message.content.webPage?.story?.peerId)
|
if (!messageList) return undefined;
|
||||||
.filter((id): id is string => Boolean(id) && !isUserId(id));
|
|
||||||
|
const messages = selectChatMessages(global, messageList.chatId);
|
||||||
|
const viewportIds = selectViewportIds(global, messageList.chatId, messageList.threadId, tabId);
|
||||||
|
return viewportIds?.map((id) => {
|
||||||
|
const message = messages[id];
|
||||||
|
const content = message?.content;
|
||||||
|
const replyPeer = message.replyInfo?.type === 'message' && message.replyInfo.replyToPeerId;
|
||||||
|
return content.storyData?.peerId || content.webPage?.story?.peerId || replyPeer;
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
const idsToSave = unique([
|
const idsToSave = unique([
|
||||||
...currentUserId ? [currentUserId] : [],
|
...currentUserId ? [currentUserId] : [],
|
||||||
...currentChatIds,
|
...currentChatIds,
|
||||||
...chatStoriesChannelIds,
|
...messagesChatIds,
|
||||||
...getOrderedIds(ALL_FOLDER_ID) || [],
|
...getOrderedIds(ALL_FOLDER_ID) || [],
|
||||||
...getOrderedIds(ARCHIVED_FOLDER_ID) || [],
|
...getOrderedIds(ARCHIVED_FOLDER_ID) || [],
|
||||||
...global.recentlyFoundChatIds || [],
|
...global.recentlyFoundChatIds || [],
|
||||||
|
|||||||
@ -19,13 +19,13 @@ import {
|
|||||||
import { formatDateToString, formatTime } from '../../util/dateFormat';
|
import { formatDateToString, formatTime } from '../../util/dateFormat';
|
||||||
import { orderBy } from '../../util/iteratees';
|
import { orderBy } from '../../util/iteratees';
|
||||||
import { prepareSearchWordsForNeedle } from '../../util/searchWords';
|
import { prepareSearchWordsForNeedle } from '../../util/searchWords';
|
||||||
|
import { getGlobal } from '..';
|
||||||
import { getMainUsername, getUserFirstOrLastName } from './users';
|
import { getMainUsername, getUserFirstOrLastName } from './users';
|
||||||
|
|
||||||
const FOREVER_BANNED_DATE = Date.now() / 1000 + 31622400; // 366 days
|
const FOREVER_BANNED_DATE = Date.now() / 1000 + 31622400; // 366 days
|
||||||
|
|
||||||
const VERIFIED_PRIORITY_BASE = 3e9;
|
const VERIFIED_PRIORITY_BASE = 3e9;
|
||||||
const PINNED_PRIORITY_BASE = 3e8;
|
const PINNED_PRIORITY_BASE = 3e8;
|
||||||
const USER_COLOR_KEYS = [1, 8, 5, 2, 7, 4, 6];
|
|
||||||
|
|
||||||
export function isUserId(entityId: string) {
|
export function isUserId(entityId: string) {
|
||||||
return !entityId.startsWith('-');
|
return !entityId.startsWith('-');
|
||||||
@ -39,6 +39,10 @@ export function toChannelId(mtpId: string) {
|
|||||||
return `-100${mtpId}`;
|
return `-100${mtpId}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isApiPeerChat(peer: ApiPeer): peer is ApiChat {
|
||||||
|
return 'title' in peer;
|
||||||
|
}
|
||||||
|
|
||||||
export function isChatGroup(chat: ApiChat) {
|
export function isChatGroup(chat: ApiChat) {
|
||||||
return isChatBasicGroup(chat) || isChatSuperGroup(chat);
|
return isChatBasicGroup(chat) || isChatSuperGroup(chat);
|
||||||
}
|
}
|
||||||
@ -467,9 +471,14 @@ export function getPeerIdDividend(peerId: string) {
|
|||||||
return Math.abs(Number(getCleanPeerId(peerId)));
|
return Math.abs(Number(getCleanPeerId(peerId)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/telegramdesktop/tdesktop/blob/371510cfe23b0bd226de8c076bc49248fbe40c26/Telegram/SourceFiles/data/data_peer.cpp#L53
|
|
||||||
export function getPeerColorKey(peer: ApiPeer | undefined) {
|
export function getPeerColorKey(peer: ApiPeer | undefined) {
|
||||||
const index = peer ? getPeerIdDividend(peer.id) % 7 : 0;
|
if (peer?.color) return peer.color;
|
||||||
|
|
||||||
return USER_COLOR_KEYS[index];
|
const index = peer ? getPeerIdDividend(peer.id) % 7 : 0;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPeerColorCount(peer: ApiPeer) {
|
||||||
|
const key = getPeerColorKey(peer);
|
||||||
|
return getGlobal().appConfig?.peerColors?.[key]?.length || 1;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
import type { ApiMessage } from '../../api/types';
|
import type { MediaContent } from '../../api/types';
|
||||||
import { ApiMessageEntityTypes } from '../../api/types';
|
import { ApiMessageEntityTypes } from '../../api/types';
|
||||||
|
|
||||||
import parseEmojiOnlyString from '../../util/parseEmojiOnlyString';
|
import parseEmojiOnlyString from '../../util/parseEmojiOnlyString';
|
||||||
|
|
||||||
export function getEmojiOnlyCountForMessage(content: ApiMessage['content'], groupedId?: string): number | undefined {
|
export function getEmojiOnlyCountForMessage(content: MediaContent, groupedId?: string): number | undefined {
|
||||||
if (!content.text) return undefined;
|
if (!content.text) return undefined;
|
||||||
return (
|
return (
|
||||||
!groupedId
|
!groupedId
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import type {
|
|||||||
ApiPhoto,
|
ApiPhoto,
|
||||||
ApiVideo,
|
ApiVideo,
|
||||||
ApiWebDocument,
|
ApiWebDocument,
|
||||||
|
MediaContent,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import { ApiMediaFormat } from '../../api/types';
|
import { ApiMediaFormat } from '../../api/types';
|
||||||
|
|
||||||
@ -22,6 +23,10 @@ import {
|
|||||||
import { getDocumentHasPreview } from '../../components/common/helpers/documentInfo';
|
import { getDocumentHasPreview } from '../../components/common/helpers/documentInfo';
|
||||||
import { getMessageKey, isMessageLocal, matchLinkInMessageText } from './messages';
|
import { getMessageKey, isMessageLocal, matchLinkInMessageText } from './messages';
|
||||||
|
|
||||||
|
type MediaContainer = {
|
||||||
|
content: MediaContent;
|
||||||
|
};
|
||||||
|
|
||||||
type Target =
|
type Target =
|
||||||
'micro'
|
'micro'
|
||||||
| 'pictogram'
|
| 'pictogram'
|
||||||
@ -30,11 +35,11 @@ type Target =
|
|||||||
| 'full'
|
| 'full'
|
||||||
| 'download';
|
| 'download';
|
||||||
|
|
||||||
export function getMessageContent(message: ApiMessage) {
|
export function getMessageContent(message: MediaContainer) {
|
||||||
return message.content;
|
return message.content;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasMessageMedia(message: ApiMessage) {
|
export function hasMessageMedia(message: MediaContainer) {
|
||||||
return Boolean((
|
return Boolean((
|
||||||
getMessagePhoto(message)
|
getMessagePhoto(message)
|
||||||
|| getMessageVideo(message)
|
|| getMessageVideo(message)
|
||||||
@ -48,91 +53,91 @@ export function hasMessageMedia(message: ApiMessage) {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessagePhoto(message: ApiMessage) {
|
export function getMessagePhoto(message: MediaContainer) {
|
||||||
return message.content.photo;
|
return message.content.photo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageActionPhoto(message: ApiMessage) {
|
export function getMessageActionPhoto(message: MediaContainer) {
|
||||||
return message.content.action?.type === 'suggestProfilePhoto' ? message.content.action.photo : undefined;
|
return message.content.action?.type === 'suggestProfilePhoto' ? message.content.action.photo : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageVideo(message: ApiMessage) {
|
export function getMessageVideo(message: MediaContainer) {
|
||||||
return message.content.video;
|
return message.content.video;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageRoundVideo(message: ApiMessage) {
|
export function getMessageRoundVideo(message: MediaContainer) {
|
||||||
const { video } = message.content;
|
const { video } = message.content;
|
||||||
|
|
||||||
return video?.isRound ? video : undefined;
|
return video?.isRound ? video : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageAction(message: ApiMessage) {
|
export function getMessageAction(message: MediaContainer) {
|
||||||
return message.content.action;
|
return message.content.action;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageAudio(message: ApiMessage) {
|
export function getMessageAudio(message: MediaContainer) {
|
||||||
return message.content.audio;
|
return message.content.audio;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageVoice(message: ApiMessage) {
|
export function getMessageVoice(message: MediaContainer) {
|
||||||
return message.content.voice;
|
return message.content.voice;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageSticker(message: ApiMessage) {
|
export function getMessageSticker(message: MediaContainer) {
|
||||||
return message.content.sticker;
|
return message.content.sticker;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageDocument(message: ApiMessage) {
|
export function getMessageDocument(message: MediaContainer) {
|
||||||
return message.content.document;
|
return message.content.document;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isMessageDocumentPhoto(message: ApiMessage) {
|
export function isMessageDocumentPhoto(message: MediaContainer) {
|
||||||
const document = getMessageDocument(message);
|
const document = getMessageDocument(message);
|
||||||
return document ? document.mediaType === 'photo' : undefined;
|
return document ? document.mediaType === 'photo' : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isMessageDocumentVideo(message: ApiMessage) {
|
export function isMessageDocumentVideo(message: MediaContainer) {
|
||||||
const document = getMessageDocument(message);
|
const document = getMessageDocument(message);
|
||||||
return document ? document.mediaType === 'video' : undefined;
|
return document ? document.mediaType === 'video' : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageContact(message: ApiMessage) {
|
export function getMessageContact(message: MediaContainer) {
|
||||||
return message.content.contact;
|
return message.content.contact;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessagePoll(message: ApiMessage) {
|
export function getMessagePoll(message: MediaContainer) {
|
||||||
return message.content.poll;
|
return message.content.poll;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageInvoice(message: ApiMessage) {
|
export function getMessageInvoice(message: MediaContainer) {
|
||||||
return message.content.invoice;
|
return message.content.invoice;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageLocation(message: ApiMessage) {
|
export function getMessageLocation(message: MediaContainer) {
|
||||||
return message.content.location;
|
return message.content.location;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageWebPage(message: ApiMessage) {
|
export function getMessageWebPage(message: MediaContainer) {
|
||||||
return message.content.webPage;
|
return message.content.webPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageWebPagePhoto(message: ApiMessage) {
|
export function getMessageWebPagePhoto(message: MediaContainer) {
|
||||||
return getMessageWebPage(message)?.photo;
|
return getMessageWebPage(message)?.photo;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageDocumentPhoto(message: ApiMessage) {
|
export function getMessageDocumentPhoto(message: MediaContainer) {
|
||||||
return isMessageDocumentPhoto(message) ? getMessageDocument(message) : undefined;
|
return isMessageDocumentPhoto(message) ? getMessageDocument(message) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageWebPageVideo(message: ApiMessage) {
|
export function getMessageWebPageVideo(message: MediaContainer) {
|
||||||
return getMessageWebPage(message)?.video;
|
return getMessageWebPage(message)?.video;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageDocumentVideo(message: ApiMessage) {
|
export function getMessageDocumentVideo(message: MediaContainer) {
|
||||||
return isMessageDocumentVideo(message) ? getMessageDocument(message) : undefined;
|
return isMessageDocumentVideo(message) ? getMessageDocument(message) : undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageMediaThumbnail(message: ApiMessage) {
|
export function getMessageMediaThumbnail(message: MediaContainer) {
|
||||||
const media = getMessagePhoto(message)
|
const media = getMessagePhoto(message)
|
||||||
|| getMessageVideo(message)
|
|| getMessageVideo(message)
|
||||||
|| getMessageDocument(message)
|
|| getMessageDocument(message)
|
||||||
@ -148,11 +153,11 @@ export function getMessageMediaThumbnail(message: ApiMessage) {
|
|||||||
return media.thumbnail;
|
return media.thumbnail;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageMediaThumbDataUri(message: ApiMessage) {
|
export function getMessageMediaThumbDataUri(message: MediaContainer) {
|
||||||
return getMessageMediaThumbnail(message)?.dataUri;
|
return getMessageMediaThumbnail(message)?.dataUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getMessageIsSpoiler(message: ApiMessage) {
|
export function getMessageIsSpoiler(message: MediaContainer) {
|
||||||
const media = getMessagePhoto(message)
|
const media = getMessagePhoto(message)
|
||||||
|| getMessageVideo(message);
|
|| getMessageVideo(message);
|
||||||
|
|
||||||
|
|||||||
@ -164,8 +164,8 @@ export function isOwnMessage(message: ApiMessage) {
|
|||||||
return message.isOutgoing;
|
return message.isOutgoing;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isReplyMessage(message: ApiMessage) {
|
export function isReplyToMessage(message: ApiMessage) {
|
||||||
return Boolean(message.replyToMessageId);
|
return Boolean(message.replyInfo?.type === 'message');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isForwardedMessage(message: ApiMessage) {
|
export function isForwardedMessage(message: ApiMessage) {
|
||||||
|
|||||||
13
src/global/helpers/replies.ts
Normal file
13
src/global/helpers/replies.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { ApiMessage, ApiMessageReplyInfo, ApiStoryReplyInfo } from '../../api/types';
|
||||||
|
|
||||||
|
export function getMessageReplyInfo(message: ApiMessage): ApiMessageReplyInfo | undefined {
|
||||||
|
const { replyInfo } = message;
|
||||||
|
if (!replyInfo || replyInfo.type !== 'message') return undefined;
|
||||||
|
return replyInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getStoryReplyInfo(message: ApiMessage): ApiStoryReplyInfo | undefined {
|
||||||
|
const { replyInfo } = message;
|
||||||
|
if (!replyInfo || replyInfo.type !== 'story') return undefined;
|
||||||
|
return replyInfo;
|
||||||
|
}
|
||||||
@ -9,6 +9,7 @@ import { cloneDeep } from '../util/iteratees';
|
|||||||
import { Bundles, loadBundle } from '../util/moduleLoader';
|
import { Bundles, loadBundle } from '../util/moduleLoader';
|
||||||
import { parseLocationHash } from '../util/routing';
|
import { parseLocationHash } from '../util/routing';
|
||||||
import { clearStoredSession } from '../util/sessions';
|
import { clearStoredSession } from '../util/sessions';
|
||||||
|
import { updatePeerColors } from '../util/theme';
|
||||||
import { IS_MULTITAB_SUPPORTED } from '../util/windowEnvironment';
|
import { IS_MULTITAB_SUPPORTED } from '../util/windowEnvironment';
|
||||||
import { updateTabState } from './reducers/tabs';
|
import { updateTabState } from './reducers/tabs';
|
||||||
import { initCache, loadCache } from './cache';
|
import { initCache, loadCache } from './cache';
|
||||||
@ -43,6 +44,10 @@ addActionHandler('initShared', (prevGlobal, actions, payload): ActionReturnType
|
|||||||
global.byTabId = prevGlobal.byTabId;
|
global.byTabId = prevGlobal.byTabId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (global.appConfig?.peerColors) {
|
||||||
|
updatePeerColors(global.appConfig.peerColors, global.appConfig.darkPeerColors);
|
||||||
|
}
|
||||||
|
|
||||||
return global;
|
return global;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,9 @@
|
|||||||
import type {
|
import type {
|
||||||
ApiChat,
|
ApiChat,
|
||||||
|
ApiInputMessageReplyInfo,
|
||||||
ApiMessage,
|
ApiMessage,
|
||||||
ApiMessageEntityCustomEmoji,
|
ApiMessageEntityCustomEmoji,
|
||||||
|
ApiMessageForwardInfo,
|
||||||
ApiMessageOutgoingStatus,
|
ApiMessageOutgoingStatus,
|
||||||
ApiPeer,
|
ApiPeer,
|
||||||
ApiStickerSetInfo,
|
ApiStickerSetInfo,
|
||||||
@ -49,6 +51,7 @@ import {
|
|||||||
isUserId,
|
isUserId,
|
||||||
isUserRightBanned,
|
isUserRightBanned,
|
||||||
} from '../helpers';
|
} from '../helpers';
|
||||||
|
import { getMessageReplyInfo } from '../helpers/replies';
|
||||||
import {
|
import {
|
||||||
selectChat, selectChatFullInfo, selectIsChatWithSelf, selectPeer, selectRequestedChatTranslationLanguage,
|
selectChat, selectChatFullInfo, selectIsChatWithSelf, selectPeer, selectRequestedChatTranslationLanguage,
|
||||||
} from './chats';
|
} from './chats';
|
||||||
@ -197,10 +200,6 @@ export function selectLastScrollOffset<T extends GlobalState>(global: T, chatId:
|
|||||||
return selectThreadParam(global, chatId, threadId, 'lastScrollOffset');
|
return selectThreadParam(global, chatId, threadId, 'lastScrollOffset');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectReplyingToId<T extends GlobalState>(global: T, chatId: string, threadId: number) {
|
|
||||||
return selectThreadParam(global, chatId, threadId, 'replyingToId');
|
|
||||||
}
|
|
||||||
|
|
||||||
export function selectEditingId<T extends GlobalState>(global: T, chatId: string, threadId: number) {
|
export function selectEditingId<T extends GlobalState>(global: T, chatId: string, threadId: number) {
|
||||||
return selectThreadParam(global, chatId, threadId, 'editingId');
|
return selectThreadParam(global, chatId, threadId, 'editingId');
|
||||||
}
|
}
|
||||||
@ -425,15 +424,9 @@ export function selectSender<T extends GlobalState>(global: T, message: ApiMessa
|
|||||||
return selectPeer(global, senderId);
|
return selectPeer(global, senderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectReplySender<T extends GlobalState>(global: T, message: ApiMessage, isForwarded = false) {
|
export function selectReplySender<T extends GlobalState>(
|
||||||
if (isForwarded) {
|
global: T, message: ApiMessage,
|
||||||
const { senderUserId, hiddenUserName } = message.forwardInfo || {};
|
) {
|
||||||
if (senderUserId) {
|
|
||||||
return selectPeer(global, senderUserId);
|
|
||||||
}
|
|
||||||
if (hiddenUserName) return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { senderId } = message;
|
const { senderId } = message;
|
||||||
if (!senderId) {
|
if (!senderId) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -442,6 +435,18 @@ export function selectReplySender<T extends GlobalState>(global: T, message: Api
|
|||||||
return selectPeer(global, senderId);
|
return selectPeer(global, senderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectSenderFromHeader<T extends GlobalState>(
|
||||||
|
global: T,
|
||||||
|
header: ApiMessageForwardInfo,
|
||||||
|
) {
|
||||||
|
const { senderUserId } = header;
|
||||||
|
if (senderUserId) {
|
||||||
|
return selectPeer(global, senderUserId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export function selectForwardedSender<T extends GlobalState>(
|
export function selectForwardedSender<T extends GlobalState>(
|
||||||
global: T, message: ApiMessage,
|
global: T, message: ApiMessage,
|
||||||
): ApiPeer | undefined {
|
): ApiPeer | undefined {
|
||||||
@ -507,9 +512,8 @@ export function selectCanDeleteTopic<T extends GlobalState>(global: T, chatId: s
|
|||||||
|
|
||||||
export function selectThreadIdFromMessage<T extends GlobalState>(global: T, message: ApiMessage): number {
|
export function selectThreadIdFromMessage<T extends GlobalState>(global: T, message: ApiMessage): number {
|
||||||
const chat = selectChat(global, message.chatId);
|
const chat = selectChat(global, message.chatId);
|
||||||
const {
|
const { content } = message;
|
||||||
replyToMessageId, replyToTopMessageId, isTopicReply, content,
|
const { replyToMsgId, replyToTopId, isForumTopic } = getMessageReplyInfo(message) || {};
|
||||||
} = message;
|
|
||||||
if ('action' in content && content.action?.type === 'topicCreate') {
|
if ('action' in content && content.action?.type === 'topicCreate') {
|
||||||
return message.id;
|
return message.id;
|
||||||
}
|
}
|
||||||
@ -518,12 +522,12 @@ export function selectThreadIdFromMessage<T extends GlobalState>(global: T, mess
|
|||||||
if (chat && isChatBasicGroup(chat)) return MAIN_THREAD_ID;
|
if (chat && isChatBasicGroup(chat)) return MAIN_THREAD_ID;
|
||||||
|
|
||||||
if (chat && isChatSuperGroup(chat)) {
|
if (chat && isChatSuperGroup(chat)) {
|
||||||
return replyToTopMessageId || replyToMessageId || MAIN_THREAD_ID;
|
return replyToTopId || replyToMsgId || MAIN_THREAD_ID;
|
||||||
}
|
}
|
||||||
return MAIN_THREAD_ID;
|
return MAIN_THREAD_ID;
|
||||||
}
|
}
|
||||||
if (!isTopicReply) return GENERAL_TOPIC_ID;
|
if (!isForumTopic) return GENERAL_TOPIC_ID;
|
||||||
return replyToTopMessageId || replyToMessageId || GENERAL_TOPIC_ID;
|
return replyToTopId || replyToMsgId || GENERAL_TOPIC_ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function selectTopicFromMessage<T extends GlobalState>(global: T, message: ApiMessage) {
|
export function selectTopicFromMessage<T extends GlobalState>(global: T, message: ApiMessage) {
|
||||||
@ -985,11 +989,12 @@ function selectShouldHideReplyKeyboard<T extends GlobalState>(global: T, message
|
|||||||
const {
|
const {
|
||||||
shouldHideKeyboardButtons,
|
shouldHideKeyboardButtons,
|
||||||
isHideKeyboardSelective,
|
isHideKeyboardSelective,
|
||||||
replyToMessageId,
|
|
||||||
isMentioned,
|
isMentioned,
|
||||||
} = message;
|
} = message;
|
||||||
if (!shouldHideKeyboardButtons) return false;
|
if (!shouldHideKeyboardButtons) return false;
|
||||||
|
|
||||||
|
const replyToMessageId = getMessageReplyInfo(message)?.replyToMsgId;
|
||||||
|
|
||||||
if (isHideKeyboardSelective) {
|
if (isHideKeyboardSelective) {
|
||||||
if (isMentioned) return true;
|
if (isMentioned) return true;
|
||||||
if (!replyToMessageId) return false;
|
if (!replyToMessageId) return false;
|
||||||
@ -1006,10 +1011,11 @@ function selectShouldDisplayReplyKeyboard<T extends GlobalState>(global: T, mess
|
|||||||
shouldHideKeyboardButtons,
|
shouldHideKeyboardButtons,
|
||||||
isKeyboardSelective,
|
isKeyboardSelective,
|
||||||
isMentioned,
|
isMentioned,
|
||||||
replyToMessageId,
|
|
||||||
} = message;
|
} = message;
|
||||||
if (!keyboardButtons || shouldHideKeyboardButtons) return false;
|
if (!keyboardButtons || shouldHideKeyboardButtons) return false;
|
||||||
|
|
||||||
|
const replyToMessageId = getMessageReplyInfo(message)?.replyToMsgId;
|
||||||
|
|
||||||
if (isKeyboardSelective) {
|
if (isKeyboardSelective) {
|
||||||
if (isMentioned) return true;
|
if (isMentioned) return true;
|
||||||
if (!replyToMessageId) return false;
|
if (!replyToMessageId) return false;
|
||||||
@ -1380,3 +1386,23 @@ export function selectTopicLink<T extends GlobalState>(
|
|||||||
) {
|
) {
|
||||||
return selectMessageLink(global, chatId, topicId);
|
return selectMessageLink(global, chatId, topicId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function selectMessageReplyInfo<T extends GlobalState>(
|
||||||
|
global: T, chatId: string, threadId: number = MAIN_THREAD_ID, additionalReplyInfo?: ApiInputMessageReplyInfo,
|
||||||
|
) {
|
||||||
|
const chat = selectChat(global, chatId);
|
||||||
|
if (!chat) return undefined;
|
||||||
|
|
||||||
|
const replyingToTopId = selectThreadTopMessageId(global, chatId, threadId);
|
||||||
|
|
||||||
|
if (!additionalReplyInfo && !replyingToTopId) return undefined;
|
||||||
|
|
||||||
|
const replyInfo: ApiInputMessageReplyInfo = {
|
||||||
|
type: 'message',
|
||||||
|
...additionalReplyInfo,
|
||||||
|
replyToMsgId: additionalReplyInfo?.replyToMsgId || replyingToTopId!,
|
||||||
|
replyToTopId: additionalReplyInfo?.replyToTopId || replyingToTopId,
|
||||||
|
};
|
||||||
|
|
||||||
|
return replyInfo;
|
||||||
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user