2024-02-23 14:06:22 +01:00

324 lines
9.6 KiB
TypeScript

import type { FC } from '../../lib/teact/teact';
import React, {
memo, useEffect, useMemo, useState,
} from '../../lib/teact/teact';
import { getActions, withGlobal } from '../../global';
import type {
ApiChat, ApiCountryCode, ApiUser, ApiUsername,
} from '../../api/types';
import { MAIN_THREAD_ID } from '../../api/types';
import { TME_LINK_PREFIX } from '../../config';
import {
getChatLink,
getHasAdminRight,
isChatChannel,
isUserId,
isUserRightBanned,
selectIsChatMuted,
} from '../../global/helpers';
import {
selectChat,
selectChatFullInfo,
selectCurrentMessageList,
selectNotifyExceptions,
selectNotifySettings,
selectTopicLink,
selectUser,
selectUserFullInfo,
} from '../../global/selectors';
import { copyTextToClipboard } from '../../util/clipboard';
import { formatPhoneNumberWithCode } from '../../util/phoneNumber';
import { debounce } from '../../util/schedulers';
import stopEvent from '../../util/stopEvent';
import renderText from './helpers/renderText';
import useEffectWithPrevDeps from '../../hooks/useEffectWithPrevDeps';
import useLang from '../../hooks/useLang';
import useLastCallback from '../../hooks/useLastCallback';
import ListItem from '../ui/ListItem';
import Switcher from '../ui/Switcher';
type OwnProps = {
chatOrUserId: string;
isSavedDialog?: boolean;
isInSettings?: boolean;
};
type StateProps =
{
user?: ApiUser;
chat?: ApiChat;
canInviteUsers?: boolean;
isMuted?: boolean;
phoneCodeList: ApiCountryCode[];
topicId?: number;
description?: string;
chatInviteLink?: string;
topicLink?: string;
hasSavedMessages?: boolean;
};
const runDebounced = debounce((cb) => cb(), 500, false);
const ChatExtra: FC<OwnProps & StateProps> = ({
chatOrUserId,
user,
chat,
isInSettings,
canInviteUsers,
isMuted,
phoneCodeList,
topicId,
description,
chatInviteLink,
topicLink,
hasSavedMessages,
}) => {
const {
loadFullUser,
showNotification,
updateChatMutedState,
updateTopicMutedState,
loadPeerStories,
openSavedDialog,
} = getActions();
const {
id: userId,
usernames,
phoneNumber,
isSelf,
} = user || {};
const { id: chatId, usernames: chatUsernames } = chat || {};
const peerId = userId || chatId;
const lang = useLang();
const [areNotificationsEnabled, setAreNotificationsEnabled] = useState(!isMuted);
useEffect(() => {
setAreNotificationsEnabled(!isMuted);
}, [isMuted]);
useEffect(() => {
if (!userId) return;
loadFullUser({ userId });
}, [userId]);
useEffectWithPrevDeps(([prevPeerId]) => {
if (!peerId || prevPeerId === peerId) return;
if (user || (chat && isChatChannel(chat))) {
loadPeerStories({ peerId });
}
}, [peerId, chat, user]);
const isTopicInfo = Boolean(topicId && topicId !== MAIN_THREAD_ID);
const activeUsernames = useMemo(() => {
const result = usernames?.filter((u) => u.isActive);
return result?.length ? result : undefined;
}, [usernames]);
const activeChatUsernames = useMemo(() => {
const result = !user ? chatUsernames?.filter((u) => u.isActive) : undefined;
return result?.length ? result : undefined;
}, [chatUsernames, user]);
const link = useMemo(() => {
if (!chat) {
return undefined;
}
return isTopicInfo ? topicLink! : getChatLink(chat) || chatInviteLink;
}, [chat, isTopicInfo, topicLink, chatInviteLink]);
const handleNotificationChange = useLastCallback(() => {
setAreNotificationsEnabled((current) => {
const newAreNotificationsEnabled = !current;
runDebounced(() => {
if (isTopicInfo) {
updateTopicMutedState({
chatId: chatId!,
topicId: topicId!,
isMuted: !newAreNotificationsEnabled,
});
} else {
updateChatMutedState({ chatId: chatId!, isMuted: !newAreNotificationsEnabled });
}
});
return newAreNotificationsEnabled;
});
});
const handleOpenSavedDialog = useLastCallback(() => {
openSavedDialog({ chatId: chatOrUserId });
});
if (!chat || chat.isRestricted || (isSelf && !isInSettings)) {
return undefined;
}
function copy(text: string, entity: string) {
copyTextToClipboard(text);
showNotification({ message: `${entity} was copied` });
}
const formattedNumber = phoneNumber && formatPhoneNumberWithCode(phoneCodeList, phoneNumber);
function renderUsernames(usernameList: ApiUsername[], isChat?: boolean) {
const [mainUsername, ...otherUsernames] = usernameList;
const usernameLinks = otherUsernames.length
? (lang('UsernameAlso', '%USERNAMES%') as string)
.split('%')
.map((s) => {
return (s === 'USERNAMES' ? (
<>
{otherUsernames.map(({ username: nick }, idx) => {
const textToCopy = isChat ? `${TME_LINK_PREFIX}${nick}` : `@${nick}`;
return (
<>
{idx > 0 ? ', ' : ''}
<a
key={nick}
href={`${TME_LINK_PREFIX}${nick}`}
onClick={(e) => {
stopEvent(e);
copy(textToCopy, lang(isChat ? 'Link' : 'Username'));
}}
className="text-entity-link username-link"
>
{`@${nick}`}
</a>
</>
);
})}
</>
) : s);
})
: undefined;
const username = isChat ? `t.me/${mainUsername.username}` : mainUsername.username;
const textToCopy = isChat ? `${TME_LINK_PREFIX}${mainUsername.username}` : `@${mainUsername.username}`;
return (
<ListItem
icon={isChat ? 'link' : 'mention'}
multiline
narrow
ripple
// eslint-disable-next-line react/jsx-no-bind
onClick={() => copy(textToCopy, lang(isChat ? 'Link' : 'Username'))}
>
<span className="title" dir="auto">{username}</span>
<span className="subtitle">
{usernameLinks && <span className="other-usernames">{usernameLinks}</span>}
{lang(isChat ? 'Link' : 'Username')}
</span>
</ListItem>
);
}
return (
<div className="ChatExtra">
{formattedNumber && Boolean(formattedNumber.length) && (
// eslint-disable-next-line react/jsx-no-bind
<ListItem icon="phone" multiline narrow ripple onClick={() => copy(formattedNumber, lang('Phone'))}>
<span className="title" dir="auto">{formattedNumber}</span>
<span className="subtitle">{lang('Phone')}</span>
</ListItem>
)}
{activeUsernames && renderUsernames(activeUsernames)}
{description && Boolean(description.length) && (
<ListItem
icon="info"
multiline
narrow
isStatic
>
<span className="title word-break allow-selection" dir="auto">
{renderText(description, ['br', 'links', 'emoji'])}
</span>
<span className="subtitle">{lang(userId ? 'UserBio' : 'Info')}</span>
</ListItem>
)}
{activeChatUsernames && !isTopicInfo && renderUsernames(activeChatUsernames, true)}
{((!activeChatUsernames && canInviteUsers) || isTopicInfo) && link && (
<ListItem
icon="link"
multiline
narrow
ripple
// eslint-disable-next-line react/jsx-no-bind
onClick={() => copy(link, lang('SetUrlPlaceholder'))}
>
<div className="title">{link}</div>
<span className="subtitle">{lang('SetUrlPlaceholder')}</span>
</ListItem>
)}
{!isInSettings && (
<ListItem icon="unmute" ripple onClick={handleNotificationChange}>
<span>{lang('Notifications')}</span>
<Switcher
id="group-notifications"
label={userId ? 'Toggle User Notifications' : 'Toggle Chat Notifications'}
checked={areNotificationsEnabled}
inactive
/>
</ListItem>
)}
{hasSavedMessages && !isInSettings && (
<ListItem icon="saved-messages" ripple onClick={handleOpenSavedDialog}>
<span>{lang('SavedMessagesTab')}</span>
</ListItem>
)}
</div>
);
};
export default memo(withGlobal<OwnProps>(
(global, { chatOrUserId, isSavedDialog }): StateProps => {
const { countryList: { phoneCodes: phoneCodeList } } = global;
const chat = chatOrUserId ? selectChat(global, chatOrUserId) : undefined;
const user = isUserId(chatOrUserId) ? selectUser(global, chatOrUserId) : undefined;
const isForum = chat?.isForum;
const isMuted = chat && selectIsChatMuted(chat, selectNotifySettings(global), selectNotifyExceptions(global));
const { threadId } = selectCurrentMessageList(global) || {};
const topicId = isForum ? Number(threadId) : undefined;
const chatInviteLink = chat ? selectChatFullInfo(global, chat.id)?.inviteLink : undefined;
let description = user ? selectUserFullInfo(global, user.id)?.bio : undefined;
if (!description && chat) {
description = selectChatFullInfo(global, chat.id)?.about;
}
const canInviteUsers = chat && !user && (
(!isChatChannel(chat) && !isUserRightBanned(chat, 'inviteUsers'))
|| getHasAdminRight(chat, 'inviteUsers')
);
const topicLink = topicId ? selectTopicLink(global, chatOrUserId, topicId) : undefined;
const hasSavedMessages = !isSavedDialog && global.chats.listIds.saved?.includes(chatOrUserId);
return {
phoneCodeList,
chat,
user,
canInviteUsers,
isMuted,
topicId,
chatInviteLink,
description,
topicLink,
hasSavedMessages,
};
},
)(ChatExtra));