[Perf] Forward Modal: Fix missing contacts; Optimize text filtering in large lists
This commit is contained in:
parent
dac6c3a101
commit
f6cd826e7f
@ -6,7 +6,7 @@ import { withGlobal } from '../../lib/teact/teactn';
|
|||||||
import { ApiCountryCode } from '../../api/types';
|
import { ApiCountryCode } from '../../api/types';
|
||||||
|
|
||||||
import { ANIMATION_END_DELAY } from '../../config';
|
import { ANIMATION_END_DELAY } from '../../config';
|
||||||
import searchWords from '../../util/searchWords';
|
import { prepareSearchWordsForNeedle } from '../../util/searchWords';
|
||||||
import buildClassName from '../../util/buildClassName';
|
import buildClassName from '../../util/buildClassName';
|
||||||
import renderText from '../common/helpers/renderText';
|
import renderText from '../common/helpers/renderText';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
@ -159,11 +159,15 @@ const CountryCodeInput: FC<OwnProps & StateProps> = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
function getFilteredList(countryList: ApiCountryCode[], filter = ''): ApiCountryCode[] {
|
function getFilteredList(countryList: ApiCountryCode[], filter = ''): ApiCountryCode[] {
|
||||||
const filtered = filter.length
|
if (!filter.length) {
|
||||||
? countryList.filter((country) => (
|
return countryList;
|
||||||
searchWords(country.defaultName, filter) || (country.name && searchWords(country.name, filter))
|
}
|
||||||
)) : countryList;
|
|
||||||
return filtered;
|
const searchWords = prepareSearchWordsForNeedle(filter);
|
||||||
|
|
||||||
|
return countryList.filter((country) => (
|
||||||
|
searchWords(country.defaultName) || (country.name && searchWords(country.name))
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
|
|||||||
@ -8,9 +8,8 @@ import { ApiUser, ApiUserStatus } from '../../../api/types';
|
|||||||
|
|
||||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||||
import { throttle } from '../../../util/schedulers';
|
import { throttle } from '../../../util/schedulers';
|
||||||
import searchWords from '../../../util/searchWords';
|
|
||||||
import { pick } from '../../../util/iteratees';
|
import { pick } from '../../../util/iteratees';
|
||||||
import { getUserFullName, sortUserIds } from '../../../modules/helpers';
|
import { filterUsersByName, sortUserIds } from '../../../modules/helpers';
|
||||||
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
import useInfiniteScroll from '../../../hooks/useInfiniteScroll';
|
||||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
|
|
||||||
@ -66,16 +65,9 @@ const ContactList: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const resultIds = filter ? contactIds.filter((id) => {
|
const filteredIds = filterUsersByName(contactIds, usersById, filter);
|
||||||
const user = usersById[id];
|
|
||||||
if (!user) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const fullName = getUserFullName(user);
|
|
||||||
return fullName && searchWords(fullName, filter);
|
|
||||||
}) : contactIds;
|
|
||||||
|
|
||||||
return sortUserIds(resultIds, usersById, userStatusesById, undefined, serverTimeOffset);
|
return sortUserIds(filteredIds, usersById, userStatusesById, undefined, serverTimeOffset);
|
||||||
}, [contactIds, filter, usersById, userStatusesById, serverTimeOffset]);
|
}, [contactIds, filter, usersById, userStatusesById, serverTimeOffset]);
|
||||||
|
|
||||||
const [viewportIds, getMore] = useInfiniteScroll(undefined, listIds, Boolean(filter));
|
const [viewportIds, getMore] = useInfiniteScroll(undefined, listIds, Boolean(filter));
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
import React, {
|
import React, {
|
||||||
FC, useCallback, useEffect, useMemo, memo,
|
FC, useCallback, useEffect, useMemo, memo,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { withGlobal } from '../../../lib/teact/teactn';
|
import { getGlobal, withGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
import { GlobalActions } from '../../../global/types';
|
import { GlobalActions } from '../../../global/types';
|
||||||
import { ApiChat, ApiUser } from '../../../api/types';
|
import { ApiChat } 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 { filterUsersByName, isUserBot, sortChatIds } from '../../../modules/helpers';
|
||||||
import { getUserFullName, isUserBot, sortChatIds } from '../../../modules/helpers';
|
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
import useHistoryBack from '../../../hooks/useHistoryBack';
|
import useHistoryBack from '../../../hooks/useHistoryBack';
|
||||||
|
|
||||||
@ -27,8 +26,6 @@ export type OwnProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
currentUserId?: string;
|
|
||||||
usersById: Record<string, ApiUser>;
|
|
||||||
chatsById: Record<string, ApiChat>;
|
chatsById: Record<string, ApiChat>;
|
||||||
localContactIds?: string[];
|
localContactIds?: string[];
|
||||||
searchQuery?: string;
|
searchQuery?: string;
|
||||||
@ -48,8 +45,6 @@ const NewChatStep1: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
onSelectedMemberIdsChange,
|
onSelectedMemberIdsChange,
|
||||||
onNextStep,
|
onNextStep,
|
||||||
onReset,
|
onReset,
|
||||||
currentUserId,
|
|
||||||
usersById,
|
|
||||||
chatsById,
|
chatsById,
|
||||||
localContactIds,
|
localContactIds,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
@ -76,22 +71,9 @@ const NewChatStep1: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
}, [setGlobalSearchQuery]);
|
}, [setGlobalSearchQuery]);
|
||||||
|
|
||||||
const displayedIds = useMemo(() => {
|
const displayedIds = useMemo(() => {
|
||||||
const contactIds = localContactIds
|
// No need for expensive global updates on users, so we avoid them
|
||||||
? sortChatIds(localContactIds.filter((id) => id !== currentUserId), chatsById)
|
const usersById = getGlobal().users.byId;
|
||||||
: [];
|
const foundContactIds = localContactIds ? filterUsersByName(localContactIds, usersById, searchQuery) : [];
|
||||||
|
|
||||||
if (!searchQuery) {
|
|
||||||
return contactIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
const foundContactIds = contactIds.filter((id) => {
|
|
||||||
const user = usersById[id];
|
|
||||||
if (!user) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const fullName = getUserFullName(user);
|
|
||||||
return fullName && searchWords(fullName, searchQuery);
|
|
||||||
});
|
|
||||||
|
|
||||||
return sortChatIds(
|
return sortChatIds(
|
||||||
unique([
|
unique([
|
||||||
@ -100,17 +82,17 @@ const NewChatStep1: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
...(globalUserIds || []),
|
...(globalUserIds || []),
|
||||||
]).filter((contactId) => {
|
]).filter((contactId) => {
|
||||||
const user = usersById[contactId];
|
const user = usersById[contactId];
|
||||||
|
if (!user) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return !user || !isUserBot(user) || user.canBeInvitedToGroup;
|
return user.canBeInvitedToGroup && !user.isSelf && !isUserBot(user);
|
||||||
}),
|
}),
|
||||||
chatsById,
|
chatsById,
|
||||||
false,
|
false,
|
||||||
selectedMemberIds,
|
selectedMemberIds,
|
||||||
);
|
);
|
||||||
}, [
|
}, [localContactIds, chatsById, searchQuery, localUserIds, globalUserIds, selectedMemberIds]);
|
||||||
localContactIds, chatsById, searchQuery, localUserIds, globalUserIds, selectedMemberIds,
|
|
||||||
currentUserId, usersById,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const handleNextStep = useCallback(() => {
|
const handleNextStep = useCallback(() => {
|
||||||
if (selectedMemberIds.length || isChannel) {
|
if (selectedMemberIds.length || isChannel) {
|
||||||
@ -160,9 +142,7 @@ const NewChatStep1: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
export default memo(withGlobal<OwnProps>(
|
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: chatsById } = global.chats;
|
const { byId: chatsById } = global.chats;
|
||||||
const { currentUserId } = global;
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
query: searchQuery,
|
query: searchQuery,
|
||||||
@ -174,8 +154,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
const { userIds: localUserIds } = localResults || {};
|
const { userIds: localUserIds } = localResults || {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentUserId,
|
|
||||||
usersById,
|
|
||||||
chatsById,
|
chatsById,
|
||||||
localContactIds,
|
localContactIds,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
|
|||||||
@ -1,16 +1,15 @@
|
|||||||
import React, {
|
import React, {
|
||||||
FC, memo, useCallback, useMemo, useState,
|
FC, memo, useCallback, useMemo, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { withGlobal } from '../../../lib/teact/teactn';
|
import { getGlobal, withGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
import { ApiUser, ApiChat, ApiMessage } from '../../../api/types';
|
import { ApiChat, ApiMessage } from '../../../api/types';
|
||||||
import { GlobalActions } from '../../../global/types';
|
import { GlobalActions } from '../../../global/types';
|
||||||
import { LoadMoreDirection } from '../../../types';
|
import { LoadMoreDirection } from '../../../types';
|
||||||
|
|
||||||
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
import { IS_SINGLE_COLUMN_LAYOUT } from '../../../util/environment';
|
||||||
import searchWords from '../../../util/searchWords';
|
|
||||||
import { unique, pick } from '../../../util/iteratees';
|
import { unique, pick } from '../../../util/iteratees';
|
||||||
import { getUserFullName, getMessageSummaryText, sortChatIds } from '../../../modules/helpers';
|
import { getMessageSummaryText, sortChatIds, filterUsersByName } 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';
|
||||||
@ -42,7 +41,6 @@ type StateProps = {
|
|||||||
foundIds?: string[];
|
foundIds?: string[];
|
||||||
globalMessagesByChatId?: Record<string, { byId: Record<number, ApiMessage> }>;
|
globalMessagesByChatId?: Record<string, { byId: Record<number, ApiMessage> }>;
|
||||||
chatsById: Record<string, ApiChat>;
|
chatsById: Record<string, ApiChat>;
|
||||||
usersById: Record<string, ApiUser>;
|
|
||||||
fetchingStatus?: { chats?: boolean; messages?: boolean };
|
fetchingStatus?: { chats?: boolean; messages?: boolean };
|
||||||
lastSyncTime?: number;
|
lastSyncTime?: number;
|
||||||
};
|
};
|
||||||
@ -52,14 +50,14 @@ 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 = 5;
|
||||||
|
|
||||||
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, localChatIds, localUserIds, globalChatIds, globalUserIds,
|
localContactIds, localChatIds, localUserIds, globalChatIds, globalUserIds,
|
||||||
foundIds, globalMessagesByChatId, chatsById, usersById, fetchingStatus, lastSyncTime,
|
foundIds, globalMessagesByChatId, chatsById, fetchingStatus, lastSyncTime,
|
||||||
onReset, onSearchDateSelect, openChat, addRecentlyFoundChatId, searchMessagesGlobal, setGlobalSearchChatId,
|
onReset, onSearchDateSelect, openChat, addRecentlyFoundChatId, searchMessagesGlobal, setGlobalSearchChatId,
|
||||||
}) => {
|
}) => {
|
||||||
const lang = useLang();
|
const lang = useLang();
|
||||||
@ -102,37 +100,33 @@ const ChatResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
return MEMO_EMPTY_ARRAY;
|
return MEMO_EMPTY_ARRAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
const foundContactIds = localContactIds
|
const contactIdsWithMe = [
|
||||||
? localContactIds.filter((id) => {
|
...(currentUserId ? [currentUserId] : []),
|
||||||
const user = usersById[id];
|
...(localContactIds || []),
|
||||||
if (!user) {
|
];
|
||||||
return false;
|
// No need for expensive global updates on users, so we avoid them
|
||||||
}
|
const usersById = getGlobal().users.byId;
|
||||||
|
const foundContactIds = filterUsersByName(contactIdsWithMe, usersById, searchQuery);
|
||||||
const fullName = getUserFullName(user);
|
|
||||||
return (fullName && searchWords(fullName, searchQuery)) || searchWords(user.username, searchQuery);
|
|
||||||
})
|
|
||||||
: [];
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
...(currentUserId && searchWords(lang('SavedMessages'), searchQuery) ? [currentUserId] : []),
|
|
||||||
...sortChatIds(unique([
|
...sortChatIds(unique([
|
||||||
...foundContactIds,
|
...(foundContactIds || []),
|
||||||
...(localChatIds || []),
|
...(localChatIds || []),
|
||||||
...(localUserIds || []),
|
...(localUserIds || []),
|
||||||
]), chatsById),
|
]), chatsById, undefined, currentUserId ? [currentUserId] : undefined),
|
||||||
];
|
];
|
||||||
}, [
|
}, [searchQuery, localContactIds, currentUserId, localChatIds, localUserIds, chatsById]);
|
||||||
searchQuery, localContactIds, currentUserId, lang, localChatIds, localUserIds, chatsById, usersById,
|
|
||||||
]);
|
|
||||||
|
|
||||||
const globalResults = useMemo(() => {
|
const globalResults = useMemo(() => {
|
||||||
if (!searchQuery || searchQuery.length < MIN_QUERY_LENGTH_FOR_GLOBAL_SEARCH || !globalChatIds || !globalUserIds) {
|
if (!searchQuery || searchQuery.length < MIN_QUERY_LENGTH_FOR_GLOBAL_SEARCH || !globalChatIds || !globalUserIds) {
|
||||||
return MEMO_EMPTY_ARRAY;
|
return MEMO_EMPTY_ARRAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
return sortChatIds(unique([...globalChatIds, ...globalUserIds]),
|
return sortChatIds(
|
||||||
chatsById, true);
|
unique([...globalChatIds, ...globalUserIds]),
|
||||||
|
chatsById,
|
||||||
|
true,
|
||||||
|
);
|
||||||
}, [chatsById, globalChatIds, globalUserIds, searchQuery]);
|
}, [chatsById, globalChatIds, globalUserIds, searchQuery]);
|
||||||
|
|
||||||
const foundMessages = useMemo(() => {
|
const foundMessages = useMemo(() => {
|
||||||
@ -278,14 +272,12 @@ const ChatResults: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
const { byId: chatsById } = global.chats;
|
const { byId: chatsById } = global.chats;
|
||||||
const { byId: usersById } = global.users;
|
|
||||||
|
|
||||||
const { userIds: localContactIds } = global.contactList || {};
|
const { userIds: localContactIds } = global.contactList || {};
|
||||||
|
|
||||||
if (!localContactIds) {
|
if (!localContactIds) {
|
||||||
return {
|
return {
|
||||||
chatsById,
|
chatsById,
|
||||||
usersById,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -310,7 +302,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
foundIds,
|
foundIds,
|
||||||
globalMessagesByChatId,
|
globalMessagesByChatId,
|
||||||
chatsById,
|
chatsById,
|
||||||
usersById,
|
|
||||||
fetchingStatus,
|
fetchingStatus,
|
||||||
lastSyncTime,
|
lastSyncTime,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,8 +6,7 @@ import { withGlobal } from '../../../lib/teact/teactn';
|
|||||||
import { GlobalActions } from '../../../global/types';
|
import { GlobalActions } from '../../../global/types';
|
||||||
import { ApiUser } from '../../../api/types';
|
import { ApiUser } from '../../../api/types';
|
||||||
|
|
||||||
import { getUserFullName } from '../../../modules/helpers';
|
import { filterUsersByName, getUserFullName } from '../../../modules/helpers';
|
||||||
import searchWords from '../../../util/searchWords';
|
|
||||||
import { pick, unique } from '../../../util/iteratees';
|
import { pick, unique } from '../../../util/iteratees';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
|
|
||||||
@ -49,23 +48,15 @@ const BlockUserModal: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
setUserSearchQuery({ query: filter });
|
setUserSearchQuery({ query: filter });
|
||||||
}, [filter, setUserSearchQuery]);
|
}, [filter, setUserSearchQuery]);
|
||||||
|
|
||||||
const filteredContactsId = useMemo(() => {
|
const filteredContactIds = useMemo(() => {
|
||||||
const availableContactsId = (contactIds || []).concat(localContactIds || []).filter((contactId) => {
|
const availableContactIds = unique([
|
||||||
return !blockedIds.includes(contactId) && contactId !== currentUserId;
|
...(contactIds || []),
|
||||||
});
|
...(localContactIds || []),
|
||||||
|
].filter((contactId) => {
|
||||||
|
return contactId !== currentUserId && !blockedIds.includes(contactId);
|
||||||
|
}));
|
||||||
|
|
||||||
return unique(availableContactsId).reduce<string[]>((acc, contactId) => {
|
return filterUsersByName(availableContactIds, usersById, filter)
|
||||||
if (
|
|
||||||
!filter
|
|
||||||
|| !usersById[contactId]
|
|
||||||
|| searchWords(getUserFullName(usersById[contactId]) || '', filter)
|
|
||||||
|| usersById[contactId]?.username.toLowerCase().includes(filter)
|
|
||||||
) {
|
|
||||||
acc.push(contactId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
|
||||||
}, [])
|
|
||||||
.sort((firstId, secondId) => {
|
.sort((firstId, secondId) => {
|
||||||
const firstName = getUserFullName(usersById[firstId]) || '';
|
const firstName = getUserFullName(usersById[firstId]) || '';
|
||||||
const secondName = getUserFullName(usersById[secondId]) || '';
|
const secondName = getUserFullName(usersById[secondId]) || '';
|
||||||
@ -86,7 +77,7 @@ const BlockUserModal: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
return (
|
return (
|
||||||
<ChatOrUserPicker
|
<ChatOrUserPicker
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
chatOrUserIds={filteredContactsId}
|
chatOrUserIds={filteredContactIds}
|
||||||
filterRef={filterRef}
|
filterRef={filterRef}
|
||||||
filterPlaceholder={lang('BlockedUsers.BlockUser')}
|
filterPlaceholder={lang('BlockedUsers.BlockUser')}
|
||||||
filter={filter}
|
filter={filter}
|
||||||
|
|||||||
@ -1,13 +1,17 @@
|
|||||||
import React, {
|
import React, {
|
||||||
FC, useMemo, useState, memo, useRef, useCallback,
|
FC, useMemo, useState, memo, useRef, useCallback,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { withGlobal } from '../../lib/teact/teactn';
|
import { getGlobal, withGlobal } from '../../lib/teact/teactn';
|
||||||
|
|
||||||
import { GlobalActions } from '../../global/types';
|
import { GlobalActions } from '../../global/types';
|
||||||
import { ApiChat, MAIN_THREAD_ID } from '../../api/types';
|
import { ApiChat, MAIN_THREAD_ID } from '../../api/types';
|
||||||
|
|
||||||
import { getCanPostInChat, getChatTitle, sortChatIds } from '../../modules/helpers';
|
import {
|
||||||
import searchWords from '../../util/searchWords';
|
filterChatsByName,
|
||||||
|
filterUsersByName,
|
||||||
|
getCanPostInChat,
|
||||||
|
sortChatIds,
|
||||||
|
} from '../../modules/helpers';
|
||||||
import { pick, unique } from '../../util/iteratees';
|
import { pick, unique } from '../../util/iteratees';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
import useCurrentOrPrev from '../../hooks/useCurrentOrPrev';
|
||||||
@ -20,10 +24,10 @@ export type OwnProps = {
|
|||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
chatsById: Record<string, ApiChat>;
|
chatsById: Record<string, ApiChat>;
|
||||||
pinnedIds?: string[];
|
|
||||||
activeListIds?: string[];
|
activeListIds?: string[];
|
||||||
archivedListIds?: string[];
|
archivedListIds?: string[];
|
||||||
orderedPinnedIds?: string[];
|
pinnedIds?: string[];
|
||||||
|
contactIds?: string[];
|
||||||
currentUserId?: string;
|
currentUserId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -31,9 +35,10 @@ type DispatchProps = Pick<GlobalActions, 'setForwardChatId' | 'exitForwardMode'
|
|||||||
|
|
||||||
const ForwardPicker: FC<OwnProps & StateProps & DispatchProps> = ({
|
const ForwardPicker: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||||
chatsById,
|
chatsById,
|
||||||
pinnedIds,
|
|
||||||
activeListIds,
|
activeListIds,
|
||||||
archivedListIds,
|
archivedListIds,
|
||||||
|
pinnedIds,
|
||||||
|
contactIds,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
isOpen,
|
isOpen,
|
||||||
setForwardChatId,
|
setForwardChatId,
|
||||||
@ -45,47 +50,45 @@ const ForwardPicker: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const filterRef = useRef<HTMLInputElement>(null);
|
const filterRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const chatIds = useMemo(() => {
|
const chatAndContactIds = useMemo(() => {
|
||||||
if (!isOpen) {
|
if (!isOpen) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const listIds = [...(activeListIds || []), ...(archivedListIds || [])];
|
|
||||||
|
|
||||||
let priorityIds = pinnedIds || [];
|
let priorityIds = pinnedIds || [];
|
||||||
if (currentUserId) {
|
if (currentUserId) {
|
||||||
priorityIds = unique([currentUserId, ...priorityIds]);
|
priorityIds = unique([currentUserId, ...priorityIds]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return sortChatIds(listIds.filter((id) => {
|
const chatIds = [
|
||||||
|
...(activeListIds || []),
|
||||||
|
...(archivedListIds || []),
|
||||||
|
].filter((id) => {
|
||||||
const chat = chatsById[id];
|
const chat = chatsById[id];
|
||||||
if (!chat) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!getCanPostInChat(chat, MAIN_THREAD_ID)) {
|
return chat && getCanPostInChat(chat, MAIN_THREAD_ID);
|
||||||
return false;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
if (!filter) {
|
// No need for expensive global updates on users, so we avoid them
|
||||||
return true;
|
const usersById = getGlobal().users.byId;
|
||||||
}
|
|
||||||
|
|
||||||
return searchWords(getChatTitle(lang, chatsById[id], undefined, id === currentUserId), filter);
|
return sortChatIds(unique([
|
||||||
}), chatsById, undefined, priorityIds);
|
...filterChatsByName(lang, chatIds, chatsById, filter, currentUserId),
|
||||||
}, [activeListIds, archivedListIds, chatsById, currentUserId, filter, isOpen, lang, pinnedIds]);
|
...(contactIds ? filterUsersByName(contactIds, usersById, filter) : []),
|
||||||
|
]), chatsById, undefined, priorityIds);
|
||||||
|
}, [activeListIds, archivedListIds, chatsById, contactIds, currentUserId, filter, isOpen, lang, pinnedIds]);
|
||||||
|
|
||||||
const handleSelectUser = useCallback((userId: string) => {
|
const handleSelectUser = useCallback((userId: string) => {
|
||||||
setForwardChatId({ id: userId });
|
setForwardChatId({ id: userId });
|
||||||
}, [setForwardChatId]);
|
}, [setForwardChatId]);
|
||||||
|
|
||||||
const renderingChatIds = useCurrentOrPrev(chatIds)!;
|
const renderingChatAndContactIds = useCurrentOrPrev(chatAndContactIds)!;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ChatOrUserPicker
|
<ChatOrUserPicker
|
||||||
currentUserId={currentUserId}
|
currentUserId={currentUserId}
|
||||||
isOpen={isOpen}
|
isOpen={isOpen}
|
||||||
chatOrUserIds={renderingChatIds}
|
chatOrUserIds={renderingChatAndContactIds}
|
||||||
filterRef={filterRef}
|
filterRef={filterRef}
|
||||||
filterPlaceholder={lang('ForwardTo')}
|
filterPlaceholder={lang('ForwardTo')}
|
||||||
filter={filter}
|
filter={filter}
|
||||||
@ -110,9 +113,10 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
chatsById,
|
chatsById,
|
||||||
pinnedIds: orderedPinnedIds.active,
|
|
||||||
activeListIds: listIds.active,
|
activeListIds: listIds.active,
|
||||||
archivedListIds: listIds.archived,
|
archivedListIds: listIds.archived,
|
||||||
|
pinnedIds: orderedPinnedIds.active,
|
||||||
|
contactIds: global.contactList?.userIds,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@ -85,7 +85,6 @@ const AttachmentModal: FC<OwnProps> = ({
|
|||||||
groupChatMembers,
|
groupChatMembers,
|
||||||
undefined,
|
undefined,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
usersById,
|
|
||||||
);
|
);
|
||||||
const {
|
const {
|
||||||
isEmojiTooltipOpen, closeEmojiTooltip, filteredEmojis, insertEmoji,
|
isEmojiTooltipOpen, closeEmojiTooltip, filteredEmojis, insertEmoji,
|
||||||
|
|||||||
@ -299,7 +299,6 @@ const Composer: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
groupChatMembers,
|
groupChatMembers,
|
||||||
topInlineBotIds,
|
topInlineBotIds,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
usersById,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
@ -1,15 +0,0 @@
|
|||||||
import { ApiUser } from '../../../../api/types';
|
|
||||||
import { getUserFullName } from '../../../../modules/helpers';
|
|
||||||
import searchWords from '../../../../util/searchWords';
|
|
||||||
|
|
||||||
// TODO: Support cyrillic translit search
|
|
||||||
export default function searchUserName(filter: string, user: ApiUser) {
|
|
||||||
const usernameLowered = user.username.toLowerCase();
|
|
||||||
const fullName = getUserFullName(user);
|
|
||||||
const fullNameLowered = fullName && fullName.toLowerCase();
|
|
||||||
const filterLowered = filter.toLowerCase();
|
|
||||||
|
|
||||||
return usernameLowered.startsWith(filterLowered) || (
|
|
||||||
fullNameLowered && searchWords(fullNameLowered, filterLowered)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,15 +1,15 @@
|
|||||||
import {
|
import {
|
||||||
useCallback, useEffect, useState, useMemo,
|
useCallback, useEffect, useState,
|
||||||
} from '../../../../lib/teact/teact';
|
} from '../../../../lib/teact/teact';
|
||||||
|
import { getGlobal } from '../../../../lib/teact/teactn';
|
||||||
|
|
||||||
import { ApiMessageEntityTypes, ApiChatMember, ApiUser } from '../../../../api/types';
|
import { ApiMessageEntityTypes, ApiChatMember, ApiUser } from '../../../../api/types';
|
||||||
import { EDITABLE_INPUT_ID } from '../../../../config';
|
import { EDITABLE_INPUT_ID } from '../../../../config';
|
||||||
import { getUserFirstOrLastName } from '../../../../modules/helpers';
|
import { filterUsersByName, getUserFirstOrLastName } from '../../../../modules/helpers';
|
||||||
import searchUserName from '../helpers/searchUserName';
|
|
||||||
import { prepareForRegExp } from '../helpers/prepareForRegExp';
|
import { prepareForRegExp } from '../helpers/prepareForRegExp';
|
||||||
import focusEditableElement from '../../../../util/focusEditableElement';
|
import focusEditableElement from '../../../../util/focusEditableElement';
|
||||||
import useFlag from '../../../../hooks/useFlag';
|
import useFlag from '../../../../hooks/useFlag';
|
||||||
import { unique } from '../../../../util/iteratees';
|
import { pickTruthy, unique } from '../../../../util/iteratees';
|
||||||
import { throttle } from '../../../../util/schedulers';
|
import { throttle } from '../../../../util/schedulers';
|
||||||
|
|
||||||
const runThrottled = throttle((cb) => cb(), 500, true);
|
const runThrottled = throttle((cb) => cb(), 500, true);
|
||||||
@ -30,39 +30,37 @@ export default function useMentionTooltip(
|
|||||||
groupChatMembers?: ApiChatMember[],
|
groupChatMembers?: ApiChatMember[],
|
||||||
topInlineBotIds?: string[],
|
topInlineBotIds?: string[],
|
||||||
currentUserId?: string,
|
currentUserId?: string,
|
||||||
usersById?: Record<string, ApiUser>,
|
|
||||||
) {
|
) {
|
||||||
const [isOpen, markIsOpen, unmarkIsOpen] = useFlag();
|
const [isOpen, markIsOpen, unmarkIsOpen] = useFlag();
|
||||||
const [usersToMention, setUsersToMention] = useState<ApiUser[] | undefined>();
|
const [usersToMention, setUsersToMention] = useState<ApiUser[] | undefined>();
|
||||||
|
|
||||||
const topInlineBots = useMemo(() => {
|
const updateFilteredUsers = useCallback((filter, withInlineBots: boolean) => {
|
||||||
return (topInlineBotIds || []).map((id) => usersById?.[id]).filter<ApiUser>(Boolean as any);
|
// No need for expensive global updates on users, so we avoid them
|
||||||
}, [topInlineBotIds, usersById]);
|
const usersById = getGlobal().users.byId;
|
||||||
|
|
||||||
const getFilteredUsers = useCallback((filter, withInlineBots: boolean) => {
|
|
||||||
if (!(groupChatMembers || topInlineBotIds) || !usersById) {
|
if (!(groupChatMembers || topInlineBotIds) || !usersById) {
|
||||||
setUsersToMention(undefined);
|
setUsersToMention(undefined);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
runThrottled(() => {
|
runThrottled(() => {
|
||||||
const inlineBots = (withInlineBots ? topInlineBots : []).filter((inlineBot) => {
|
const memberIds = groupChatMembers?.reduce((acc: string[], member) => {
|
||||||
return !filter || searchUserName(filter, inlineBot);
|
if (member.userId !== currentUserId) {
|
||||||
});
|
acc.push(member.userId);
|
||||||
|
}
|
||||||
|
|
||||||
const chatMembers = (groupChatMembers || [])
|
return acc;
|
||||||
.map(({ userId }) => usersById[userId])
|
}, []);
|
||||||
.filter((user) => {
|
|
||||||
if (!user || user.id === currentUserId) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return !filter || searchUserName(filter, user);
|
const filteredIds = filterUsersByName(unique([
|
||||||
});
|
...((withInlineBots && topInlineBotIds) || []),
|
||||||
|
...(memberIds || []),
|
||||||
|
]), usersById, filter);
|
||||||
|
|
||||||
setUsersToMention(unique(inlineBots.concat(chatMembers)));
|
setUsersToMention(Object.values(pickTruthy(usersById, filteredIds)));
|
||||||
});
|
});
|
||||||
}, [currentUserId, groupChatMembers, topInlineBotIds, topInlineBots, usersById]);
|
}, [currentUserId, groupChatMembers, topInlineBotIds]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!canSuggestMembers || !html.length) {
|
if (!canSuggestMembers || !html.length) {
|
||||||
@ -74,11 +72,11 @@ export default function useMentionTooltip(
|
|||||||
|
|
||||||
if (usernameFilter) {
|
if (usernameFilter) {
|
||||||
const filter = usernameFilter ? usernameFilter.substr(1) : '';
|
const filter = usernameFilter ? usernameFilter.substr(1) : '';
|
||||||
getFilteredUsers(filter, canSuggestInlineBots(html));
|
updateFilteredUsers(filter, canSuggestInlineBots(html));
|
||||||
} else {
|
} else {
|
||||||
unmarkIsOpen();
|
unmarkIsOpen();
|
||||||
}
|
}
|
||||||
}, [canSuggestMembers, html, getFilteredUsers, markIsOpen, unmarkIsOpen]);
|
}, [canSuggestMembers, html, updateFilteredUsers, markIsOpen, unmarkIsOpen]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (usersToMention?.length) {
|
if (usersToMention?.length) {
|
||||||
|
|||||||
@ -1,19 +1,18 @@
|
|||||||
import React, {
|
import React, {
|
||||||
FC, useCallback, useMemo, memo, useState, useEffect,
|
FC, useCallback, useMemo, memo, useState, useEffect,
|
||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { withGlobal } from '../../lib/teact/teactn';
|
import { getGlobal, withGlobal } from '../../lib/teact/teactn';
|
||||||
|
|
||||||
import { GlobalActions } from '../../global/types';
|
import { GlobalActions } from '../../global/types';
|
||||||
import {
|
import {
|
||||||
ApiChat, ApiChatMember, ApiUpdateConnectionStateType, ApiUser,
|
ApiChat, ApiChatMember, ApiUpdateConnectionStateType,
|
||||||
} from '../../api/types';
|
} from '../../api/types';
|
||||||
import { NewChatMembersProgress } from '../../types';
|
import { NewChatMembersProgress } from '../../types';
|
||||||
|
|
||||||
import { pick, unique } from '../../util/iteratees';
|
import { pick, unique } from '../../util/iteratees';
|
||||||
import { selectChat } from '../../modules/selectors';
|
import { selectChat } from '../../modules/selectors';
|
||||||
import searchWords from '../../util/searchWords';
|
|
||||||
import {
|
import {
|
||||||
getUserFullName, isChatChannel, isUserBot, sortChatIds,
|
filterUsersByName, isChatChannel, isUserBot, sortChatIds,
|
||||||
} from '../../modules/helpers';
|
} from '../../modules/helpers';
|
||||||
import useLang from '../../hooks/useLang';
|
import useLang from '../../hooks/useLang';
|
||||||
import usePrevious from '../../hooks/usePrevious';
|
import usePrevious from '../../hooks/usePrevious';
|
||||||
@ -37,7 +36,6 @@ type StateProps = {
|
|||||||
isChannel?: boolean;
|
isChannel?: boolean;
|
||||||
members?: ApiChatMember[];
|
members?: ApiChatMember[];
|
||||||
currentUserId?: string;
|
currentUserId?: string;
|
||||||
usersById: Record<string, ApiUser>;
|
|
||||||
chatsById: Record<string, ApiChat>;
|
chatsById: Record<string, ApiChat>;
|
||||||
localContactIds?: string[];
|
localContactIds?: string[];
|
||||||
searchQuery?: string;
|
searchQuery?: string;
|
||||||
@ -55,7 +53,6 @@ const AddChatMembers: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
members,
|
members,
|
||||||
onNextStep,
|
onNextStep,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
usersById,
|
|
||||||
chatsById,
|
chatsById,
|
||||||
localContactIds,
|
localContactIds,
|
||||||
isLoading,
|
isLoading,
|
||||||
@ -90,43 +87,33 @@ const AddChatMembers: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
}, [setUserSearchQuery]);
|
}, [setUserSearchQuery]);
|
||||||
|
|
||||||
const displayedIds = useMemo(() => {
|
const displayedIds = useMemo(() => {
|
||||||
const contactIds = localContactIds
|
// No need for expensive global updates on users, so we avoid them
|
||||||
? sortChatIds(localContactIds.filter((id) => id !== currentUserId), chatsById)
|
const usersById = getGlobal().users.byId;
|
||||||
: [];
|
const filteredContactIds = localContactIds ? filterUsersByName(localContactIds, usersById, searchQuery) : [];
|
||||||
|
|
||||||
if (!searchQuery) {
|
|
||||||
return contactIds.filter((id) => !memberIds.includes(id));
|
|
||||||
}
|
|
||||||
|
|
||||||
const foundContactIds = contactIds.filter((id) => {
|
|
||||||
const user = usersById[id];
|
|
||||||
if (!user) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const fullName = getUserFullName(user);
|
|
||||||
return fullName && searchWords(fullName, searchQuery);
|
|
||||||
});
|
|
||||||
|
|
||||||
return sortChatIds(
|
return sortChatIds(
|
||||||
unique([
|
unique([
|
||||||
...foundContactIds,
|
...filteredContactIds,
|
||||||
...(localUserIds || []),
|
...(localUserIds || []),
|
||||||
...(globalUserIds || []),
|
...(globalUserIds || []),
|
||||||
]).filter((contactId) => {
|
]).filter((userId) => {
|
||||||
const user = usersById[contactId];
|
const user = usersById[userId];
|
||||||
|
|
||||||
// The user can be added to the chat if the following conditions are met:
|
// The user can be added to the chat if the following conditions are met:
|
||||||
// the user has not yet been added to the current chat
|
// the user has not yet been added to the current chat
|
||||||
|
// AND it is not the current user,
|
||||||
// AND (it is not found (user from global search) OR it is not a bot OR it is a bot,
|
// AND (it is not found (user from global search) OR it is not a bot OR it is a bot,
|
||||||
// but the current chat is not a channel AND the appropriate permission is set).
|
// but the current chat is not a channel AND the appropriate permission is set).
|
||||||
return !memberIds.includes(contactId)
|
return (
|
||||||
&& (!user || !isUserBot(user) || (!isChannel && user.canBeInvitedToGroup));
|
!memberIds.includes(userId)
|
||||||
|
&& userId !== currentUserId
|
||||||
|
&& (!user || !isUserBot(user) || (!isChannel && user.canBeInvitedToGroup))
|
||||||
|
);
|
||||||
}),
|
}),
|
||||||
chatsById,
|
chatsById,
|
||||||
);
|
);
|
||||||
}, [
|
}, [
|
||||||
localContactIds, chatsById, searchQuery, localUserIds, globalUserIds,
|
localContactIds, chatsById, searchQuery, localUserIds, globalUserIds, currentUserId, memberIds, isChannel,
|
||||||
currentUserId, usersById, memberIds, isChannel,
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleNextStep = useCallback(() => {
|
const handleNextStep = useCallback(() => {
|
||||||
@ -172,7 +159,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
(global, { chatId }): StateProps => {
|
(global, { chatId }): StateProps => {
|
||||||
const chat = selectChat(global, chatId);
|
const chat = selectChat(global, chatId);
|
||||||
const { userIds: localContactIds } = global.contactList || {};
|
const { userIds: localContactIds } = global.contactList || {};
|
||||||
const { byId: usersById } = global.users;
|
|
||||||
const { byId: chatsById } = global.chats;
|
const { byId: chatsById } = global.chats;
|
||||||
const { currentUserId, newChatMembersProgress, connectionState } = global;
|
const { currentUserId, newChatMembersProgress, connectionState } = global;
|
||||||
const isChannel = chat && isChatChannel(chat);
|
const isChannel = chat && isChatChannel(chat);
|
||||||
@ -188,7 +174,6 @@ export default memo(withGlobal<OwnProps>(
|
|||||||
isChannel,
|
isChannel,
|
||||||
members: chat?.fullInfo?.members,
|
members: chat?.fullInfo?.members,
|
||||||
currentUserId,
|
currentUserId,
|
||||||
usersById,
|
|
||||||
chatsById,
|
chatsById,
|
||||||
localContactIds,
|
localContactIds,
|
||||||
searchQuery,
|
searchQuery,
|
||||||
|
|||||||
@ -1,13 +1,12 @@
|
|||||||
import React, {
|
import React, {
|
||||||
FC, useMemo, useState, memo, useRef, useCallback,
|
FC, useMemo, useState, memo, useRef, useCallback,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { withGlobal } from '../../../lib/teact/teactn';
|
import { getGlobal, withGlobal } from '../../../lib/teact/teactn';
|
||||||
|
|
||||||
import { GlobalActions } from '../../../global/types';
|
import { GlobalActions } from '../../../global/types';
|
||||||
import { ApiChat, ApiUser } from '../../../api/types';
|
import { ApiChat } from '../../../api/types';
|
||||||
|
|
||||||
import { getUserFullName } from '../../../modules/helpers';
|
import { filterUsersByName } from '../../../modules/helpers';
|
||||||
import searchWords from '../../../util/searchWords';
|
|
||||||
import { pick } from '../../../util/iteratees';
|
import { pick } from '../../../util/iteratees';
|
||||||
import useLang from '../../../hooks/useLang';
|
import useLang from '../../../hooks/useLang';
|
||||||
|
|
||||||
@ -20,7 +19,6 @@ export type OwnProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type StateProps = {
|
type StateProps = {
|
||||||
usersById: Record<string, ApiUser>;
|
|
||||||
currentUserId?: string;
|
currentUserId?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -28,7 +26,6 @@ type DispatchProps = Pick<GlobalActions, 'loadMoreMembers' | 'deleteChatMember'>
|
|||||||
|
|
||||||
const RemoveGroupUserModal: FC<OwnProps & StateProps & DispatchProps> = ({
|
const RemoveGroupUserModal: FC<OwnProps & StateProps & DispatchProps> = ({
|
||||||
chat,
|
chat,
|
||||||
usersById,
|
|
||||||
currentUserId,
|
currentUserId,
|
||||||
isOpen,
|
isOpen,
|
||||||
onClose,
|
onClose,
|
||||||
@ -41,22 +38,19 @@ const RemoveGroupUserModal: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
const filterRef = useRef<HTMLInputElement>(null);
|
const filterRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
const usersId = useMemo(() => {
|
const usersId = useMemo(() => {
|
||||||
const availableMembers = (chat.fullInfo?.members || []).filter((member) => {
|
const availableMemberIds = (chat.fullInfo?.members || [])
|
||||||
return !member.isAdmin && !member.isOwner && member.userId !== currentUserId;
|
.reduce((acc: string[], member) => {
|
||||||
});
|
if (!member.isAdmin && !member.isOwner && member.userId !== currentUserId) {
|
||||||
|
acc.push(member.userId);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, []);
|
||||||
|
|
||||||
return availableMembers.reduce<string[]>((acc, member) => {
|
// No need for expensive global updates on users, so we avoid them
|
||||||
if (
|
const usersById = getGlobal().users.byId;
|
||||||
!filter
|
|
||||||
|| !usersById[member.userId]
|
|
||||||
|| searchWords(getUserFullName(usersById[member.userId]) || '', filter)
|
|
||||||
) {
|
|
||||||
acc.push(member.userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
return acc;
|
return filterUsersByName(availableMemberIds, usersById, filter);
|
||||||
}, []);
|
}, [chat.fullInfo?.members, currentUserId, filter]);
|
||||||
}, [chat.fullInfo?.members, currentUserId, filter, usersById]);
|
|
||||||
|
|
||||||
const handleRemoveUser = useCallback((userId: string) => {
|
const handleRemoveUser = useCallback((userId: string) => {
|
||||||
deleteChatMember({ chatId: chat.id, userId });
|
deleteChatMember({ chatId: chat.id, userId });
|
||||||
@ -80,14 +74,9 @@ const RemoveGroupUserModal: FC<OwnProps & StateProps & DispatchProps> = ({
|
|||||||
|
|
||||||
export default memo(withGlobal<OwnProps>(
|
export default memo(withGlobal<OwnProps>(
|
||||||
(global): StateProps => {
|
(global): StateProps => {
|
||||||
const {
|
const { currentUserId } = global;
|
||||||
users: {
|
|
||||||
byId: usersById,
|
|
||||||
},
|
|
||||||
currentUserId,
|
|
||||||
} = global;
|
|
||||||
|
|
||||||
return { usersById, currentUserId };
|
return { currentUserId };
|
||||||
},
|
},
|
||||||
(setGlobal, actions): DispatchProps => pick(actions, ['loadMoreMembers', 'deleteChatMember']),
|
(setGlobal, actions): DispatchProps => pick(actions, ['loadMoreMembers', 'deleteChatMember']),
|
||||||
)(RemoveGroupUserModal));
|
)(RemoveGroupUserModal));
|
||||||
|
|||||||
@ -15,6 +15,7 @@ import { ARCHIVED_FOLDER_ID, REPLIES_USER_ID } from '../../config';
|
|||||||
import { orderBy } from '../../util/iteratees';
|
import { orderBy } from '../../util/iteratees';
|
||||||
import { getUserFirstOrLastName } from './users';
|
import { getUserFirstOrLastName } from './users';
|
||||||
import { formatDateToString, formatTime } from '../../util/dateFormat';
|
import { formatDateToString, formatTime } from '../../util/dateFormat';
|
||||||
|
import { prepareSearchWordsForNeedle } from '../../util/searchWords';
|
||||||
|
|
||||||
const FOREVER_BANNED_DATE = Date.now() / 1000 + 31622400; // 366 days
|
const FOREVER_BANNED_DATE = Date.now() / 1000 + 31622400; // 366 days
|
||||||
|
|
||||||
@ -560,3 +561,26 @@ export function sortChatIds(
|
|||||||
return priority;
|
return priority;
|
||||||
}, 'desc');
|
}, 'desc');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function filterChatsByName(
|
||||||
|
lang: LangFn,
|
||||||
|
chatIds: string[],
|
||||||
|
chatsById: Record<string, ApiChat>,
|
||||||
|
query?: string,
|
||||||
|
currentUserId?: string,
|
||||||
|
) {
|
||||||
|
if (!query) {
|
||||||
|
return chatIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchWords = prepareSearchWordsForNeedle(query);
|
||||||
|
|
||||||
|
return chatIds.filter((id) => {
|
||||||
|
const chat = chatsById[id];
|
||||||
|
if (!chat) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return searchWords(getChatTitle(lang, chat, undefined, id === currentUserId));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { formatFullDate, formatTime } from '../../util/dateFormat';
|
|||||||
import { orderBy } from '../../util/iteratees';
|
import { orderBy } from '../../util/iteratees';
|
||||||
import { LangFn } from '../../hooks/useLang';
|
import { LangFn } from '../../hooks/useLang';
|
||||||
import { getServerTime } from '../../util/serverTime';
|
import { getServerTime } from '../../util/serverTime';
|
||||||
|
import { prepareSearchWordsForNeedle } from '../../util/searchWords';
|
||||||
|
|
||||||
const USER_COLOR_KEYS = [1, 8, 5, 2, 7, 4, 6];
|
const USER_COLOR_KEYS = [1, 8, 5, 2, 7, 4, 6];
|
||||||
|
|
||||||
@ -231,6 +232,24 @@ export function sortUserIds(
|
|||||||
}, 'desc');
|
}, 'desc');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function filterUsersByName(userIds: string[], usersById: Record<string, ApiUser>, query?: string) {
|
||||||
|
if (!query) {
|
||||||
|
return userIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
const searchWords = prepareSearchWordsForNeedle(query);
|
||||||
|
|
||||||
|
return userIds.filter((id) => {
|
||||||
|
const user = usersById[id];
|
||||||
|
if (!user) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const name = getUserFullName(user);
|
||||||
|
return (name && searchWords(name)) || searchWords(user.username);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function getUserIdDividend(userId: string) {
|
export function getUserIdDividend(userId: string) {
|
||||||
// Workaround for old-fashioned IDs stored locally
|
// Workaround for old-fashioned IDs stored locally
|
||||||
if (typeof userId === 'number') {
|
if (typeof userId === 'number') {
|
||||||
|
|||||||
@ -7,15 +7,36 @@ try {
|
|||||||
RE_NOT_LETTER = new RegExp('[^\\wа-яё]+', 'i');
|
RE_NOT_LETTER = new RegExp('[^\\wа-яё]+', 'i');
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function searchWords(haystack: string, needle: string) {
|
export default function searchWords(haystack: string, needle: string | string[]) {
|
||||||
if (!haystack || !needle) {
|
if (!haystack || !needle) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const haystackWords = haystack.toLowerCase().split(RE_NOT_LETTER);
|
const needleWords = typeof needle === 'string' ? needle.toLowerCase().split(RE_NOT_LETTER) : needle;
|
||||||
|
const haystackLower = haystack.toLowerCase();
|
||||||
|
|
||||||
|
// @optimization
|
||||||
|
if (needleWords.length === 1 && !haystackLower.includes(needleWords[0])) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let haystackWords: string[];
|
||||||
|
|
||||||
|
return needleWords.every((needleWord) => {
|
||||||
|
if (!haystackLower.includes(needleWord)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!haystackWords) {
|
||||||
|
haystackWords = haystackLower.split(RE_NOT_LETTER);
|
||||||
|
}
|
||||||
|
|
||||||
|
return haystackWords.some((haystackWord) => haystackWord.startsWith(needleWord));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function prepareSearchWordsForNeedle(needle: string) {
|
||||||
const needleWords = needle.toLowerCase().split(RE_NOT_LETTER);
|
const needleWords = needle.toLowerCase().split(RE_NOT_LETTER);
|
||||||
|
|
||||||
return needleWords.every((needleWord) => (
|
return (haystack: string) => searchWords(haystack, needleWords);
|
||||||
haystackWords.some((haystackWord) => haystackWord.startsWith(needleWord))
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user