Left Search, New Chat, Forward Picker: Order chats by last message, check rights to post, add archived

This commit is contained in:
Alexander Zinchuk 2021-05-06 01:47:48 +03:00
parent 79cde7292b
commit 1984318297
17 changed files with 165 additions and 126 deletions

View File

@ -163,8 +163,8 @@ export function fetchFullChat(chat: ApiChat) {
: getFullChatInfo(input as number); : getFullChatInfo(input as number);
} }
export async function searchChats({ query, limit }: { query: string; limit?: number }) { export async function searchChats({ query }: { query: string }) {
const result = await invokeRequest(new GramJs.contacts.Search({ q: query, limit })); const result = await invokeRequest(new GramJs.contacts.Search({ q: query }));
if (!result) { if (!result) {
return undefined; return undefined;
} }

View File

@ -27,7 +27,7 @@ type OwnProps = {
typingStatus?: ApiTypingStatus; typingStatus?: ApiTypingStatus;
avatarSize?: 'small' | 'medium' | 'large' | 'jumbo'; avatarSize?: 'small' | 'medium' | 'large' | 'jumbo';
withMediaViewer?: boolean; withMediaViewer?: boolean;
withHandle?: boolean; withUsername?: boolean;
withFullInfo?: boolean; withFullInfo?: boolean;
withUpdatingStatus?: boolean; withUpdatingStatus?: boolean;
withChatType?: boolean; withChatType?: boolean;
@ -45,7 +45,7 @@ const GroupChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
typingStatus, typingStatus,
avatarSize = 'medium', avatarSize = 'medium',
withMediaViewer, withMediaViewer,
withHandle, withUsername,
withFullInfo, withFullInfo,
withUpdatingStatus, withUpdatingStatus,
withChatType, withChatType,
@ -102,7 +102,7 @@ const GroupChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
); );
} }
const handle = withHandle ? chat.username : undefined; const handle = withUsername ? chat.username : undefined;
const groupStatus = getGroupStatus(chat, lang); const groupStatus = getGroupStatus(chat, lang);
const onlineStatus = onlineCount ? `, ${lang('OnlineCount', onlineCount, 'i')}` : undefined; const onlineStatus = onlineCount ? `, ${lang('OnlineCount', onlineCount, 'i')}` : undefined;

View File

@ -25,7 +25,7 @@ type OwnProps = {
forceShowSelf?: boolean; forceShowSelf?: boolean;
status?: string; status?: string;
withMediaViewer?: boolean; withMediaViewer?: boolean;
withHandle?: boolean; withUsername?: boolean;
withFullInfo?: boolean; withFullInfo?: boolean;
withUpdatingStatus?: boolean; withUpdatingStatus?: boolean;
noStatusOrTyping?: boolean; noStatusOrTyping?: boolean;
@ -44,7 +44,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
avatarSize = 'medium', avatarSize = 'medium',
status, status,
withMediaViewer, withMediaViewer,
withHandle, withUsername,
withFullInfo, withFullInfo,
withUpdatingStatus, withUpdatingStatus,
noStatusOrTyping, noStatusOrTyping,
@ -103,7 +103,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
return ( return (
<div className={`status ${isUserOnline(user) ? 'online' : ''}`}> <div className={`status ${isUserOnline(user) ? 'online' : ''}`}>
{withHandle && user.username && <span className="handle">{user.username}</span>} {withUsername && user.username && <span className="handle">{user.username}</span>}
<span className="user-status">{getUserStatus(user, lang)}</span> <span className="user-status">{getUserStatus(user, lang)}</span>
</div> </div>
); );
@ -129,7 +129,7 @@ const PrivateChatInfo: FC<OwnProps & StateProps & DispatchProps> = ({
{user && user.isVerified && <VerifiedIcon />} {user && user.isVerified && <VerifiedIcon />}
</div> </div>
)} )}
{!isSavedMessages && !noStatusOrTyping && renderStatusOrTyping()} {(status || (!isSavedMessages && !noStatusOrTyping)) && renderStatusOrTyping()}
</div> </div>
</div> </div>
); );

View File

