Messages: Implement non-сontact Info (#5708)
Co-authored-by: zubiden <19638254+zubiden@users.noreply.github.com>
This commit is contained in:
parent
d4c7fdb7ed
commit
dc61b12f9b
@ -13,7 +13,6 @@ import type {
|
||||
ApiChatlistInvite,
|
||||
ApiChatMember,
|
||||
ApiChatReactions,
|
||||
ApiChatSettings,
|
||||
ApiExportedInvite,
|
||||
ApiMissingInvitedUser,
|
||||
ApiRestrictionReason,
|
||||
@ -517,20 +516,6 @@ export function buildChatInviteImporter(importer: GramJs.ChatInviteImporter): Ap
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiChatSettings({
|
||||
autoarchived,
|
||||
reportSpam,
|
||||
addContact,
|
||||
blockContact,
|
||||
}: GramJs.PeerSettings): ApiChatSettings {
|
||||
return {
|
||||
isAutoArchived: Boolean(autoarchived),
|
||||
canReportSpam: Boolean(reportSpam),
|
||||
canAddContact: Boolean(addContact),
|
||||
canBlockContact: Boolean(blockContact),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiChatReactions(chatReactions?: GramJs.TypeChatReactions): ApiChatReactions | undefined {
|
||||
if (chatReactions instanceof GramJs.ChatReactionsAll) {
|
||||
return {
|
||||
|
||||
@ -2,6 +2,7 @@ import { Api as GramJs } from '../../../lib/gramjs';
|
||||
|
||||
import type {
|
||||
ApiBirthday,
|
||||
ApiPeerSettings,
|
||||
ApiUser,
|
||||
ApiUserFullInfo,
|
||||
ApiUserStatus,
|
||||
@ -24,7 +25,7 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
|
||||
fallbackPhoto, personalPhoto, translationsDisabled, storiesPinnedAvailable,
|
||||
contactRequirePremium, businessWorkHours, businessLocation, businessIntro,
|
||||
birthday, personalChannelId, personalChannelMessage, sponsoredEnabled, stargiftsCount, botVerification,
|
||||
botCanManageEmojiStatus,
|
||||
botCanManageEmojiStatus, settings,
|
||||
},
|
||||
users,
|
||||
} = mtpUserFull;
|
||||
@ -55,6 +56,29 @@ export function buildApiUserFullInfo(mtpUserFull: GramJs.users.UserFull): ApiUse
|
||||
starGiftCount: stargiftsCount,
|
||||
isBotCanManageEmojiStatus: botCanManageEmojiStatus,
|
||||
hasScheduledMessages: hasScheduled,
|
||||
settings: buildApiPeerSettings(settings),
|
||||
};
|
||||
}
|
||||
|
||||
export function buildApiPeerSettings({
|
||||
autoarchived,
|
||||
reportSpam,
|
||||
addContact,
|
||||
blockContact,
|
||||
registrationMonth,
|
||||
phoneCountry,
|
||||
nameChangeDate,
|
||||
photoChangeDate,
|
||||
}: GramJs.PeerSettings): ApiPeerSettings {
|
||||
return {
|
||||
isAutoArchived: Boolean(autoarchived),
|
||||
canReportSpam: Boolean(reportSpam),
|
||||
canAddContact: Boolean(addContact),
|
||||
canBlockContact: Boolean(blockContact),
|
||||
registrationMonth,
|
||||
phoneCountry,
|
||||
nameChangeDate,
|
||||
photoChangeDate,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -44,7 +44,6 @@ import {
|
||||
buildApiChatlistExportedInvite,
|
||||
buildApiChatlistInvite,
|
||||
buildApiChatReactions,
|
||||
buildApiChatSettings,
|
||||
buildApiMissingInvitedUser,
|
||||
buildApiTopic,
|
||||
buildChatMember,
|
||||
@ -56,7 +55,7 @@ import { buildApiMessage, buildMessageDraft } from '../apiBuilders/messages';
|
||||
import { buildApiPeerNotifySettings } from '../apiBuilders/misc';
|
||||
import { buildApiPeerId, getApiChatIdFromMtpPeer } from '../apiBuilders/peers';
|
||||
import { buildStickerSet } from '../apiBuilders/symbols';
|
||||
import { buildApiUser, buildApiUserStatuses } from '../apiBuilders/users';
|
||||
import { buildApiPeerSettings, buildApiUser, buildApiUserStatuses } from '../apiBuilders/users';
|
||||
import {
|
||||
buildChatAdminRights,
|
||||
buildChatBannedRights,
|
||||
@ -354,8 +353,8 @@ export function fetchFullChat(chat: ApiChat) {
|
||||
: getFullChatInfo(id);
|
||||
}
|
||||
|
||||
export async function fetchChatSettings(chat: ApiChat) {
|
||||
const { id, accessHash } = chat;
|
||||
export async function fetchPeerSettings(peer: ApiPeer) {
|
||||
const { id, accessHash } = peer;
|
||||
|
||||
const result = await invokeRequest(new GramJs.messages.GetPeerSettings({
|
||||
peer: buildInputPeer(id, accessHash),
|
||||
@ -368,7 +367,7 @@ export async function fetchChatSettings(chat: ApiChat) {
|
||||
}
|
||||
|
||||
return {
|
||||
settings: buildApiChatSettings(result.settings),
|
||||
settings: buildApiPeerSettings(result.settings),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Api as GramJs } from '../../../lib/gramjs';
|
||||
|
||||
import type {
|
||||
ApiChat, ApiError, ApiUser, ApiUsername,
|
||||
ApiChat, ApiError, ApiPeer, ApiUser, ApiUsername,
|
||||
} from '../../types';
|
||||
|
||||
import { ACCEPTABLE_USERNAME_ERRORS } from '../../../config';
|
||||
@ -280,8 +280,8 @@ export function hideAllChatJoinRequests({
|
||||
});
|
||||
}
|
||||
|
||||
export function hideChatReportPane(chat: ApiChat) {
|
||||
const { id, accessHash } = chat;
|
||||
export function hidePeerSettingsBar(peer: ApiPeer) {
|
||||
const { id, accessHash } = peer;
|
||||
|
||||
return invokeRequest(new GramJs.messages.HidePeerSettingsBar({
|
||||
peer: buildInputPeer(id, accessHash),
|
||||
|
||||
@ -22,7 +22,6 @@ import {
|
||||
import {
|
||||
buildApiChatFolder,
|
||||
buildApiChatFromPreview,
|
||||
buildApiChatSettings,
|
||||
buildChatMember,
|
||||
buildChatMembers,
|
||||
buildChatTypingStatus,
|
||||
@ -61,7 +60,7 @@ import {
|
||||
import { buildApiStealthMode, buildApiStory } from '../apiBuilders/stories';
|
||||
import { buildApiEmojiInteraction, buildStickerSet } from '../apiBuilders/symbols';
|
||||
import {
|
||||
buildApiUser,
|
||||
buildApiPeerSettings,
|
||||
buildApiUserStatus,
|
||||
} from '../apiBuilders/users';
|
||||
import {
|
||||
@ -776,42 +775,14 @@ export function updater(update: Update) {
|
||||
user: { phoneNumber: phone },
|
||||
});
|
||||
} else if (update instanceof GramJs.UpdatePeerSettings) {
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { _entities, settings } = update;
|
||||
if (!_entities) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_entities?.length) {
|
||||
_entities
|
||||
.filter((e) => e instanceof GramJs.User && !e.contact)
|
||||
.forEach((user) => {
|
||||
sendApiUpdate({
|
||||
'@type': 'deleteContact',
|
||||
id: buildApiPeerId(user.id, 'user'),
|
||||
});
|
||||
});
|
||||
|
||||
_entities
|
||||
.filter((e) => e instanceof GramJs.User && e.contact)
|
||||
.map(buildApiUser)
|
||||
.forEach((user) => {
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
|
||||
sendApiUpdate({
|
||||
'@type': 'updateUser',
|
||||
id: user.id,
|
||||
user: {
|
||||
...user,
|
||||
...(settings && { settings: buildApiChatSettings(settings) }),
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Settings
|
||||
const { peer, settings } = update;
|
||||
const peerId = getApiChatIdFromMtpPeer(peer);
|
||||
const apiSettings = buildApiPeerSettings(settings);
|
||||
sendApiUpdate({
|
||||
'@type': 'updatePeerSettings',
|
||||
id: peerId,
|
||||
settings: apiSettings,
|
||||
});
|
||||
} else if (update instanceof GramJs.UpdateNotifySettings) {
|
||||
const {
|
||||
notifySettings,
|
||||
|
||||
@ -68,9 +68,6 @@ export interface ApiChat {
|
||||
accessHash?: string;
|
||||
};
|
||||
|
||||
// Obtained from GetChatSettings
|
||||
settings?: ApiChatSettings;
|
||||
|
||||
joinRequests?: ApiChatInviteImporter[];
|
||||
isJoinToSend?: boolean;
|
||||
isJoinRequest?: boolean;
|
||||
@ -230,11 +227,15 @@ export interface ApiChatFolder {
|
||||
hasMyInvites?: true;
|
||||
}
|
||||
|
||||
export interface ApiChatSettings {
|
||||
export interface ApiPeerSettings {
|
||||
isAutoArchived?: boolean;
|
||||
canReportSpam?: boolean;
|
||||
canAddContact?: boolean;
|
||||
canBlockContact?: boolean;
|
||||
registrationMonth?: string;
|
||||
phoneCountry?: string;
|
||||
nameChangeDate?: number;
|
||||
photoChangeDate?: number;
|
||||
}
|
||||
|
||||
export interface ApiSendAsPeerId {
|
||||
|
||||
@ -18,6 +18,7 @@ import type {
|
||||
ApiChatFullInfo,
|
||||
ApiChatMember,
|
||||
ApiDraft,
|
||||
ApiPeerSettings,
|
||||
ApiTypingStatus,
|
||||
} from './chats';
|
||||
import type {
|
||||
@ -403,6 +404,12 @@ export type ApiDeleteContact = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
export type ApiUpdatePeerSettings = {
|
||||
'@type': 'updatePeerSettings';
|
||||
id: string;
|
||||
settings: ApiPeerSettings;
|
||||
};
|
||||
|
||||
export type ApiUpdateUser = {
|
||||
'@type': 'updateUser';
|
||||
id: string;
|
||||
@ -822,7 +829,7 @@ export type ApiUpdate = (
|
||||
ApiUpdateDeleteMessages | ApiUpdateMessagePoll | ApiUpdateMessagePollVote | ApiUpdateDeleteHistory |
|
||||
ApiDeleteParticipantHistory | ApiUpdateMessageSendSucceeded | ApiUpdateMessageSendFailed |
|
||||
ApiUpdateServiceNotification | ApiDeleteContact | ApiUpdateUser | ApiUpdateUserStatus |
|
||||
ApiUpdateUserFullInfo | ApiUpdateVideoProcessingPending |
|
||||
ApiUpdateUserFullInfo | ApiUpdateVideoProcessingPending | ApiUpdatePeerSettings |
|
||||
ApiUpdateAvatar | ApiUpdateMessageImage | ApiUpdateDraftMessage |
|
||||
ApiUpdateError | ApiUpdateResetContacts | ApiUpdateStartEmojiInteraction |
|
||||
ApiUpdateFavoriteStickers | ApiUpdateStickerSet | ApiUpdateStickerSets | ApiUpdateStickerSetsOrder |
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import type { API_CHAT_TYPES } from '../../config';
|
||||
import type { ApiBotInfo } from './bots';
|
||||
import type { ApiBusinessIntro, ApiBusinessLocation, ApiBusinessWorkHours } from './business';
|
||||
import type { ApiPeerColor } from './chats';
|
||||
import type { ApiPeerColor, ApiPeerSettings } from './chats';
|
||||
import type { ApiDocument, ApiPhoto } from './messages';
|
||||
import type { ApiBotVerification } from './misc';
|
||||
import type { ApiSavedStarGift } from './stars';
|
||||
@ -65,6 +65,7 @@ export interface ApiUserFullInfo {
|
||||
isBotAccessEmojiGranted?: boolean;
|
||||
hasScheduledMessages?: boolean;
|
||||
botVerification?: ApiBotVerification;
|
||||
settings?: ApiPeerSettings;
|
||||
}
|
||||
|
||||
export type ApiFakeType = 'fake' | 'scam';
|
||||
|
||||
@ -252,6 +252,10 @@
|
||||
"ChatListDeleteChatConfirmation" = "Are you sure you want to delete the chat\nwith {chat}?";
|
||||
"DeleteAndStop" = "Delete and Block";
|
||||
"ChatListDeleteForEveryone" = "Delete for me and {user}";
|
||||
"ChatNonContactUserSubtitle" = "Not a contact";
|
||||
"ChatNonContactUserGroups" = "Shared Groups";
|
||||
"ContactInfoRegistration" = "Registration";
|
||||
"ContactInfoNotVerified" = "Not an official account";
|
||||
"DeleteForAll" = "Delete for all members";
|
||||
"DeleteSingleMessagesTitle" = "Delete message";
|
||||
"AreYouSureDeleteSingleMessage" = "Are you sure you want to delete this message?";
|
||||
@ -1117,6 +1121,7 @@
|
||||
"WeekdaySaturday" = "Saturday";
|
||||
"WeekdaySunday" = "Sunday";
|
||||
"WeekdayToday" = "Today";
|
||||
"Today" = "today";
|
||||
"WeekdayYesterday" = "Yesterday";
|
||||
"User" = "User";
|
||||
"SecretChat" = "Secret Chat";
|
||||
@ -1193,6 +1198,14 @@
|
||||
"Seconds_other" = "{count} seconds";
|
||||
"Minutes_one" = "{count} minute";
|
||||
"Minutes_other" = "{count} minutes";
|
||||
"JustNowAgo" = "just now";
|
||||
"MinutesAgo_one" = "1 minute ago";
|
||||
"MinutesAgo_other" = "{count} minutes ago";
|
||||
"HoursAgo_one" = "1 hour ago";
|
||||
"HoursAgo_other" = "{count} hours ago";
|
||||
"DaysAgo_one" = "1 day ago";
|
||||
"DaysAgo_other" = "{count} days ago";
|
||||
"AtDateAgo" = "on {date}";
|
||||
"AudioPause" = "Pause audio";
|
||||
"AudioPlay" = "Play audio";
|
||||
"ToggleUserNotifications" = "Toggle user notifications";
|
||||
@ -1419,6 +1432,8 @@
|
||||
"GiftInfoConvertDescription2" = "This action cannot be undone. This will permanently destroy the gift.";
|
||||
"GiftInfoConvertDescriptionPeriod_one" = "Conversion is available for the next **{count} days**.";
|
||||
"GiftInfoConvertDescriptionPeriod_other" = "Conversion is available for the next **{count} days**.";
|
||||
"UserUpdatedName" = "{user} updated name {time}";
|
||||
"UserUpdatedPhoto" = "{user} updated photo {time}";
|
||||
"GiftInfoSaved" = "This gift is visible on your profile. {link}";
|
||||
"GiftInfoHidden" = "This gift is hidden. Only you can see it. {link}";
|
||||
"GiftInfoChannelSaved" = "This gift is visible in your channel's profile. {link}";
|
||||
@ -1487,6 +1502,8 @@
|
||||
"GiftWithdrawSubmit" = "Open Fragment";
|
||||
"GiftWithdrawWait_one" = "In {days} day, you'll be able to send this collectible to any TON blockchain address outside Telegram for sale or auction.";
|
||||
"GiftWithdrawWait_other" = "In {days} days, you'll be able to send this collectible to any TON blockchain address outside Telegram for sale or auction.";
|
||||
"ChatGroups_one" = "{count} group";
|
||||
"ChatGroups_other" = "{count} groups";
|
||||
"StarsAmount" = "⭐️{amount}";
|
||||
"StarsAmountText_one" = "{amount} Star";
|
||||
"StarsAmountText_other" = "{amount} Stars";
|
||||
|
||||
@ -13,7 +13,7 @@ import { preloadImage } from '../../util/files';
|
||||
import preloadFonts from '../../util/fonts';
|
||||
import { pick } from '../../util/iteratees';
|
||||
import { oldSetLanguage } from '../../util/oldLangProvider';
|
||||
import { formatPhoneNumber, getCountryCodesByIso, getCountryFromPhoneNumber } from '../../util/phoneNumber';
|
||||
import { formatPhoneNumber, getCountryCodeByIso, getCountryFromPhoneNumber } from '../../util/phoneNumber';
|
||||
import { IS_SAFARI, IS_TOUCH_ENV } from '../../util/windowEnvironment';
|
||||
import { getSuggestedLanguage } from './helpers/getSuggestedLanguage';
|
||||
|
||||
@ -101,7 +101,7 @@ const AuthPhoneNumber: FC<StateProps> = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (authNearestCountry && phoneCodeList && !country && !isTouched) {
|
||||
setCountry(getCountryCodesByIso(phoneCodeList, authNearestCountry)[0]);
|
||||
setCountry(getCountryCodeByIso(phoneCodeList, authNearestCountry));
|
||||
}
|
||||
}, [country, authNearestCountry, isTouched, phoneCodeList]);
|
||||
|
||||
|
||||
@ -6,7 +6,10 @@ import { addExtraClass, removeExtraClass } from '../../lib/teact/teact-dom';
|
||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||
|
||||
import type {
|
||||
ApiChatFullInfo, ApiMessage, ApiRestrictionReason, ApiTopic,
|
||||
ApiChatFullInfo,
|
||||
ApiMessage,
|
||||
ApiRestrictionReason,
|
||||
ApiTopic,
|
||||
} from '../../api/types';
|
||||
import type { OnIntersectPinnedMessage } from './hooks/usePinnedMessage';
|
||||
import { MAIN_THREAD_ID } from '../../api/types';
|
||||
@ -74,7 +77,7 @@ import useStickyDates from './hooks/useStickyDates';
|
||||
|
||||
import Loading from '../ui/Loading';
|
||||
import ContactGreeting from './ContactGreeting';
|
||||
import MessageListBotInfo from './MessageListBotInfo';
|
||||
import MessageListAccountInfo from './MessageListAccountInfo';
|
||||
import MessageListContent from './MessageListContent';
|
||||
import NoMessages from './NoMessages';
|
||||
import PremiumRequiredMessage from './PremiumRequiredMessage';
|
||||
@ -106,6 +109,9 @@ type StateProps = {
|
||||
isCreator?: boolean;
|
||||
isChannelWithAvatars?: boolean;
|
||||
isBot?: boolean;
|
||||
isNonContact?: boolean;
|
||||
nameChangeDate?: number;
|
||||
photoChangeDate?: number;
|
||||
isSynced?: boolean;
|
||||
messageIds?: number[];
|
||||
messagesById?: Record<number, ApiMessage>;
|
||||
@ -159,6 +165,9 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
isAnonymousForwards,
|
||||
isCreator,
|
||||
isBot,
|
||||
isNonContact,
|
||||
nameChangeDate,
|
||||
photoChangeDate,
|
||||
messageIds,
|
||||
messagesById,
|
||||
firstUnreadId,
|
||||
@ -682,8 +691,8 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
</div>
|
||||
) : isContactRequirePremium && !hasMessages ? (
|
||||
<PremiumRequiredMessage userId={chatId} />
|
||||
) : isBot && !hasMessages ? (
|
||||
<MessageListBotInfo chatId={chatId} />
|
||||
) : (isBot || isNonContact) && !hasMessages ? (
|
||||
<MessageListAccountInfo chatId={chatId} />
|
||||
) : shouldRenderGreeting ? (
|
||||
<ContactGreeting key={chatId} userId={chatId} />
|
||||
) : messageIds && (!messageGroups || isGroupChatJustCreated || isEmptyTopic) ? (
|
||||
@ -718,7 +727,9 @@ const MessageList: FC<OwnProps & StateProps> = ({
|
||||
isReady={isReady}
|
||||
hasLinkedChat={hasLinkedChat}
|
||||
isSchedule={messageGroups ? type === 'scheduled' : false}
|
||||
shouldRenderBotInfo={isBot}
|
||||
shouldRenderAccountInfo={isBot || isNonContact}
|
||||
nameChangeDate={nameChangeDate}
|
||||
photoChangeDate={photoChangeDate}
|
||||
noAppearanceAnimation={!messageGroups || !shouldAnimateAppearanceRef.current}
|
||||
onScrollDownToggle={onScrollDownToggle}
|
||||
onNotchToggle={onNotchToggle}
|
||||
@ -735,6 +746,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId, threadId, type }): StateProps => {
|
||||
const currentUserId = global.currentUserId!;
|
||||
const chat = selectChat(global, chatId);
|
||||
const userFullInfo = selectUserFullInfo(global, chatId);
|
||||
if (!chat) {
|
||||
return { currentUserId };
|
||||
}
|
||||
@ -763,6 +775,9 @@ export default memo(withGlobal<OwnProps>(
|
||||
);
|
||||
|
||||
const chatBot = selectBot(global, chatId);
|
||||
const isNonContact = Boolean(userFullInfo?.settings?.canAddContact);
|
||||
const nameChangeDate = userFullInfo?.settings?.nameChangeDate;
|
||||
const photoChangeDate = userFullInfo?.settings?.photoChangeDate;
|
||||
|
||||
const topic = selectTopic(global, chatId, threadId);
|
||||
const chatFullInfo = !isUserId(chatId) ? selectChatFullInfo(global, chatId) : undefined;
|
||||
@ -784,6 +799,9 @@ export default memo(withGlobal<OwnProps>(
|
||||
isSystemBotChat: isSystemBot(chatId),
|
||||
isAnonymousForwards: isAnonymousForwardsChat(chatId),
|
||||
isBot: Boolean(chatBot),
|
||||
isNonContact,
|
||||
nameChangeDate,
|
||||
photoChangeDate,
|
||||
isSynced: global.isSynced,
|
||||
messageIds,
|
||||
messagesById,
|
||||
|
||||
109
src/components/middle/MessageListAccountInfo.module.scss
Normal file
109
src/components/middle/MessageListAccountInfo.module.scss
Normal file
@ -0,0 +1,109 @@
|
||||
.root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0 0.6875rem;
|
||||
}
|
||||
|
||||
.chatInfo {
|
||||
max-width: min(80%, 25rem);
|
||||
min-width: 15.8125rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
font-size: calc(var(--message-text-size, 1rem) - 0.0625rem);
|
||||
border-radius: var(--border-radius-messages);
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.chatBackground {
|
||||
background-color: var(--action-message-bg);
|
||||
color: white;
|
||||
padding: 1rem 1.5rem;
|
||||
margin: 5rem 0 2rem;
|
||||
contain: layout; // Force new stacking context for text blending
|
||||
}
|
||||
|
||||
.botBackground {
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.bot-info-description {
|
||||
padding: 0.5rem 1rem;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
.bot-info-title {
|
||||
font-weight: var(--font-weight-medium);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.media {
|
||||
max-width: 100%;
|
||||
height: auto !important;
|
||||
}
|
||||
|
||||
.chatInfoTitle {
|
||||
font-weight: var(--font-weight-semibold);
|
||||
margin: 0 0 0.25rem;
|
||||
}
|
||||
|
||||
.chatInfoSubtitle {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.chatDescription {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.country {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
:global(.emoji-small) {
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
margin-top: -0.1875rem;
|
||||
}
|
||||
|
||||
.chatNotVerified {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.verifiedTitle {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.linkInfo:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-inline-start: -0.1875rem;
|
||||
}
|
||||
|
||||
.textColor {
|
||||
color: white;
|
||||
mix-blend-mode: overlay;
|
||||
}
|
||||
293
src/components/middle/MessageListAccountInfo.tsx
Normal file
293
src/components/middle/MessageListAccountInfo.tsx
Normal file
@ -0,0 +1,293 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, {
|
||||
memo,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from '../../lib/teact/teact';
|
||||
import { getActions, getGlobal, withGlobal } from '../../global';
|
||||
|
||||
import type {
|
||||
ApiBotInfo, ApiChat, ApiCountryCode, ApiUserCommonChats, ApiUserFullInfo,
|
||||
} from '../../api/types';
|
||||
|
||||
import {
|
||||
getBotCoverMediaHash,
|
||||
getChatTitle,
|
||||
getPhotoFullDimensions,
|
||||
getVideoDimensions,
|
||||
getVideoMediaHash,
|
||||
isChatWithVerificationCodesBot,
|
||||
} from '../../global/helpers';
|
||||
import {
|
||||
selectBot, selectChat, selectPeer, selectUserCommonChats, selectUserFullInfo,
|
||||
} from '../../global/selectors';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import buildStyle from '../../util/buildStyle';
|
||||
import { formatPastDatetime, formatRegistrationMonth } from '../../util/dates/dateFormat';
|
||||
import { isoToEmoji } from '../../util/emoji/emoji';
|
||||
import { getCountryCodeByIso } from '../../util/phoneNumber';
|
||||
import stopEvent from '../../util/stopEvent';
|
||||
import renderText from '../common/helpers/renderText';
|
||||
|
||||
import useEffectOnce from '../../hooks/useEffectOnce';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
import useMedia from '../../hooks/useMedia';
|
||||
import useOldLang from '../../hooks/useOldLang';
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
|
||||
import AvatarList from '../common/AvatarList';
|
||||
import Icon from '../common/icons/Icon';
|
||||
import MiniTable, { type TableEntry } from '../common/MiniTable';
|
||||
import Link from '../ui/Link';
|
||||
import OptimizedVideo from '../ui/OptimizedVideo';
|
||||
import Skeleton from '../ui/placeholder/Skeleton';
|
||||
|
||||
import styles from './MessageListAccountInfo.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
chatId: string;
|
||||
isInMessageList?: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
chat?: ApiChat;
|
||||
botInfo?: ApiBotInfo;
|
||||
isLoadingFullUser?: boolean;
|
||||
phoneCodeList?: ApiCountryCode[];
|
||||
commonChats?: ApiUserCommonChats;
|
||||
userFullInfo?: ApiUserFullInfo;
|
||||
};
|
||||
|
||||
const MessageListAccountInfo: FC<OwnProps & StateProps> = ({
|
||||
chat,
|
||||
chatId,
|
||||
botInfo,
|
||||
isLoadingFullUser,
|
||||
isInMessageList,
|
||||
phoneCodeList,
|
||||
commonChats,
|
||||
userFullInfo,
|
||||
}) => {
|
||||
const { loadCommonChats, openChatWithInfo } = getActions();
|
||||
const oldLang = useOldLang();
|
||||
const lang = useLang();
|
||||
|
||||
const {
|
||||
phoneCountry,
|
||||
registrationMonth,
|
||||
nameChangeDate,
|
||||
photoChangeDate,
|
||||
} = userFullInfo?.settings || {};
|
||||
|
||||
useEffect(() => {
|
||||
loadCommonChats({ userId: chatId });
|
||||
}, [chatId]);
|
||||
|
||||
const country = useMemo(() => {
|
||||
if (!phoneCodeList || !phoneCountry) return undefined;
|
||||
return getCountryCodeByIso(phoneCodeList, phoneCountry);
|
||||
}, [phoneCodeList, phoneCountry]);
|
||||
|
||||
const botInfoPhotoUrl = useMedia(botInfo?.photo ? getBotCoverMediaHash(botInfo.photo) : undefined);
|
||||
const botInfoGifUrl = useMedia(botInfo?.gif ? getVideoMediaHash(botInfo.gif, 'full') : undefined);
|
||||
const botInfoDimensions = botInfo?.photo ? getPhotoFullDimensions(botInfo.photo) : botInfo?.gif
|
||||
? getVideoDimensions(botInfo.gif) : undefined;
|
||||
const isBotInfoEmpty = botInfo && !botInfo.description && !botInfo.gif && !botInfo.photo;
|
||||
const isChatInfoEmpty = !country || !registrationMonth;
|
||||
|
||||
const isVerifyCodes = isChatWithVerificationCodesBot(chatId);
|
||||
|
||||
const { width, height } = botInfoDimensions || {};
|
||||
|
||||
const handleClick = useLastCallback((e: React.SyntheticEvent<any>) => {
|
||||
stopEvent(e);
|
||||
openChatWithInfo({
|
||||
id: chatId, shouldReplaceHistory: true, profileTab: 'commonChats', forceScrollProfileTab: true,
|
||||
});
|
||||
});
|
||||
|
||||
const securityNameInfo = nameChangeDate && chat ? (
|
||||
<div className="local-action-message" key="security-name-message">
|
||||
<span>{lang('UserUpdatedName', {
|
||||
user: chat.title,
|
||||
time: formatPastDatetime(lang, nameChangeDate),
|
||||
}, { withNodes: true, withMarkdown: true })}
|
||||
</span>
|
||||
</div>
|
||||
) : undefined;
|
||||
|
||||
const securityPhotoInfo = photoChangeDate && chat ? (
|
||||
<div className="local-action-message" key="security-photo-message">
|
||||
<span>{lang('UserUpdatedPhoto', {
|
||||
user: chat.title,
|
||||
time: formatPastDatetime(lang, photoChangeDate),
|
||||
}, { withNodes: true, withMarkdown: true })}
|
||||
</span>
|
||||
</div>
|
||||
) : undefined;
|
||||
|
||||
const tableData = useMemo((): TableEntry[] => {
|
||||
const entries: TableEntry[] = [];
|
||||
if (country) {
|
||||
entries.push([
|
||||
oldLang('PrivacyPhone'),
|
||||
<span className={styles.chatDescription}>
|
||||
<span className={styles.country}>
|
||||
{renderText(isoToEmoji(country?.iso2))}
|
||||
</span>
|
||||
{country?.defaultName}
|
||||
</span>,
|
||||
]);
|
||||
}
|
||||
if (registrationMonth) {
|
||||
entries.push([
|
||||
lang('ContactInfoRegistration'),
|
||||
formatRegistrationMonth(lang.code, registrationMonth),
|
||||
]);
|
||||
}
|
||||
if (userFullInfo?.commonChatsCount) {
|
||||
const global = getGlobal();
|
||||
const peers = commonChats?.ids.slice(0, 3).map((id) => selectPeer(global, id)!).filter(Boolean);
|
||||
entries.push([
|
||||
lang('ChatNonContactUserGroups'),
|
||||
<Link className={styles.link} onClick={handleClick}>
|
||||
<span className={styles.linkInfo}>
|
||||
{lang('ChatGroups', {
|
||||
count: userFullInfo.commonChatsCount,
|
||||
}, {
|
||||
pluralValue: userFullInfo.commonChatsCount,
|
||||
})}
|
||||
</span>
|
||||
{Boolean(peers?.length) && <AvatarList size="micro" peers={peers} />}
|
||||
<Icon name="next" className={styles.icon} />
|
||||
</Link>,
|
||||
]);
|
||||
}
|
||||
return entries;
|
||||
}, [lang, oldLang, country, registrationMonth, commonChats, userFullInfo]);
|
||||
|
||||
const isEmptyOrLoading = (isBotInfoEmpty && isChatInfoEmpty) || isLoadingFullUser;
|
||||
|
||||
const isFirstRenderRef = useRef(true);
|
||||
const {
|
||||
shouldRender,
|
||||
ref,
|
||||
} = useShowTransition({
|
||||
isOpen: !isEmptyOrLoading && isInMessageList,
|
||||
withShouldRender: true,
|
||||
});
|
||||
|
||||
useEffectOnce(() => {
|
||||
isFirstRenderRef.current = false;
|
||||
});
|
||||
|
||||
if (!shouldRender) return undefined;
|
||||
|
||||
return (
|
||||
<div ref={ref} className={buildClassName(styles.root, 'empty')}>
|
||||
{isLoadingFullUser && isChatInfoEmpty && <span>{oldLang('Loading')}</span>}
|
||||
{(isBotInfoEmpty && isChatInfoEmpty) && !isLoadingFullUser && <span>{oldLang('NoMessages')}</span>}
|
||||
{botInfo && (
|
||||
<div
|
||||
className={buildClassName(styles.chatInfo, styles.botBackground)}
|
||||
style={buildStyle(
|
||||
width ? `width: ${width}px` : undefined,
|
||||
)}
|
||||
>
|
||||
{botInfoPhotoUrl && (
|
||||
<img
|
||||
className={styles.media}
|
||||
src={botInfoPhotoUrl}
|
||||
width={width}
|
||||
height={height}
|
||||
alt="Bot info"
|
||||
/>
|
||||
)}
|
||||
{botInfoGifUrl && (
|
||||
<OptimizedVideo
|
||||
canPlay
|
||||
className={styles.media}
|
||||
src={botInfoGifUrl}
|
||||
loop
|
||||
disablePictureInPicture
|
||||
muted
|
||||
playsInline
|
||||
style={buildStyle(Boolean(width) && `width: ${width}px`, Boolean(height) && `height: ${height}px`)}
|
||||
/>
|
||||
)}
|
||||
{botInfoDimensions && !botInfoPhotoUrl && !botInfoGifUrl && (
|
||||
<Skeleton
|
||||
className={styles.media}
|
||||
width={width}
|
||||
height={height}
|
||||
forceAspectRatio
|
||||
/>
|
||||
)}
|
||||
{isVerifyCodes && (
|
||||
<div className={styles.botInfoDescription}>
|
||||
{oldLang('VerifyChatInfo')}
|
||||
</div>
|
||||
)}
|
||||
{!isVerifyCodes && botInfo.description && (
|
||||
<div className={styles.botInfoDescription}>
|
||||
<p className={styles.botInfoTitle}>{oldLang('BotInfoTitle')}</p>
|
||||
{renderText(botInfo.description, ['br', 'emoji', 'links'])}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{!isChatInfoEmpty && chat && (
|
||||
<div
|
||||
className={buildClassName(styles.chatInfo, styles.chatBackground)}
|
||||
>
|
||||
<h3 className={styles.chatInfoTitle}>{renderText(getChatTitle(lang, chat))}</h3>
|
||||
<p className={buildClassName(styles.chatInfoSubtitle, styles.textColor)}>
|
||||
{lang('ChatNonContactUserSubtitle')}
|
||||
</p>
|
||||
<MiniTable keyClassName={styles.textColor} data={tableData} />
|
||||
{!chat?.isVerified && (
|
||||
<div className={buildClassName(styles.chatNotVerified, styles.textColor)}>
|
||||
<Icon name="info-filled" />
|
||||
<p className={styles.verifiedTitle}>{lang('ContactInfoNotVerified')}</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{securityNameInfo}
|
||||
{securityPhotoInfo}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId }) => {
|
||||
const {
|
||||
countryList: { phoneCodes: phoneCodeList },
|
||||
} = global;
|
||||
const chat = selectChat(global, chatId);
|
||||
const userFullInfo = selectUserFullInfo(global, chatId);
|
||||
const commonChats = selectUserCommonChats(global, chatId);
|
||||
const chatBot = selectBot(global, chatId);
|
||||
|
||||
let isLoadingFullUser = false;
|
||||
let botInfo;
|
||||
if (chatBot) {
|
||||
if (userFullInfo) {
|
||||
botInfo = userFullInfo.botInfo;
|
||||
} else {
|
||||
isLoadingFullUser = true;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
chat,
|
||||
userFullInfo,
|
||||
botInfo,
|
||||
isLoadingFullUser,
|
||||
phoneCodeList,
|
||||
commonChats,
|
||||
};
|
||||
},
|
||||
)(MessageListAccountInfo));
|
||||
@ -1,35 +0,0 @@
|
||||
.root {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.bot-info {
|
||||
max-width: min(80%, 25rem);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
background-color: var(--color-background);
|
||||
color: var(--color-text);
|
||||
font-size: calc(var(--message-text-size, 1rem) - 0.0625rem);
|
||||
border-radius: var(--border-radius-messages);
|
||||
overflow: hidden;
|
||||
text-align: initial;
|
||||
}
|
||||
|
||||
.bot-info-description {
|
||||
padding: 0.5rem 1rem;
|
||||
text-wrap: pretty;
|
||||
}
|
||||
|
||||
.bot-info-title {
|
||||
font-weight: var(--font-weight-medium);
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.media {
|
||||
max-width: 100%;
|
||||
height: auto !important;
|
||||
}
|
||||
@ -1,134 +0,0 @@
|
||||
import type { FC } from '../../lib/teact/teact';
|
||||
import React, { memo } from '../../lib/teact/teact';
|
||||
import { withGlobal } from '../../global';
|
||||
|
||||
import type { ApiBotInfo } from '../../api/types';
|
||||
|
||||
import {
|
||||
getBotCoverMediaHash,
|
||||
getPhotoFullDimensions,
|
||||
getVideoDimensions,
|
||||
getVideoMediaHash,
|
||||
isChatWithVerificationCodesBot,
|
||||
} from '../../global/helpers';
|
||||
import { selectBot, selectUserFullInfo } from '../../global/selectors';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import buildStyle from '../../util/buildStyle';
|
||||
import renderText from '../common/helpers/renderText';
|
||||
|
||||
import useMedia from '../../hooks/useMedia';
|
||||
import useOldLang from '../../hooks/useOldLang';
|
||||
|
||||
import OptimizedVideo from '../ui/OptimizedVideo';
|
||||
import Skeleton from '../ui/placeholder/Skeleton';
|
||||
|
||||
import styles from './MessageListBotInfo.module.scss';
|
||||
|
||||
type OwnProps = {
|
||||
chatId: string;
|
||||
isInMessageList?: boolean;
|
||||
};
|
||||
|
||||
type StateProps = {
|
||||
botInfo?: ApiBotInfo;
|
||||
isLoadingBotInfo?: boolean;
|
||||
};
|
||||
|
||||
const MessageListBotInfo: FC<OwnProps & StateProps> = ({
|
||||
chatId,
|
||||
botInfo,
|
||||
isLoadingBotInfo,
|
||||
isInMessageList,
|
||||
}) => {
|
||||
const lang = useOldLang();
|
||||
|
||||
const botInfoPhotoUrl = useMedia(botInfo?.photo ? getBotCoverMediaHash(botInfo.photo) : undefined);
|
||||
const botInfoGifUrl = useMedia(botInfo?.gif ? getVideoMediaHash(botInfo.gif, 'full') : undefined);
|
||||
const botInfoDimensions = botInfo?.photo ? getPhotoFullDimensions(botInfo.photo) : botInfo?.gif
|
||||
? getVideoDimensions(botInfo.gif) : undefined;
|
||||
const isBotInfoEmpty = botInfo && !botInfo.description && !botInfo.gif && !botInfo.photo;
|
||||
|
||||
const isVerifyCodes = isChatWithVerificationCodesBot(chatId);
|
||||
|
||||
const { width, height } = botInfoDimensions || {};
|
||||
|
||||
const isEmptyOrLoading = isBotInfoEmpty || isLoadingBotInfo;
|
||||
|
||||
if (isEmptyOrLoading && isInMessageList) return undefined;
|
||||
|
||||
return (
|
||||
<div className={buildClassName(styles.root, 'empty')}>
|
||||
{isLoadingBotInfo && <span>{lang('Loading')}</span>}
|
||||
{isBotInfoEmpty && !isLoadingBotInfo && <span>{lang('NoMessages')}</span>}
|
||||
{botInfo && (
|
||||
<div
|
||||
className={styles.botInfo}
|
||||
style={buildStyle(
|
||||
width ? `width: ${width}px` : undefined,
|
||||
)}
|
||||
>
|
||||
{botInfoPhotoUrl && (
|
||||
<img
|
||||
className={styles.media}
|
||||
src={botInfoPhotoUrl}
|
||||
width={width}
|
||||
height={height}
|
||||
alt="Bot info"
|
||||
/>
|
||||
)}
|
||||
{botInfoGifUrl && (
|
||||
<OptimizedVideo
|
||||
canPlay
|
||||
className={styles.media}
|
||||
src={botInfoGifUrl}
|
||||
loop
|
||||
disablePictureInPicture
|
||||
muted
|
||||
playsInline
|
||||
style={buildStyle(Boolean(width) && `width: ${width}px`, Boolean(height) && `height: ${height}px`)}
|
||||
/>
|
||||
)}
|
||||
{botInfoDimensions && !botInfoPhotoUrl && !botInfoGifUrl && (
|
||||
<Skeleton
|
||||
className={styles.media}
|
||||
width={width}
|
||||
height={height}
|
||||
forceAspectRatio
|
||||
/>
|
||||
)}
|
||||
{isVerifyCodes && (
|
||||
<div className={styles.botInfoDescription}>
|
||||
{lang('VerifyChatInfo')}
|
||||
</div>
|
||||
)}
|
||||
{!isVerifyCodes && botInfo.description && (
|
||||
<div className={styles.botInfoDescription}>
|
||||
<p className={styles.botInfoTitle}>{lang('BotInfoTitle')}</p>
|
||||
{renderText(botInfo.description, ['br', 'emoji', 'links'])}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(withGlobal<OwnProps>(
|
||||
(global, { chatId }) => {
|
||||
const chatBot = selectBot(global, chatId);
|
||||
let isLoadingBotInfo = false;
|
||||
let botInfo;
|
||||
if (chatBot) {
|
||||
const chatBotFullInfo = selectUserFullInfo(global, chatBot.id);
|
||||
if (chatBotFullInfo) {
|
||||
botInfo = chatBotFullInfo.botInfo;
|
||||
} else {
|
||||
isLoadingBotInfo = true;
|
||||
}
|
||||
}
|
||||
return {
|
||||
botInfo,
|
||||
isLoadingBotInfo,
|
||||
};
|
||||
},
|
||||
)(MessageListBotInfo));
|
||||
@ -33,7 +33,7 @@ import ActionMessage from './message/ActionMessage';
|
||||
import Message from './message/Message';
|
||||
import SenderGroupContainer from './message/SenderGroupContainer';
|
||||
import SponsoredMessage from './message/SponsoredMessage';
|
||||
import MessageListBotInfo from './MessageListBotInfo';
|
||||
import MessageListAccountInfo from './MessageListAccountInfo';
|
||||
|
||||
interface OwnProps {
|
||||
canShowAds?: boolean;
|
||||
@ -57,7 +57,9 @@ interface OwnProps {
|
||||
isReady: boolean;
|
||||
hasLinkedChat: boolean | undefined;
|
||||
isSchedule: boolean;
|
||||
shouldRenderBotInfo?: boolean;
|
||||
shouldRenderAccountInfo?: boolean;
|
||||
nameChangeDate?: number;
|
||||
photoChangeDate?: number;
|
||||
noAppearanceAnimation: boolean;
|
||||
isSavedDialog?: boolean;
|
||||
onScrollDownToggle: BooleanToVoidFunction;
|
||||
@ -89,7 +91,9 @@ const MessageListContent: FC<OwnProps> = ({
|
||||
isReady,
|
||||
hasLinkedChat,
|
||||
isSchedule,
|
||||
shouldRenderBotInfo,
|
||||
shouldRenderAccountInfo,
|
||||
nameChangeDate,
|
||||
photoChangeDate,
|
||||
noAppearanceAnimation,
|
||||
isSavedDialog,
|
||||
onScrollDownToggle,
|
||||
@ -126,11 +130,11 @@ const MessageListContent: FC<OwnProps> = ({
|
||||
isReady,
|
||||
);
|
||||
|
||||
const lang = useOldLang();
|
||||
const oldLang = useOldLang();
|
||||
|
||||
const unreadDivider = (
|
||||
<div className={buildClassName(UNREAD_DIVIDER_CLASS, 'local-action-message')} key="unread-messages">
|
||||
<span>{lang('UnreadMessages')}</span>
|
||||
<span>{oldLang('UnreadMessages')}</span>
|
||||
</div>
|
||||
);
|
||||
const messageCountToAnimate = noAppearanceAnimation ? 0 : messageGroups.reduce((acc, messageGroup) => {
|
||||
@ -251,7 +255,7 @@ const MessageListContent: FC<OwnProps> = ({
|
||||
/>,
|
||||
message.id === threadId && (
|
||||
<div className="local-action-message" key="discussion-started">
|
||||
<span>{lang(isEmptyThread
|
||||
<span>{oldLang(isEmptyThread
|
||||
? (isComments ? 'NoComments' : 'NoReplies') : 'DiscussionStarted')}
|
||||
</span>
|
||||
</div>
|
||||
@ -301,7 +305,8 @@ const MessageListContent: FC<OwnProps> = ({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={buildClassName('message-date-group', dateGroupIndex === 0 && 'first-message-date-group')}
|
||||
className={buildClassName('message-date-group', !(nameChangeDate || photoChangeDate)
|
||||
&& dateGroupIndex === 0 && 'first-message-date-group')}
|
||||
key={dateGroup.datetime}
|
||||
onMouseDown={preventMessageInputBlur}
|
||||
teactFastList
|
||||
@ -314,12 +319,12 @@ const MessageListContent: FC<OwnProps> = ({
|
||||
>
|
||||
<span dir="auto">
|
||||
{isSchedule && dateGroup.originalDate === SCHEDULED_WHEN_ONLINE && (
|
||||
lang('MessageScheduledUntilOnline')
|
||||
oldLang('MessageScheduledUntilOnline')
|
||||
)}
|
||||
{isSchedule && dateGroup.originalDate !== SCHEDULED_WHEN_ONLINE && (
|
||||
lang('MessageScheduledOn', formatHumanDate(lang, dateGroup.datetime, undefined, true))
|
||||
oldLang('MessageScheduledOn', formatHumanDate(oldLang, dateGroup.datetime, undefined, true))
|
||||
)}
|
||||
{!isSchedule && formatHumanDate(lang, dateGroup.datetime)}
|
||||
{!isSchedule && formatHumanDate(oldLang, dateGroup.datetime)}
|
||||
</span>
|
||||
</div>
|
||||
{senderGroups.flat()}
|
||||
@ -330,7 +335,8 @@ const MessageListContent: FC<OwnProps> = ({
|
||||
return (
|
||||
<div className="messages-container" teactFastList>
|
||||
{withHistoryTriggers && <div ref={backwardsTriggerRef} key="backwards-trigger" className="backwards-trigger" />}
|
||||
{shouldRenderBotInfo && <MessageListBotInfo isInMessageList key={`bot_info_${chatId}`} chatId={chatId} />}
|
||||
{shouldRenderAccountInfo
|
||||
&& <MessageListAccountInfo isInMessageList key={`account_info_${chatId}`} chatId={chatId} />}
|
||||
{dateGroups.flat()}
|
||||
{withHistoryTriggers && (
|
||||
<div
|
||||
|
||||
@ -138,7 +138,7 @@ type StateProps = {
|
||||
shouldSkipHistoryAnimations?: boolean;
|
||||
currentTransitionKey: number;
|
||||
isChannel?: boolean;
|
||||
areChatSettingsLoaded?: boolean;
|
||||
arePeerSettingsLoaded?: boolean;
|
||||
canSubscribe?: boolean;
|
||||
canStartBot?: boolean;
|
||||
canRestartBot?: boolean;
|
||||
@ -198,7 +198,7 @@ function MiddleColumn({
|
||||
shouldSkipHistoryAnimations,
|
||||
currentTransitionKey,
|
||||
isChannel,
|
||||
areChatSettingsLoaded,
|
||||
arePeerSettingsLoaded,
|
||||
canSubscribe,
|
||||
canStartBot,
|
||||
canRestartBot,
|
||||
@ -219,7 +219,7 @@ function MiddleColumn({
|
||||
openPreviousChat,
|
||||
unpinAllMessages,
|
||||
loadUser,
|
||||
loadChatSettings,
|
||||
loadPeerSettings,
|
||||
exitMessageSelectMode,
|
||||
joinChannel,
|
||||
sendBotCommand,
|
||||
@ -339,10 +339,10 @@ function MiddleColumn({
|
||||
}, [chatId, isPrivate, loadUser]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!areChatSettingsLoaded) {
|
||||
loadChatSettings({ chatId: chatId! });
|
||||
if (!arePeerSettingsLoaded) {
|
||||
loadPeerSettings({ peerId: chatId! });
|
||||
}
|
||||
}, [chatId, isPrivate, areChatSettingsLoaded]);
|
||||
}, [chatId, isPrivate, arePeerSettingsLoaded]);
|
||||
|
||||
useEffect(() => {
|
||||
if (chatId && shouldLoadFullChat && isReady) {
|
||||
@ -762,6 +762,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
const bot = selectBot(global, chatId);
|
||||
const pinnedIds = selectPinnedIds(global, chatId, threadId);
|
||||
const chatFullInfo = chatId ? selectChatFullInfo(global, chatId) : undefined;
|
||||
const userFullInfo = chatId ? selectUserFullInfo(global, chatId) : undefined;
|
||||
|
||||
const threadInfo = selectThreadInfo(global, chatId, threadId);
|
||||
const isMessageThread = Boolean(!threadInfo?.isCommentsInfo && threadInfo?.fromChannelId);
|
||||
@ -809,7 +810,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
chat,
|
||||
draftReplyInfo,
|
||||
isPrivate,
|
||||
areChatSettingsLoaded: Boolean(chat?.settings),
|
||||
arePeerSettingsLoaded: Boolean(userFullInfo?.settings),
|
||||
isComments: isMessageThread,
|
||||
canPost:
|
||||
!isPinnedMessageList
|
||||
|
||||
@ -4,12 +4,13 @@ import React, {
|
||||
import { setExtraStyles } from '../../lib/teact/teact-dom';
|
||||
import { withGlobal } from '../../global';
|
||||
|
||||
import type { ApiChat, ApiUserFullInfo } from '../../api/types';
|
||||
import type { MessageListType, ThreadId } from '../../types';
|
||||
import type { Signal } from '../../util/signals';
|
||||
import { type ApiChat, MAIN_THREAD_ID } from '../../api/types';
|
||||
import { MAIN_THREAD_ID } from '../../api/types';
|
||||
|
||||
import {
|
||||
selectChat, selectChatMessage, selectCurrentMiddleSearch, selectTabState,
|
||||
selectChat, selectChatMessage, selectCurrentMiddleSearch, selectTabState, selectUserFullInfo,
|
||||
} from '../../global/selectors';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
|
||||
@ -40,6 +41,7 @@ type OwnProps = {
|
||||
|
||||
type StateProps = {
|
||||
chat?: ApiChat;
|
||||
userFullInfo?: ApiUserFullInfo;
|
||||
isAudioPlayerRendered?: boolean;
|
||||
isMiddleSearchOpen?: boolean;
|
||||
};
|
||||
@ -52,13 +54,14 @@ const MiddleHeaderPanes = ({
|
||||
threadId,
|
||||
messageListType,
|
||||
chat,
|
||||
userFullInfo,
|
||||
getCurrentPinnedIndex,
|
||||
getLoadingPinnedId,
|
||||
isAudioPlayerRendered,
|
||||
isMiddleSearchOpen,
|
||||
onFocusPinnedMessage,
|
||||
}: OwnProps & StateProps) => {
|
||||
const { settings } = chat || {};
|
||||
const { settings } = userFullInfo || {};
|
||||
|
||||
const { isDesktop } = useAppLayout();
|
||||
const [getAudioPlayerState, setAudioPlayerState] = useSignal<PaneState>(FALLBACK_PANE_STATE);
|
||||
@ -163,6 +166,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
}): StateProps => {
|
||||
const { audioPlayer } = selectTabState(global);
|
||||
const chat = selectChat(global, chatId);
|
||||
const userFullInfo = selectUserFullInfo(global, chatId);
|
||||
|
||||
const { chatId: audioChatId, messageId: audioMessageId } = audioPlayer;
|
||||
const audioMessage = audioChatId && audioMessageId
|
||||
@ -173,6 +177,7 @@ export default memo(withGlobal<OwnProps>(
|
||||
|
||||
return {
|
||||
chat,
|
||||
userFullInfo,
|
||||
isAudioPlayerRendered: Boolean(audioMessage),
|
||||
isMiddleSearchOpen,
|
||||
};
|
||||
|
||||
@ -14,7 +14,7 @@ import {
|
||||
isGeoLiveExpired,
|
||||
} from '../../../global/helpers';
|
||||
import buildClassName from '../../../util/buildClassName';
|
||||
import { formatCountdownShort, formatLastUpdated } from '../../../util/dates/dateFormat';
|
||||
import { formatCountdownShort, formatLocationLastUpdate } from '../../../util/dates/dateFormat';
|
||||
import {
|
||||
getMetersPerPixel, getVenueColor, getVenueIconUrl,
|
||||
} from '../../../util/map';
|
||||
@ -161,7 +161,7 @@ const Location: FC<OwnProps> = ({
|
||||
<div className="location-info">
|
||||
<div className="location-info-title">{lang('AttachLiveLocation')}</div>
|
||||
<div className="location-info-subtitle">
|
||||
{formatLastUpdated(lang, serverTime, message.editDate)}
|
||||
{formatLocationLastUpdate(lang, serverTime, message.editDate)}
|
||||
</div>
|
||||
{!isExpired && (
|
||||
<div className="geo-countdown" ref={countdownRef}>
|
||||
|
||||
@ -59,7 +59,7 @@ const ChatReportPane: FC<OwnProps & StateProps> = ({
|
||||
deleteChatUser,
|
||||
deleteHistory,
|
||||
toggleChatArchived,
|
||||
hideChatReportPane,
|
||||
hidePeerSettingsBar,
|
||||
} = getActions();
|
||||
|
||||
const lang = useOldLang();
|
||||
@ -96,7 +96,7 @@ const ChatReportPane: FC<OwnProps & StateProps> = ({
|
||||
});
|
||||
|
||||
const handleCloseReportPane = useLastCallback(() => {
|
||||
hideChatReportPane({ chatId });
|
||||
hidePeerSettingsBar({ peerId: chatId });
|
||||
});
|
||||
|
||||
const handleChatReportSpam = useLastCallback(() => {
|
||||
|
||||
@ -24,10 +24,11 @@ import {
|
||||
selectPerformanceSettingsValue,
|
||||
selectTabState,
|
||||
selectUser,
|
||||
selectUserFullInfo,
|
||||
} from '../../global/selectors';
|
||||
import buildClassName from '../../util/buildClassName';
|
||||
import captureKeyboardListeners from '../../util/captureKeyboardListeners';
|
||||
import { formatMediaDuration, formatRelativeTime } from '../../util/dates/dateFormat';
|
||||
import { formatMediaDuration, formatRelativePastTime } from '../../util/dates/dateFormat';
|
||||
import download from '../../util/download';
|
||||
import { round } from '../../util/math';
|
||||
import { getServerTime } from '../../util/serverTime';
|
||||
@ -94,7 +95,7 @@ interface StateProps {
|
||||
storyChangelogUserId?: string;
|
||||
viewersExpirePeriod: number;
|
||||
isChatExist?: boolean;
|
||||
areChatSettingsLoaded?: boolean;
|
||||
arePeerSettingsLoaded?: boolean;
|
||||
isCurrentUserPremium?: boolean;
|
||||
stealthMode: ApiStealthMode;
|
||||
withHeaderAnimation?: boolean;
|
||||
@ -122,7 +123,7 @@ function Story({
|
||||
storyChangelogUserId,
|
||||
viewersExpirePeriod,
|
||||
isChatExist,
|
||||
areChatSettingsLoaded,
|
||||
arePeerSettingsLoaded,
|
||||
getIsAnimating,
|
||||
isCurrentUserPremium,
|
||||
stealthMode,
|
||||
@ -143,7 +144,7 @@ function Story({
|
||||
openChat,
|
||||
showNotification,
|
||||
openStoryPrivacyEditor,
|
||||
loadChatSettings,
|
||||
loadPeerSettings,
|
||||
fetchChat,
|
||||
loadStoryViews,
|
||||
toggleStealthModal,
|
||||
@ -279,10 +280,10 @@ function Story({
|
||||
}
|
||||
}, [isChatExist, peerId]);
|
||||
useEffect(() => {
|
||||
if (isChatExist && !areChatSettingsLoaded) {
|
||||
loadChatSettings({ chatId: peerId });
|
||||
if (isChatExist && !arePeerSettingsLoaded) {
|
||||
loadPeerSettings({ peerId });
|
||||
}
|
||||
}, [areChatSettingsLoaded, isChatExist, peerId]);
|
||||
}, [arePeerSettingsLoaded, isChatExist, peerId]);
|
||||
|
||||
const handlePauseStory = useLastCallback(() => {
|
||||
if (isVideo) {
|
||||
@ -672,7 +673,7 @@ function Story({
|
||||
</span>
|
||||
)}
|
||||
{story && 'date' in story && (
|
||||
<span className={styles.storyMeta}>{formatRelativeTime(lang, serverTime, story.date)}</span>
|
||||
<span className={styles.storyMeta}>{formatRelativePastTime(lang, serverTime, story.date)}</span>
|
||||
)}
|
||||
{isLoadedStory && story.isEdited && (
|
||||
<span className={styles.storyMeta}>{lang('Story.HeaderEdited')}</span>
|
||||
@ -906,6 +907,7 @@ export default memo(withGlobal<OwnProps>((global, {
|
||||
const { appConfig } = global;
|
||||
const user = selectUser(global, peerId);
|
||||
const chat = selectChat(global, peerId);
|
||||
const userFullInfo = selectUserFullInfo(global, peerId);
|
||||
const tabState = selectTabState(global);
|
||||
const {
|
||||
storyViewer: {
|
||||
@ -951,7 +953,7 @@ export default memo(withGlobal<OwnProps>((global, {
|
||||
storyChangelogUserId: appConfig!.storyChangelogUserId,
|
||||
viewersExpirePeriod: appConfig!.storyExpirePeriod + appConfig!.storyViewersExpirePeriod,
|
||||
isChatExist: Boolean(chat),
|
||||
areChatSettingsLoaded: Boolean(chat?.settings),
|
||||
arePeerSettingsLoaded: Boolean(userFullInfo?.settings),
|
||||
stealthMode: global.stories.stealthMode,
|
||||
withHeaderAnimation,
|
||||
};
|
||||
|
||||
@ -2130,21 +2130,6 @@ addActionHandler('fetchChat', (global, actions, payload): ActionReturnType => {
|
||||
}
|
||||
});
|
||||
|
||||
addActionHandler('loadChatSettings', async (global, actions, payload): Promise<void> => {
|
||||
const { chatId } = payload;
|
||||
const chat = selectChat(global, chatId);
|
||||
if (!chat) return;
|
||||
|
||||
const result = await callApi('fetchChatSettings', chat);
|
||||
if (!result) return;
|
||||
|
||||
const { settings } = result;
|
||||
|
||||
global = getGlobal();
|
||||
global = updateChat(global, chat.id, { settings });
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('toggleJoinToSend', async (global, actions, payload): Promise<void> => {
|
||||
const { chatId, isEnabled } = payload;
|
||||
const chat = selectChat(global, chatId);
|
||||
|
||||
@ -7,7 +7,7 @@ import { callApi } from '../../../api/gramjs';
|
||||
import { getUserFirstOrLastName } from '../../helpers';
|
||||
import { addActionHandler, getGlobal, setGlobal } from '../../index';
|
||||
import {
|
||||
updateChat, updateChatFullInfo, updateManagement, updateManagementProgress,
|
||||
updateChat, updateChatFullInfo, updateManagement, updateManagementProgress, updateUserFullInfo,
|
||||
} from '../../reducers';
|
||||
import {
|
||||
selectChat, selectCurrentMessageList, selectTabState, selectUser,
|
||||
@ -394,16 +394,16 @@ addActionHandler('hideAllChatJoinRequests', async (global, actions, payload): Pr
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('hideChatReportPane', async (global, actions, payload): Promise<void> => {
|
||||
const { chatId } = payload!;
|
||||
const chat = selectChat(global, chatId);
|
||||
if (!chat) return;
|
||||
addActionHandler('hidePeerSettingsBar', async (global, actions, payload): Promise<void> => {
|
||||
const { peerId } = payload!;
|
||||
const user = selectUser(global, peerId);
|
||||
if (!user) return;
|
||||
|
||||
const result = await callApi('hideChatReportPane', chat);
|
||||
const result = await callApi('hidePeerSettingsBar', user);
|
||||
if (!result) return;
|
||||
|
||||
global = getGlobal();
|
||||
global = updateChat(global, chatId, {
|
||||
global = updateUserFullInfo(global, peerId, {
|
||||
settings: undefined,
|
||||
});
|
||||
setGlobal(global);
|
||||
|
||||
@ -217,7 +217,7 @@ addActionHandler('updateContact', async (global, actions, payload): Promise<void
|
||||
}
|
||||
|
||||
if (result) {
|
||||
actions.loadChatSettings({ chatId: userId });
|
||||
actions.loadPeerSettings({ peerId: userId });
|
||||
actions.loadPeerStories({ peerId: userId });
|
||||
|
||||
global = getGlobal();
|
||||
@ -506,6 +506,30 @@ addActionHandler('openSuggestedStatusModal', async (global, actions, payload): P
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('loadPeerSettings', async (global, actions, payload): Promise<void> => {
|
||||
const { peerId } = payload;
|
||||
|
||||
const userFullInfo = selectUserFullInfo(global, peerId);
|
||||
if (!userFullInfo) {
|
||||
actions.loadFullUser({ userId: peerId });
|
||||
return;
|
||||
}
|
||||
|
||||
const user = selectUser(global, peerId);
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await callApi('fetchPeerSettings', user);
|
||||
if (!result) return;
|
||||
|
||||
const { settings } = result;
|
||||
|
||||
global = getGlobal();
|
||||
global = updateUserFullInfo(global, peerId, { settings });
|
||||
setGlobal(global);
|
||||
});
|
||||
|
||||
addActionHandler('markBotVerificationInfoShown', (global, actions, payload): ActionReturnType => {
|
||||
const { peerId } = payload;
|
||||
|
||||
|
||||
@ -106,6 +106,21 @@ addActionHandler('apiUpdate', (global, actions, update): ActionReturnType => {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
case 'updatePeerSettings': {
|
||||
const { id, settings } = update;
|
||||
|
||||
const targetUserFullInfo = selectUserFullInfo(global, id);
|
||||
if (!targetUserFullInfo?.botInfo) {
|
||||
actions.loadFullUser({ userId: id });
|
||||
return undefined;
|
||||
}
|
||||
|
||||
global = updateUserFullInfo(global, id, {
|
||||
settings,
|
||||
});
|
||||
return global;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
@ -14,7 +14,6 @@ import { getCurrentTabId } from '../../util/establishMultitabRole';
|
||||
import { omit, omitUndefined, unique } from '../../util/iteratees';
|
||||
import { MEMO_EMPTY_ARRAY } from '../../util/memo';
|
||||
import { selectTabState } from '../selectors';
|
||||
import { updateChat } from './chats';
|
||||
import { updateTabState } from './tabs';
|
||||
|
||||
export function replaceUsers<T extends GlobalState>(global: T, newById: Record<string, ApiUser>): T {
|
||||
@ -179,7 +178,7 @@ export function deleteContact<T extends GlobalState>(global: T, userId: string):
|
||||
},
|
||||
};
|
||||
|
||||
return updateChat(global, userId, {
|
||||
return updateUserFullInfo(global, userId, {
|
||||
settings: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
@ -918,8 +918,8 @@ export interface ActionPayloads {
|
||||
offsetUserId?: string;
|
||||
limit?: number;
|
||||
} & WithTabId;
|
||||
hideChatReportPane: {
|
||||
chatId: string;
|
||||
hidePeerSettingsBar: {
|
||||
peerId: string;
|
||||
};
|
||||
toggleManagement: ({
|
||||
force?: boolean;
|
||||
@ -1032,8 +1032,8 @@ export interface ActionPayloads {
|
||||
};
|
||||
|
||||
// Chats
|
||||
loadChatSettings: {
|
||||
chatId: string;
|
||||
loadPeerSettings: {
|
||||
peerId: string;
|
||||
};
|
||||
fetchChat: {
|
||||
chatId: string;
|
||||
|
||||
@ -58,6 +58,8 @@ $color-message-reaction-own-hover: #b5e0a4;
|
||||
$color-message-reaction-chosen-hover: #1a82ea;
|
||||
$color-message-reaction-chosen-hover-own: #3f9d4b;
|
||||
|
||||
$color-message-non-contact: #cceebf;
|
||||
|
||||
$color-message-story-mention-from: #4ef390;
|
||||
$color-message-story-mention-to: #74bcff;
|
||||
|
||||
@ -151,6 +153,8 @@ $color-message-story-mention-to: #74bcff;
|
||||
--color-message-reaction-chosen-hover: $color-message-reaction-chosen-hover;
|
||||
--color-message-reaction-chosen-hover-own: $color-message-reaction-chosen-hover-own;
|
||||
|
||||
--color-message-non-contact: $color-message-non-contact;
|
||||
|
||||
--color-message-story-mention-from: $color-message-story-mention-from;
|
||||
--color-message-story-mention-to: $color-message-story-mention-to;
|
||||
|
||||
|
||||
@ -400,6 +400,7 @@ body:not(.is-ios) {
|
||||
--color-message-reaction-hover-own: rgb(91, 82, 155);
|
||||
--color-message-reaction-chosen-hover: rgb(120, 100, 221);
|
||||
--color-message-reaction-chosen-hover-own: rgb(245, 245, 245);
|
||||
--color-message-non-contact: rgb(204, 238, 191);
|
||||
--color-voice-transcribe-button: rgb(42, 42, 60);
|
||||
--color-voice-transcribe-button-own: rgb(131, 115, 211);
|
||||
--color-topic-blue: rgb(111, 249, 240);
|
||||
|
||||
@ -57,6 +57,7 @@
|
||||
"--color-message-reaction-hover-own": ["#b5e0a4", "#5B529B"],
|
||||
"--color-message-reaction-chosen-hover": ["#1a82ea", "#7864dd"],
|
||||
"--color-message-reaction-chosen-hover-own": ["#3f9d4b", "#f5f5f5"],
|
||||
"--color-message-non-contact": ["#cceebf", "#AAAAAA"],
|
||||
"--color-voice-transcribe-button": ["#e8f3ff", "#2a2a3c"],
|
||||
"--color-voice-transcribe-button-own": ["#cceebf", "#8373d3"],
|
||||
"--color-topic-blue": ["#2F7772", "#6ff9f0"],
|
||||
|
||||
29
src/types/language.d.ts
vendored
29
src/types/language.d.ts
vendored
@ -224,6 +224,10 @@ export interface LangPair {
|
||||
'WalletAddressCopied': undefined;
|
||||
'Copy': undefined;
|
||||
'DeleteAndStop': undefined;
|
||||
'ChatNonContactUserSubtitle': undefined;
|
||||
'ChatNonContactUserGroups': undefined;
|
||||
'ContactInfoRegistration': undefined;
|
||||
'ContactInfoNotVerified': undefined;
|
||||
'DeleteForAll': undefined;
|
||||
'DeleteSingleMessagesTitle': undefined;
|
||||
'AreYouSureDeleteSingleMessage': undefined;
|
||||
@ -961,6 +965,7 @@ export interface LangPair {
|
||||
'WeekdaySaturday': undefined;
|
||||
'WeekdaySunday': undefined;
|
||||
'WeekdayToday': undefined;
|
||||
'Today': undefined;
|
||||
'WeekdayYesterday': undefined;
|
||||
'User': undefined;
|
||||
'SecretChat': undefined;
|
||||
@ -1010,6 +1015,7 @@ export interface LangPair {
|
||||
'VoipIncoming': undefined;
|
||||
'LiveLocationUpdatedJustNow': undefined;
|
||||
'RightNow': undefined;
|
||||
'JustNowAgo': undefined;
|
||||
'AudioPause': undefined;
|
||||
'AudioPlay': undefined;
|
||||
'ToggleUserNotifications': undefined;
|
||||
@ -1721,6 +1727,9 @@ export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
||||
'LiveLocationUpdatedTodayAt': {
|
||||
'time': V;
|
||||
};
|
||||
'AtDateAgo': {
|
||||
'date': V;
|
||||
};
|
||||
'MediaViewDownloading': {
|
||||
'count': V;
|
||||
};
|
||||
@ -1792,6 +1801,14 @@ export interface LangPairWithVariables<V extends unknown = LangVariable> {
|
||||
'peer': V;
|
||||
'amount': V;
|
||||
};
|
||||
'UserUpdatedName': {
|
||||
'user': V;
|
||||
'time': V;
|
||||
};
|
||||
'UserUpdatedPhoto': {
|
||||
'user': V;
|
||||
'time': V;
|
||||
};
|
||||
'GiftInfoSaved': {
|
||||
'link': V;
|
||||
};
|
||||
@ -2394,6 +2411,15 @@ export interface LangPairPluralWithVariables<V extends unknown = LangVariable> {
|
||||
'Minutes': {
|
||||
'count': V;
|
||||
};
|
||||
'MinutesAgo': {
|
||||
'count': V;
|
||||
};
|
||||
'HoursAgo': {
|
||||
'count': V;
|
||||
};
|
||||
'DaysAgo': {
|
||||
'count': V;
|
||||
};
|
||||
'PreviewForwardedMessage': {
|
||||
'count': V;
|
||||
};
|
||||
@ -2436,6 +2462,9 @@ export interface LangPairPluralWithVariables<V extends unknown = LangVariable> {
|
||||
'GiftWithdrawWait': {
|
||||
'days': V;
|
||||
};
|
||||
'ChatGroups': {
|
||||
'count': V;
|
||||
};
|
||||
'StarsAmountText': {
|
||||
'amount': V;
|
||||
};
|
||||
|
||||
@ -2,8 +2,9 @@ import type { OldLangFn } from '../../hooks/useOldLang';
|
||||
import type { TimeFormat } from '../../types';
|
||||
import type { LangFn } from '../localization';
|
||||
|
||||
import { getServerTime } from '../serverTime';
|
||||
import withCache from '../withCache';
|
||||
import { getDays } from './units';
|
||||
import { getDays, getHours, getMinutes } from './units';
|
||||
|
||||
const WEEKDAYS_FULL = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
||||
const MONTHS_FULL = [
|
||||
@ -78,11 +79,11 @@ export function formatPastTimeShort(lang: OldLangFn, datetime: number | Date, al
|
||||
return alwaysShowTime ? lang('FullDateTimeFormat', [formattedDate, time]) : formattedDate;
|
||||
}
|
||||
|
||||
export function formatFullDate(lang: OldLangFn, datetime: number | Date) {
|
||||
export function formatFullDate(lang: OldLangFn | LangFn, datetime: number | Date) {
|
||||
return formatDateToString(datetime, lang.code, false, 'numeric');
|
||||
}
|
||||
|
||||
export function formatMonthAndYear(lang: OldLangFn, date: Date, isShort = false) {
|
||||
export function formatMonthAndYear(lang: OldLangFn | LangFn, date: Date, isShort = false) {
|
||||
return formatDateToString(date, lang.code, false, isShort ? 'short' : 'long', true);
|
||||
}
|
||||
|
||||
@ -122,7 +123,7 @@ export function formatCountdownShort(lang: OldLangFn, msLeft: number): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function formatLastUpdated(lang: OldLangFn, currentTime: number, lastUpdated = currentTime) {
|
||||
export function formatLocationLastUpdate(lang: OldLangFn, currentTime: number, lastUpdated = currentTime) {
|
||||
const seconds = currentTime - lastUpdated;
|
||||
if (seconds < 60) {
|
||||
return lang('LiveLocationUpdated.JustNow');
|
||||
@ -133,7 +134,7 @@ export function formatLastUpdated(lang: OldLangFn, currentTime: number, lastUpda
|
||||
}
|
||||
}
|
||||
|
||||
export function formatRelativeTime(lang: OldLangFn, currentTime: number, lastUpdated = currentTime) {
|
||||
export function formatRelativePastTime(lang: OldLangFn, currentTime: number, lastUpdated = currentTime) {
|
||||
const seconds = currentTime - lastUpdated;
|
||||
|
||||
if (seconds < 60) {
|
||||
@ -160,6 +161,31 @@ export function formatRelativeTime(lang: OldLangFn, currentTime: number, lastUpd
|
||||
return lang('Time.AtDate', formatFullDate(lang, lastUpdatedDate));
|
||||
}
|
||||
|
||||
export function formatPastDatetime(lang: LangFn, pastTime: number, currentTime = getServerTime()) {
|
||||
const seconds = currentTime - pastTime;
|
||||
const minutes = getMinutes(seconds);
|
||||
const hours = getHours(seconds);
|
||||
const days = getDays(seconds);
|
||||
|
||||
if (seconds < 60) {
|
||||
return lang('JustNowAgo');
|
||||
}
|
||||
|
||||
if (minutes < 60) {
|
||||
return lang('MinutesAgo', { count: minutes }, { pluralValue: minutes });
|
||||
}
|
||||
|
||||
if (hours < 24) {
|
||||
return lang('HoursAgo', { count: hours }, { pluralValue: hours });
|
||||
}
|
||||
|
||||
if (days < 28) {
|
||||
return lang('DaysAgo', { count: days }, { pluralValue: days });
|
||||
}
|
||||
|
||||
return lang('AtDateAgo', { date: formatFullDate(lang, pastTime) });
|
||||
}
|
||||
|
||||
type DurationType = 'Seconds' | 'Minutes' | 'Hours' | 'Days' | 'Weeks';
|
||||
|
||||
export function formatTimeDuration(lang: OldLangFn, duration: number, showLast = 2) {
|
||||
@ -449,3 +475,10 @@ function lowerFirst(str: string) {
|
||||
function upperFirst(str: string) {
|
||||
return `${str[0].toUpperCase()}${str.slice(1)}`;
|
||||
}
|
||||
|
||||
export function formatRegistrationMonth(lang: string, dateString: string) {
|
||||
const [month, year] = dateString.split('.');
|
||||
const date = new Date(`${year}-${month}`);
|
||||
|
||||
return new Intl.DateTimeFormat(lang, { month: 'long', year: 'numeric' }).format(date);
|
||||
}
|
||||
|
||||
@ -429,7 +429,7 @@ function processTranslationAdvanced(
|
||||
if (value === undefined) return result;
|
||||
|
||||
const preparedValue = Number.isFinite(value) ? formatters!.number.format(value as number) : value;
|
||||
return replaceInStringsWithTeact(result, `{${key}}`, preparedValue);
|
||||
return replaceInStringsWithTeact(result, `{${key}}`, renderText(preparedValue));
|
||||
}, [part] as TeactNode[]);
|
||||
},
|
||||
});
|
||||
@ -440,7 +440,7 @@ function processTranslationAdvanced(
|
||||
if (value === undefined) return result;
|
||||
|
||||
const preparedValue = Number.isFinite(value) ? formatters!.number.format(value as number) : value;
|
||||
return replaceInStringsWithTeact(result, `{${key}}`, preparedValue);
|
||||
return replaceInStringsWithTeact(result, `{${key}}`, renderText(preparedValue));
|
||||
}, tempResult);
|
||||
}
|
||||
|
||||
|
||||
@ -3,8 +3,8 @@ import type { ApiCountryCode } from '../api/types';
|
||||
const PATTERN_PLACEHOLDER = 'X';
|
||||
const DEFAULT_PATTERN = 'XXX XXX XXX XXX';
|
||||
|
||||
export function getCountryCodesByIso(phoneCodeList: ApiCountryCode[], iso: string) {
|
||||
return phoneCodeList.filter((country) => country.iso2 === iso);
|
||||
export function getCountryCodeByIso(phoneCodeList: ApiCountryCode[], iso: string) {
|
||||
return phoneCodeList.find((country) => country.iso2 === iso);
|
||||
}
|
||||
|
||||
export function getCountryFromPhoneNumber(phoneCodeList: ApiCountryCode[], input = '') {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user