@ -10,7 +10,7 @@ import { IS_MOBILE_SCREEN } from '../../../util/environment';
import { throttle } from '../../../util/schedulers'; import { throttle } from '../../../util/schedulers';
import searchWords from '../../../util/searchWords'; import searchWords from '../../../util/searchWords';
import { pick } from '../../../util/iteratees'; import { pick } from '../../../util/iteratees';
import { getUserFullName, getSortedUserIds } from '../../../modules/helpers'; import { getUserFullName, sortUserIds } from '../../../modules/helpers';
import useInfiniteScroll from '../../../hooks/useInfiniteScroll'; import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
import PrivateChatInfo from '../../common/PrivateChatInfo'; import PrivateChatInfo from '../../common/PrivateChatInfo';
@ -63,7 +63,7 @@ const ContactList: FC<OwnProps & StateProps & DispatchProps> = ({
return fullName && searchWords(fullName, filter); return fullName && searchWords(fullName, filter);
}) : contactIds; }) : contactIds;
return getSortedUserIds(resultIds, usersById); return sortUserIds(resultIds, usersById);
}, [filter, usersById, contactIds]); }, [filter, usersById, contactIds]);
const [viewportIds, getMore] = useInfiniteScroll(undefined, listIds, Boolean(filter)); const [viewportIds, getMore] = useInfiniteScroll(undefined, listIds, Boolean(filter));

View File

@ -4,12 +4,12 @@ import React, {
import { withGlobal } from '../../../lib/teact/teactn'; import { withGlobal } from '../../../lib/teact/teactn';
import { GlobalActions } from '../../../global/types'; import { GlobalActions } from '../../../global/types';
import { ApiUser } from '../../../api/types'; import { ApiChat, ApiUser } from '../../../api/types';
import { pick, unique } from '../../../util/iteratees'; import { pick, unique } from '../../../util/iteratees';
import { throttle } from '../../../util/schedulers'; import { throttle } from '../../../util/schedulers';
import searchWords from '../../../util/searchWords'; import searchWords from '../../../util/searchWords';
import { getSortedUserIds, getUserFullName } from '../../../modules/helpers'; import { getUserFullName, sortChatIds } from '../../../modules/helpers';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
import Picker from '../../common/Picker'; import Picker from '../../common/Picker';
@ -27,11 +27,12 @@ export type OwnProps = {
type StateProps = { type StateProps = {
currentUserId?: number; currentUserId?: number;
usersById: Record<number, ApiUser>; usersById: Record<number, ApiUser>;
chatsById: Record<number, ApiChat>;
localContactIds?: number[]; localContactIds?: number[];
searchQuery?: string; searchQuery?: string;
isSearching?: boolean; isSearching?: boolean;
localUsers?: ApiUser[]; localUserIds?: number[];
globalUsers?: ApiUser[]; globalUserIds?: number[];
}; };
type DispatchProps = Pick<GlobalActions, 'loadContactList' | 'setGlobalSearchQuery'>; type DispatchProps = Pick<GlobalActions, 'loadContactList' | 'setGlobalSearchQuery'>;
@ -46,11 +47,12 @@ const NewChatStep1: FC<OwnProps & StateProps & DispatchProps> = ({
onReset, onReset,
currentUserId, currentUserId,
usersById, usersById,
chatsById,
localContactIds, localContactIds,
searchQuery, searchQuery,
isSearching, isSearching,
localUsers, localUserIds,
globalUsers, globalUserIds,
loadContactList, loadContactList,
setGlobalSearchQuery, setGlobalSearchQuery,
}) => { }) => {
@ -67,13 +69,15 @@ const NewChatStep1: FC<OwnProps & StateProps & DispatchProps> = ({
}, [setGlobalSearchQuery]); }, [setGlobalSearchQuery]);
const displayedIds = useMemo(() => { const displayedIds = useMemo(() => {
const contactIds = localContactIds ? localContactIds.filter((id) => id !== currentUserId) : []; const contactIds = localContactIds
? sortChatIds(localContactIds.filter((id) => id !== currentUserId), chatsById)
: [];
if (!searchQuery) { if (!searchQuery) {
return contactIds; return contactIds;
} }
const foundLocalContacts = contactIds.filter((id) => { const foundContactIds = contactIds.filter((id) => {
const user = usersById[id]; const user = usersById[id];
if (!user) { if (!user) {
return false; return false;
@ -82,16 +86,19 @@ const NewChatStep1: FC<OwnProps & StateProps & DispatchProps> = ({
return fullName && searchWords(fullName, searchQuery); return fullName && searchWords(fullName, searchQuery);
}); });
return getSortedUserIds( return sortChatIds(
unique([ unique([
...foundLocalContacts, ...foundContactIds,
...(localUsers ? localUsers.map((user) => user.id) : []), ...(localUserIds || []),
...(globalUsers ? globalUsers.map((user) => user.id) : []), ...(globalUserIds || []),
]) as number[], ]),
usersById, chatsById,
false,
selectedMemberIds, selectedMemberIds,
); );
}, [localContactIds, searchQuery, localUsers, globalUsers, usersById, selectedMemberIds, currentUserId]); }, [
localContactIds, searchQuery, localUserIds, globalUserIds, usersById, chatsById, selectedMemberIds, currentUserId,
]);
const handleNextStep = useCallback(() => { const handleNextStep = useCallback(() => {
if (selectedMemberIds.length) { if (selectedMemberIds.length) {
@ -144,6 +151,7 @@ export default memo(withGlobal<OwnProps>(
(global): StateProps => { (global): StateProps => {
const { userIds: localContactIds } = global.contactList || {}; const { userIds: localContactIds } = global.contactList || {};
const { byId: usersById } = global.users; const { byId: usersById } = global.users;
const { byId: chatsById } = global.chats;
const { currentUserId } = global; const { currentUserId } = global;
const { const {
@ -152,17 +160,18 @@ export default memo(withGlobal<OwnProps>(
globalResults, globalResults,
localResults, localResults,
} = global.globalSearch; } = global.globalSearch;
const { users: globalUsers } = globalResults || {}; const { userIds: globalUserIds } = globalResults || {};
const { users: localUsers } = localResults || {}; const { userIds: localUserIds } = localResults || {};
return { return {
currentUserId, currentUserId,
usersById, usersById,
chatsById,
localContactIds, localContactIds,
searchQuery, searchQuery,
isSearching: fetchingStatus && fetchingStatus.chats, isSearching: fetchingStatus && fetchingStatus.chats,
globalUsers, globalUserIds,
localUsers, localUserIds,
}; };
}, },
(setGlobal, actions): DispatchProps => pick(actions, ['loadContactList', 'setGlobalSearchQuery']), (setGlobal, actions): DispatchProps => pick(actions, ['loadContactList', 'setGlobalSearchQuery']),

View File

@ -10,7 +10,7 @@ import { LoadMoreDirection } from '../../../types';
import { IS_MOBILE_SCREEN } from '../../../util/environment'; import { IS_MOBILE_SCREEN } from '../../../util/environment';
import searchWords from '../../../util/searchWords'; import searchWords from '../../../util/searchWords';
import { unique, pick } from '../../../util/iteratees'; import { unique, pick } from '../../../util/iteratees';
import { getUserFullName, getMessageSummaryText } from '../../../modules/helpers'; import { getUserFullName, getMessageSummaryText, sortChatIds } from '../../../modules/helpers';
import { MEMO_EMPTY_ARRAY } from '../../../util/memo'; import { MEMO_EMPTY_ARRAY } from '../../../util/memo';
import { throttle } from '../../../util/schedulers'; import { throttle } from '../../../util/schedulers';
import useLang from '../../../hooks/useLang'; import useLang from '../../../hooks/useLang';
@ -36,10 +36,10 @@ export type OwnProps = {
type StateProps = { type StateProps = {
currentUserId?: number; currentUserId?: number;
localContactIds?: number[]; localContactIds?: number[];
localChats?: ApiChat[]; localChatIds?: number[];
localUsers?: ApiUser[]; localUserIds?: number[];
globalChats?: ApiChat[]; globalChatIds?: number[];
globalUsers?: ApiUser[]; globalUserIds?: number[];
foundIds?: string[]; foundIds?: string[];
globalMessagesByChatId?: Record<number, { byId: Record<number, ApiMessage> }>; globalMessagesByChatId?: Record<number, { byId: Record<number, ApiMessage> }>;
chatsById: Record<number, ApiChat>; chatsById: Record<number, ApiChat>;
@ -55,12 +55,11 @@ type DispatchProps = Pick<GlobalActions, (
const MIN_QUERY_LENGTH_FOR_GLOBAL_SEARCH = 4; const MIN_QUERY_LENGTH_FOR_GLOBAL_SEARCH = 4;
const LESS_LIST_ITEMS_AMOUNT = 3; const LESS_LIST_ITEMS_AMOUNT = 3;
const sortSearchResults = (a: ApiChat | ApiUser, b: ApiChat | ApiUser) => Number(b.isVerified) - Number(a.isVerified);
const runThrottled = throttle((cb) => cb(), 500, true); const runThrottled = throttle((cb) => cb(), 500, true);
const ChatResults: FC<OwnProps & StateProps & DispatchProps> = ({ const ChatResults: FC<OwnProps & StateProps & DispatchProps> = ({
searchQuery, searchDate, dateSearchQuery, currentUserId, searchQuery, searchDate, dateSearchQuery, currentUserId,
localContactIds, localChats, localUsers, globalChats, globalUsers, localContactIds, localChatIds, localUserIds, globalChatIds, globalUserIds,
foundIds, globalMessagesByChatId, chatsById, usersById, fetchingStatus, lastSyncTime, foundIds, globalMessagesByChatId, chatsById, usersById, fetchingStatus, lastSyncTime,
onReset, onSearchDateSelect, openChat, addRecentlyFoundChatId, searchMessagesGlobal, setGlobalSearchChatId, onReset, onSearchDateSelect, openChat, addRecentlyFoundChatId, searchMessagesGlobal, setGlobalSearchChatId,
}) => { }) => {
@ -102,7 +101,7 @@ const ChatResults: FC<OwnProps & StateProps & DispatchProps> = ({
return MEMO_EMPTY_ARRAY; return MEMO_EMPTY_ARRAY;
} }
const foundLocalContacts = localContactIds const foundContactIds = localContactIds
? localContactIds.filter((id) => { ? localContactIds.filter((id) => {
const user = usersById[id]; const user = usersById[id];
if (!user) { if (!user) {
@ -111,26 +110,26 @@ const ChatResults: FC<OwnProps & StateProps & DispatchProps> = ({
const fullName = getUserFullName(user); const fullName = getUserFullName(user);
return (fullName && searchWords(fullName, searchQuery)) || searchWords(user.username, searchQuery); return (fullName && searchWords(fullName, searchQuery)) || searchWords(user.username, searchQuery);
}).map((id) => usersById[id]) })
: []; : [];
return unique([ return [
...(searchWords(getTranslation('SavedMessages'), searchQuery) ? [currentUserId] : []), ...(currentUserId && searchWords(getTranslation('SavedMessages'), searchQuery) ? [currentUserId] : []),
...([ ...sortChatIds(unique([
...foundLocalContacts, ...foundContactIds,
...(localChats || []), ...(localChatIds || []),
...(localUsers || []), ...(localUserIds || []),
].sort(sortSearchResults).map((chat) => chat.id)), ]), chatsById),
]) as number[]; ];
}, [searchQuery, localContactIds, localChats, localUsers, usersById, currentUserId]); }, [searchQuery, localContactIds, currentUserId, localChatIds, localUserIds, chatsById, usersById]);
const globalResults = useMemo(() => { const globalResults = useMemo(() => {
if (!searchQuery || searchQuery.length < MIN_QUERY_LENGTH_FOR_GLOBAL_SEARCH || !globalChats || !globalUsers) { if (!searchQuery || searchQuery.length < MIN_QUERY_LENGTH_FOR_GLOBAL_SEARCH || !globalChatIds || !globalUserIds) {
return MEMO_EMPTY_ARRAY; return MEMO_EMPTY_ARRAY;
} }
return unique([...globalChats, ...globalUsers].sort(sortSearchResults).map((chat) => chat.id)); return sortChatIds(unique([...globalChatIds, ...globalUserIds]), chatsById, true);
}, [globalChats, globalUsers, searchQuery]); }, [chatsById, globalChatIds, globalUserIds, searchQuery]);
const foundMessages = useMemo(() => { const foundMessages = useMemo(() => {
if ((!searchQuery && !searchDate) || !foundIds || foundIds.length === 0) { if ((!searchQuery && !searchDate) || !foundIds || foundIds.length === 0) {
@ -248,7 +247,7 @@ const ChatResults: FC<OwnProps & StateProps & DispatchProps> = ({
return ( return (
<LeftSearchResultChat <LeftSearchResultChat
chatId={id} chatId={id}
withHandle withUsername
onClick={handleChatClick} onClick={handleChatClick}
/> />
); );
@ -283,21 +282,18 @@ export default memo(withGlobal<OwnProps>(
const { const {
fetchingStatus, globalResults, localResults, resultsByType, fetchingStatus, globalResults, localResults, resultsByType,
} = global.globalSearch; } = global.globalSearch;
const { const { chatIds: globalChatIds, userIds: globalUserIds } = globalResults || {};
chats: globalChats, const { chatIds: localChatIds, userIds: localUserIds } = localResults || {};
users: globalUsers,
} = globalResults || {};
const { chats: localChats, users: localUsers } = localResults || {};
const { byChatId: globalMessagesByChatId } = messages; const { byChatId: globalMessagesByChatId } = messages;
const { foundIds } = (resultsByType && resultsByType.text) || {}; const { foundIds } = (resultsByType && resultsByType.text) || {};
return { return {
currentUserId, currentUserId,
localContactIds, localContactIds,
localChats, localChatIds,
localUsers, localUserIds,
globalChats, globalChatIds,
globalUsers, globalUserIds,
foundIds, foundIds,
globalMessagesByChatId, globalMessagesByChatId,
chatsById, chatsById,

View File

@ -15,7 +15,7 @@ import ListItem from '../../ui/ListItem';
type OwnProps = { type OwnProps = {
chatId: number; chatId: number;
withHandle?: boolean; withUsername?: boolean;
onClick: (id: number) => void; onClick: (id: number) => void;
}; };
@ -30,7 +30,7 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
chat, chat,
privateChatUser, privateChatUser,
isPinned, isPinned,
withHandle, withUsername,
onClick, onClick,
}) => { }) => {
const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag(); const [isDeleteModalOpen, openDeleteModal, closeDeleteModal] = useFlag();
@ -53,9 +53,9 @@ const LeftSearchResultChat: FC<OwnProps & StateProps> = ({
contextActions={contextActions} contextActions={contextActions}
> >
{isChatPrivate(chatId) ? ( {isChatPrivate(chatId) ? (
<PrivateChatInfo userId={chatId} withHandle={withHandle} avatarSize="large" /> <PrivateChatInfo userId={chatId} withUsername={withUsername} avatarSize="large" />
) : ( ) : (
<GroupChatInfo chatId={chatId} withHandle={withHandle} avatarSize="large" /> <GroupChatInfo chatId={chatId} withUsername={withUsername} avatarSize="large" />
)} )}
<DeleteChatModal <DeleteChatModal
isOpen={isDeleteModalOpen} isOpen={isDeleteModalOpen}

View File

@ -4,11 +4,12 @@ import React, {
import { withGlobal } from '../../lib/teact/teactn'; import { withGlobal } from '../../lib/teact/teactn';
import { GlobalActions } from '../../global/types'; import { GlobalActions } from '../../global/types';
import { ApiChat, ApiUser } from '../../api/types'; import { ApiChat, MAIN_THREAD_ID } from '../../api/types';
import { IS_MOBILE_SCREEN } from '../../util/environment'; import { IS_MOBILE_SCREEN } from '../../util/environment';
import { getChatTitle, prepareChatList, isChatPrivate } from '../../modules/helpers'; import {
import { selectUser } from '../../modules/selectors'; getCanPostInChat, getChatTitle, isChatPrivate, sortChatIds,
} from '../../modules/helpers';
import searchWords from '../../util/searchWords'; import searchWords from '../../util/searchWords';
import { pick } from '../../util/iteratees'; import { pick } from '../../util/iteratees';
import useInfiniteScroll from '../../hooks/useInfiniteScroll'; import useInfiniteScroll from '../../hooks/useInfiniteScroll';
@ -31,9 +32,10 @@ export type OwnProps = {
type StateProps = { type StateProps = {
chatsById: Record<number, ApiChat>; chatsById: Record<number, ApiChat>;
listIds?: number[]; activeListIds?: number[];
archivedListIds?: number[];
orderedPinnedIds?: number[]; orderedPinnedIds?: number[];
currentUser?: ApiUser; currentUserId?: number;
}; };
type DispatchProps = Pick<GlobalActions, 'setForwardChatId' | 'exitForwardMode' | 'loadMoreChats'>; type DispatchProps = Pick<GlobalActions, 'setForwardChatId' | 'exitForwardMode' | 'loadMoreChats'>;
@ -44,9 +46,9 @@ const MODAL_HIDE_DELAY_MS = 300;
const ForwardPicker: FC<OwnProps & StateProps & DispatchProps> = ({ const ForwardPicker: FC<OwnProps & StateProps & DispatchProps> = ({
chatsById, chatsById,
listIds, activeListIds,
orderedPinnedIds, archivedListIds,
currentUser, currentUserId,
isOpen, isOpen,
setForwardChatId, setForwardChatId,
exitForwardMode, exitForwardMode,
@ -78,30 +80,31 @@ const ForwardPicker: FC<OwnProps & StateProps & DispatchProps> = ({
} }
}, [isOpen]); }, [isOpen]);
const chats = useMemo(() => {
const chatArrays = listIds ? prepareChatList(chatsById, listIds, orderedPinnedIds) : undefined;
if (!chatArrays) {
return undefined;
}
const chatWithSelf = currentUser ? chatsById[currentUser.id] : undefined;
return [
...(chatWithSelf ? [chatWithSelf] : []),
...chatArrays.pinnedChats.filter(({ id }) => !chatWithSelf || id !== chatWithSelf.id),
...chatArrays.otherChats.filter(({ id }) => !chatWithSelf || id !== chatWithSelf.id),
];
}, [chatsById, listIds, orderedPinnedIds, currentUser]);
const chatIds = useMemo(() => { const chatIds = useMemo(() => {
if (!chats) { const listIds = [
return undefined; ...activeListIds || [],
} ...archivedListIds || [],
];
return chats return sortChatIds([
.filter((chat) => (!filter || searchWords(getChatTitle(chat, currentUser), filter))) ...listIds.filter((id) => {
.map(({ id }) => id); const chat = chatsById[id];
}, [chats, filter, currentUser]); if (!chat) {
return true;
}
if (!getCanPostInChat(chat, MAIN_THREAD_ID)) {
return false;
}
if (!filter) {
return true;
}
return searchWords(getChatTitle(chatsById[id], undefined, id === currentUserId), filter);
}),
], chatsById, undefined, currentUserId ? [currentUserId] : undefined);
}, [activeListIds, archivedListIds, chatsById, currentUserId, filter]);
const [viewportIds, getMore] = useInfiniteScroll(loadMoreChats, chatIds, Boolean(filter)); const [viewportIds, getMore] = useInfiniteScroll(loadMoreChats, chatIds, Boolean(filter));
@ -152,7 +155,7 @@ const ForwardPicker: FC<OwnProps & StateProps & DispatchProps> = ({
onClick={() => setForwardChatId({ id })} onClick={() => setForwardChatId({ id })}
> >
{isChatPrivate(id) ? ( {isChatPrivate(id) ? (
<PrivateChatInfo userId={id} /> <PrivateChatInfo status={id === currentUserId ? lang('SavedMessagesInfo') : undefined} userId={id} />
) : ( ) : (
<GroupChatInfo chatId={id} /> <GroupChatInfo chatId={id} />
)} )}
@ -174,16 +177,15 @@ export default memo(withGlobal<OwnProps>(
chats: { chats: {
byId: chatsById, byId: chatsById,
listIds, listIds,
orderedPinnedIds,
}, },
currentUserId, currentUserId,
} = global; } = global;
return { return {
chatsById, chatsById,
listIds: listIds.active, activeListIds: listIds.active,
orderedPinnedIds: orderedPinnedIds.active, archivedListIds: listIds.archived,
currentUser: currentUserId ? selectUser(global, currentUserId) : undefined, currentUserId,
}; };
}, },
(setGlobal, actions): DispatchProps => pick(actions, ['setForwardChatId', 'exitForwardMode', 'loadMoreChats']), (setGlobal, actions): DispatchProps => pick(actions, ['setForwardChatId', 'exitForwardMode', 'loadMoreChats']),

View File

@ -152,7 +152,7 @@ const MentionMenu: FC<OwnProps> = ({
<PrivateChatInfo <PrivateChatInfo
userId={userId} userId={userId}
avatarSize="small" avatarSize="small"
withHandle withUsername
/> />
</ListItem> </ListItem>
))} ))}

View File

@ -4,7 +4,7 @@ import { ApiChatMember, ApiMessage, ApiUser } from '../../../api/types';
import { ProfileTabType, SharedMediaType } from '../../../types'; import { ProfileTabType, SharedMediaType } from '../../../types';
import { MESSAGE_SEARCH_SLICE, SHARED_MEDIA_SLICE } from '../../../config'; import { MESSAGE_SEARCH_SLICE, SHARED_MEDIA_SLICE } from '../../../config';
import { getMessageContentIds, getSortedUserIds } from '../../../modules/helpers'; import { getMessageContentIds, sortUserIds } from '../../../modules/helpers';
import useOnChange from '../../../hooks/useOnChange'; import useOnChange from '../../../hooks/useOnChange';
import useInfiniteScroll from '../../../hooks/useInfiniteScroll'; import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
@ -27,7 +27,7 @@ export default function useProfileViewportIds(
return undefined; return undefined;
} }
return getSortedUserIds(groupChatMembers.map(({ userId }) => userId), usersById); return sortUserIds(groupChatMembers.map(({ userId }) => userId), usersById);
}, [groupChatMembers, usersById]); }, [groupChatMembers, usersById]);
const [mediaViewportIds, getMoreMedia, noProfileInfoForMedia] = useInfiniteScrollForSharedMedia( const [mediaViewportIds, getMoreMedia, noProfileInfoForMedia] = useInfiniteScrollForSharedMedia(

View File

@ -6,7 +6,7 @@ import { withGlobal } from '../../../lib/teact/teactn';
import { ApiChatMember, ApiUser } from '../../../api/types'; import { ApiChatMember, ApiUser } from '../../../api/types';
import { GlobalActions } from '../../../global/types'; import { GlobalActions } from '../../../global/types';
import { selectChat } from '../../../modules/selectors'; import { selectChat } from '../../../modules/selectors';
import { getSortedUserIds, isChatChannel } from '../../../modules/helpers'; import { sortUserIds, isChatChannel } from '../../../modules/helpers';
import { pick } from '../../../util/iteratees'; import { pick } from '../../../util/iteratees';
import PrivateChatInfo from '../../common/PrivateChatInfo'; import PrivateChatInfo from '../../common/PrivateChatInfo';
@ -36,7 +36,7 @@ const ManageGroupMembers: FC<OwnProps & StateProps & DispatchProps> = ({
return undefined; return undefined;
} }
return getSortedUserIds(members.map(({ userId }) => userId), usersById); return sortUserIds(members.map(({ userId }) => userId), usersById);
}, [members, usersById]); }, [members, usersById]);
const handleMemberClick = useCallback((id: number) => { const handleMemberClick = useCallback((id: number) => {

View File

@ -7,7 +7,7 @@ import { ApiChatMember, ApiUser } from '../../../api/types';
import { ManagementScreens } from '../../../types'; import { ManagementScreens } from '../../../types';
import { selectChat } from '../../../modules/selectors'; import { selectChat } from '../../../modules/selectors';
import { getSortedUserIds, isChatChannel } from '../../../modules/helpers'; import { sortUserIds, isChatChannel } from '../../../modules/helpers';
import PrivateChatInfo from '../../common/PrivateChatInfo'; import PrivateChatInfo from '../../common/PrivateChatInfo';
import ListItem from '../../ui/ListItem'; import ListItem from '../../ui/ListItem';
@ -37,7 +37,7 @@ const ManageGroupUserPermissionsCreate: FC<OwnProps & StateProps> = ({
return undefined; return undefined;
} }
return getSortedUserIds(members.filter((member) => !member.isOwner).map(({ userId }) => userId), usersById); return sortUserIds(members.filter((member) => !member.isOwner).map(({ userId }) => userId), usersById);
}, [members, usersById]); }, [members, usersById]);
const handleExceptionMemberClick = useCallback((memberId: number) => { const handleExceptionMemberClick = useCallback((memberId: number) => {

View File

@ -180,6 +180,7 @@
font-size: 0.875rem; font-size: 0.875rem;
line-height: 1.25rem; line-height: 1.25rem;
color: var(--color-text-secondary); color: var(--color-text-secondary);
display: inline-block;
&.online { &.online {
color: var(--color-primary); color: var(--color-primary);

View File

@ -224,12 +224,12 @@ export type GlobalState = {
messages?: boolean; messages?: boolean;
}; };
localResults?: { localResults?: {
chats?: ApiChat[]; chatIds?: number[];
users?: ApiUser[]; userIds?: number[];
}; };
globalResults?: { globalResults?: {
chats?: ApiChat[]; chatIds?: number[];
users?: ApiUser[]; userIds?: number[];
}; };
resultsByType?: Partial<Record<ApiGlobalMessageSearchType, { resultsByType?: Partial<Record<ApiGlobalMessageSearchType, {
totalCount?: number; totalCount?: number;

View File

@ -65,7 +65,7 @@ addReducer('searchMessagesGlobal', (global, actions, payload) => {
}); });
async function searchChats(query: string) { async function searchChats(query: string) {
const result = await callApi('searchChats', { query, limit: GLOBAL_SEARCH_SLICE }); const result = await callApi('searchChats', { query });
let global = getGlobal(); let global = getGlobal();
const currentSearchQuery = selectCurrentGlobalSearchQuery(global); const currentSearchQuery = selectCurrentGlobalSearchQuery(global);
@ -89,13 +89,13 @@ async function searchChats(query: string) {
global = updateGlobalSearchFetchingStatus(global, { chats: false }); global = updateGlobalSearchFetchingStatus(global, { chats: false });
global = updateGlobalSearch(global, { global = updateGlobalSearch(global, {
localResults: { localResults: {
chats: localChats, chatIds: localChats.map(({ id }) => id),
users: localUsers, userIds: localUsers.map(({ id }) => id),
}, },
globalResults: { globalResults: {
...global.globalSearch.globalResults, ...global.globalSearch.globalResults,
chats: globalChats, chatIds: globalUsers.map(({ id }) => id),
users: globalUsers, userIds: globalChats.map(({ id }) => id),
}, },
}); });

View File

@ -58,8 +58,9 @@ export function getPrivateChatUserId(chat: ApiChat) {
return chat.id; return chat.id;
} }
export function getChatTitle(chat: ApiChat, user?: ApiUser) { // TODO Get rid of `user`
if (user && chat.id === user.id && user.isSelf) { export function getChatTitle(chat: ApiChat, user?: ApiUser, isSelf = false) {
if (isSelf || (user && chat.id === user.id && user.isSelf)) {
return getTranslation('SavedMessages'); return getTranslation('SavedMessages');
} }
return chat.title || getTranslation('HiddenName'); return chat.title || getTranslation('HiddenName');
@ -432,3 +433,36 @@ export function getMessageSenderName(chatId: number, sender?: ApiUser) {
return getUserFirstOrLastName(sender); return getUserFirstOrLastName(sender);
} }
export function sortChatIds(
chatIds: number[],
chatsById: Record<number, ApiChat>,
shouldPrioritizeVerified = false,
priorityIds?: number[],
) {
return orderBy(chatIds, (id) => {
const chat = chatsById[id];
if (!chat) {
return 0;
}
let priority = 0;
if (chat.lastMessage) {
priority += chat.lastMessage.date;
}
if (shouldPrioritizeVerified && chat.isVerified) {
priority += 3e9; // ~100 years in seconds
}
if (priorityIds && priorityIds.includes(id)) {
// Assuming that last message date can't be less than now,
// this should place prioritized on top of the list.
// Then we subtract index of `id` in `priorityIds` to preserve selected order
priority += Date.now() + (priorityIds.length - priorityIds.indexOf(id));
}
return priority;
}, 'desc');
}

View File

@ -180,7 +180,7 @@ export function isUserBot(user: ApiUser) {
return user.type === 'userTypeBot'; return user.type === 'userTypeBot';
} }
export function getSortedUserIds( export function sortUserIds(
userIds: number[], userIds: number[],
usersById: Record<number, ApiUser>, usersById: Record<number, ApiUser>,
priorityIds?: number[], priorityIds?: number[],
@ -189,13 +189,10 @@ export function getSortedUserIds(
const now = Date.now() / 1000; const now = Date.now() / 1000;
if (priorityIds && priorityIds.includes(id)) { if (priorityIds && priorityIds.includes(id)) {
/* // Assuming that online status expiration date can't be as far as two days from now,
** Assuming that online status expiration date can't be as far as two days from now, // this should place prioritized on top of the list.
** this should place priorityIds on top of the list. // Then we subtract index of `id` in `priorityIds` to preserve selected order
** return now + (48 * 60 * 60) - (priorityIds.length - priorityIds.indexOf(id));
** We then subtract index of `id` in `priorityIds` to preserve selected order
*/
return now + (48 * 60 * 60) - priorityIds.indexOf(id);
} }
const user = usersById[id]; const user = usersById[id];