mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
fix(app): match member search on accents, display names and ids (#1850)
This commit is contained in:
1 parent
8c431c7562
commit
b0e882645e
7 files changed
+256
-200
No files matched your search
@@ -155,11 +155,11 @@ export const AddOverridePopout: React.FC<AddOverridePopoutProps> = observer(func
|
|||||||
}
|
}
|
||||||
const context = searchContextRef.current;
|
const context = searchContextRef.current;
|
||||||
if (queryForServer.length === 0) {
|
if (queryForServer.length === 0) {
|
||||||
context?.clearQuery();
|
context?.cancelSearch();
|
||||||
setServerMemberIds([]);
|
setServerMemberIds([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
context?.setQuery(queryForServer, {guild: guildId});
|
context?.beginSearch(queryForServer, {guild: guildId});
|
||||||
if (GuildMembers.isGuildFullyLoaded(guildId)) {
|
if (GuildMembers.isGuildFullyLoaded(guildId)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-5
@@ -251,7 +251,7 @@ export function useMessageSearchAutocomplete({
|
|||||||
memberFetchQueryRef.current = '';
|
memberFetchQueryRef.current = '';
|
||||||
const context = searchContextRef.current;
|
const context = searchContextRef.current;
|
||||||
if (context) {
|
if (context) {
|
||||||
context.clearQuery();
|
context.cancelSearch();
|
||||||
}
|
}
|
||||||
setMemberSearchResults([]);
|
setMemberSearchResults([]);
|
||||||
return;
|
return;
|
||||||
@@ -261,7 +261,7 @@ export function useMessageSearchAutocomplete({
|
|||||||
memberFetchQueryRef.current = '';
|
memberFetchQueryRef.current = '';
|
||||||
const context = searchContextRef.current;
|
const context = searchContextRef.current;
|
||||||
if (context) {
|
if (context) {
|
||||||
context.clearQuery();
|
context.cancelSearch();
|
||||||
}
|
}
|
||||||
setMemberSearchResults([]);
|
setMemberSearchResults([]);
|
||||||
return;
|
return;
|
||||||
@@ -273,14 +273,14 @@ export function useMessageSearchAutocomplete({
|
|||||||
if (searchQuery.length === 0) {
|
if (searchQuery.length === 0) {
|
||||||
memberFetchQueryRef.current = '';
|
memberFetchQueryRef.current = '';
|
||||||
if (context) {
|
if (context) {
|
||||||
context.clearQuery();
|
context.cancelSearch();
|
||||||
}
|
}
|
||||||
setMemberSearchResults([]);
|
setMemberSearchResults([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const boosters = buildUserSearchBoosters(channel, currentGuildIdForScope, plan.mode);
|
const boosters = buildUserSearchBoosters(channel, currentGuildIdForScope, plan.mode);
|
||||||
if (context) {
|
if (context) {
|
||||||
context.setQuery(searchQuery, plan.workerFilters, new Set(), new Set(), boosters);
|
context.beginSearch(searchQuery, plan.workerFilters, new Set(), new Set(), boosters);
|
||||||
}
|
}
|
||||||
if (!plan.guildsToSearch || plan.guildsToSearch.length === 0) {
|
if (!plan.guildsToSearch || plan.guildsToSearch.length === 0) {
|
||||||
memberFetchQueryRef.current = searchQuery;
|
memberFetchQueryRef.current = searchQuery;
|
||||||
@@ -311,7 +311,7 @@ export function useMessageSearchAutocomplete({
|
|||||||
if (autocompleteType !== 'users' || !currentFilter) {
|
if (autocompleteType !== 'users' || !currentFilter) {
|
||||||
const context = searchContextRef.current;
|
const context = searchContextRef.current;
|
||||||
if (context) {
|
if (context) {
|
||||||
context.clearQuery();
|
context.cancelSearch();
|
||||||
}
|
}
|
||||||
setMemberSearchResults([]);
|
setMemberSearchResults([]);
|
||||||
memberFetchQueryRef.current = '';
|
memberFetchQueryRef.current = '';
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ export function useAutocompleteMemberSearch({
|
|||||||
}
|
}
|
||||||
if (!isMemberSearchTrigger(triggerType) || guildId == null) {
|
if (!isMemberSearchTrigger(triggerType) || guildId == null) {
|
||||||
currentGuildIdRef.current = null;
|
currentGuildIdRef.current = null;
|
||||||
context.clearQuery();
|
context.cancelSearch();
|
||||||
setResults([]);
|
setResults([]);
|
||||||
if (debounceTimerRef.current != null) {
|
if (debounceTimerRef.current != null) {
|
||||||
clearTimeout(debounceTimerRef.current);
|
clearTimeout(debounceTimerRef.current);
|
||||||
@@ -94,7 +94,7 @@ export function useAutocompleteMemberSearch({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
currentGuildIdRef.current = guildId;
|
currentGuildIdRef.current = guildId;
|
||||||
context.setQuery(matchedText, {guild: guildId}, new Set(), new Set(), MentionFrecency.getBoosters(guildId));
|
context.beginSearch(matchedText, {guild: guildId}, new Set(), new Set(), MentionFrecency.getBoosters(guildId));
|
||||||
if (debounceTimerRef.current != null) {
|
if (debounceTimerRef.current != null) {
|
||||||
clearTimeout(debounceTimerRef.current);
|
clearTimeout(debounceTimerRef.current);
|
||||||
}
|
}
|
||||||
@@ -147,13 +147,13 @@ export function useAutocompleteSlotMemberSearch({
|
|||||||
}
|
}
|
||||||
if (slotContext == null || slotContext.optionType !== 'user' || guildId == null) {
|
if (slotContext == null || slotContext.optionType !== 'user' || guildId == null) {
|
||||||
currentGuildIdRef.current = null;
|
currentGuildIdRef.current = null;
|
||||||
context.clearQuery();
|
context.cancelSearch();
|
||||||
setResults([]);
|
setResults([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const query = normalizeSlotAutocompleteQuery(slotContext);
|
const query = normalizeSlotAutocompleteQuery(slotContext);
|
||||||
currentGuildIdRef.current = guildId;
|
currentGuildIdRef.current = guildId;
|
||||||
context.setQuery(query, {guild: guildId}, new Set(), new Set(), MentionFrecency.getBoosters(guildId));
|
context.beginSearch(query, {guild: guildId}, new Set(), new Set(), MentionFrecency.getBoosters(guildId));
|
||||||
debounceTimerRef.current = setTimeout(() => {
|
debounceTimerRef.current = setTimeout(() => {
|
||||||
void MemberSearch.fetchMembersInBackground(query, [guildId]);
|
void MemberSearch.fetchMembersInBackground(query, [guildId]);
|
||||||
debounceTimerRef.current = null;
|
debounceTimerRef.current = null;
|
||||||
|
|||||||
@@ -11,17 +11,17 @@ import {RelationshipTypes} from '@fluxer/constants/src/UserConstants';
|
|||||||
import {makeAutoObservable} from 'mobx';
|
import {makeAutoObservable} from 'mobx';
|
||||||
|
|
||||||
export enum MemberSearchActionTypes {
|
export enum MemberSearchActionTypes {
|
||||||
UPDATE_USERS = 'UPDATE_USERS',
|
INGEST_DIRECTORY = 'INGEST_DIRECTORY',
|
||||||
USER_RESULTS = 'USER_RESULTS',
|
MATCHES_READY = 'MATCHES_READY',
|
||||||
QUERY_SET = 'QUERY_SET',
|
SEARCH_BEGIN = 'SEARCH_BEGIN',
|
||||||
QUERY_CLEAR = 'QUERY_CLEAR',
|
SEARCH_CANCEL = 'SEARCH_CANCEL',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum MemberSearchWorkerMessageTypes {
|
export enum MemberSearchWorkerMessageTypes {
|
||||||
UPDATE_USERS = 'UPDATE_USERS',
|
INGEST_DIRECTORY = 'INGEST_DIRECTORY',
|
||||||
USER_RESULTS = 'USER_RESULTS',
|
MATCHES_READY = 'MATCHES_READY',
|
||||||
QUERY_SET = 'QUERY_SET',
|
SEARCH_BEGIN = 'SEARCH_BEGIN',
|
||||||
QUERY_CLEAR = 'QUERY_CLEAR',
|
SEARCH_CANCEL = 'SEARCH_CANCEL',
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MemberSearchFilters {
|
export interface MemberSearchFilters {
|
||||||
@@ -63,32 +63,33 @@ interface WorkerMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface MemberResultsMessage extends WorkerMessage {
|
interface MemberResultsMessage extends WorkerMessage {
|
||||||
type: MemberSearchWorkerMessageTypes.USER_RESULTS;
|
type: MemberSearchWorkerMessageTypes.MATCHES_READY;
|
||||||
uuid: string;
|
uuid: string;
|
||||||
generation: number;
|
generation: number;
|
||||||
payload: Array<TransformedMember>;
|
payload: Array<TransformedMember>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UpdateMembersMessage extends WorkerMessage {
|
interface UpdateMembersMessage extends WorkerMessage {
|
||||||
type: MemberSearchWorkerMessageTypes.UPDATE_USERS;
|
type: MemberSearchWorkerMessageTypes.INGEST_DIRECTORY;
|
||||||
payload: {
|
payload: {
|
||||||
users: Array<TransformedMember>;
|
users: Array<TransformedMember>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
interface QuerySetMessage extends WorkerMessage {
|
interface QuerySetMessage extends WorkerMessage {
|
||||||
type: MemberSearchWorkerMessageTypes.QUERY_SET;
|
type: MemberSearchWorkerMessageTypes.SEARCH_BEGIN;
|
||||||
uuid: string;
|
uuid: string;
|
||||||
payload: QueryData;
|
payload: QueryData;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface QueryClearMessage extends WorkerMessage {
|
interface QueryClearMessage extends WorkerMessage {
|
||||||
type: MemberSearchWorkerMessageTypes.QUERY_CLEAR;
|
type: MemberSearchWorkerMessageTypes.SEARCH_CANCEL;
|
||||||
uuid: string;
|
uuid: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_LIMIT = 10;
|
const DEFAULT_LIMIT = 10;
|
||||||
const BACKGROUND_FETCH_DEDUP_WINDOW_MS = 750;
|
const MEMBER_FETCH_DEDUPE_MS = 60_000;
|
||||||
|
const MEMBER_FETCH_SETTLE_MS = 750;
|
||||||
|
|
||||||
let worker: Worker | null = null;
|
let worker: Worker | null = null;
|
||||||
const searchContexts = new Set<SearchContext>();
|
const searchContexts = new Set<SearchContext>();
|
||||||
@@ -102,7 +103,7 @@ function updateMembers(members: Array<TransformedMember>): void {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
worker.postMessage({
|
worker.postMessage({
|
||||||
type: MemberSearchWorkerMessageTypes.UPDATE_USERS,
|
type: MemberSearchWorkerMessageTypes.INGEST_DIRECTORY,
|
||||||
payload: {users: filtered},
|
payload: {users: filtered},
|
||||||
} as UpdateMembersMessage);
|
} as UpdateMembersMessage);
|
||||||
}
|
}
|
||||||
@@ -159,41 +160,41 @@ function updateMembersList(members: Array<GuildMember>, guildId?: string): Array
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class SearchContext {
|
export class SearchContext {
|
||||||
private readonly _uuid: string;
|
private readonly _contextId: string;
|
||||||
private readonly _callback: (results: Array<TransformedMember>) => void;
|
private readonly _deliverResults: (results: Array<TransformedMember>) => void;
|
||||||
private readonly _limit: number;
|
private readonly _maxResults: number;
|
||||||
private _currentQuery: QueryData | false | null;
|
private _inFlightQuery: QueryData | false | null;
|
||||||
private _nextQuery: QueryData | null;
|
private _queuedQuery: QueryData | null;
|
||||||
private _latestGeneration: number;
|
private _latestGeneration: number;
|
||||||
private _nextGeneration: number;
|
private _nextGeneration: number;
|
||||||
private readonly _handleMessages: (event: MessageEvent<WorkerMessage>) => void;
|
private readonly _handleMessages: (event: MessageEvent<WorkerMessage>) => void;
|
||||||
private _attachedWorker: Worker | null = null;
|
private _attachedWorker: Worker | null = null;
|
||||||
|
|
||||||
constructor(callback: (results: Array<TransformedMember>) => void, limit: number = DEFAULT_LIMIT) {
|
constructor(callback: (results: Array<TransformedMember>) => void, limit: number = DEFAULT_LIMIT) {
|
||||||
this._uuid = crypto.randomUUID();
|
this._contextId = crypto.randomUUID();
|
||||||
this._callback = callback;
|
this._deliverResults = callback;
|
||||||
this._limit = limit;
|
this._maxResults = limit;
|
||||||
this._currentQuery = null;
|
this._inFlightQuery = null;
|
||||||
this._nextQuery = null;
|
this._queuedQuery = null;
|
||||||
this._latestGeneration = 0;
|
this._latestGeneration = 0;
|
||||||
this._nextGeneration = 1;
|
this._nextGeneration = 1;
|
||||||
this._handleMessages = (event: MessageEvent<WorkerMessage>) => {
|
this._handleMessages = (event: MessageEvent<WorkerMessage>) => {
|
||||||
const data = event.data;
|
const data = event.data;
|
||||||
if (!data || data.type !== MemberSearchWorkerMessageTypes.USER_RESULTS) {
|
if (!data || data.type !== MemberSearchWorkerMessageTypes.MATCHES_READY) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const resultsMessage = data as MemberResultsMessage;
|
const resultsMessage = data as MemberResultsMessage;
|
||||||
if (resultsMessage.uuid !== this._uuid) {
|
if (resultsMessage.uuid !== this._contextId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const isLatestGeneration = resultsMessage.generation === this._latestGeneration;
|
const isLatestGeneration = resultsMessage.generation === this._latestGeneration;
|
||||||
if (isLatestGeneration && this._currentQuery !== false) {
|
if (isLatestGeneration && this._inFlightQuery !== false) {
|
||||||
this._callback(resultsMessage.payload);
|
this._deliverResults(resultsMessage.payload);
|
||||||
}
|
}
|
||||||
const currentQuery = this._currentQuery;
|
const currentQuery = this._inFlightQuery;
|
||||||
if (currentQuery !== null && currentQuery !== false && currentQuery.generation === resultsMessage.generation) {
|
if (currentQuery !== null && currentQuery !== false && currentQuery.generation === resultsMessage.generation) {
|
||||||
this._currentQuery = null;
|
this._inFlightQuery = null;
|
||||||
this._setNextQuery();
|
this._flushQueuedQuery();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
searchContexts.add(this);
|
searchContexts.add(this);
|
||||||
@@ -201,7 +202,7 @@ export class SearchContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destroy(): void {
|
destroy(): void {
|
||||||
this.clearQuery();
|
this.cancelSearch();
|
||||||
searchContexts.delete(this);
|
searchContexts.delete(this);
|
||||||
this.attachToWorker(null);
|
this.attachToWorker(null);
|
||||||
}
|
}
|
||||||
@@ -218,36 +219,36 @@ export class SearchContext {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
nextWorker.addEventListener('message', this._handleMessages);
|
nextWorker.addEventListener('message', this._handleMessages);
|
||||||
if (this._currentQuery === false) {
|
if (this._inFlightQuery === false) {
|
||||||
nextWorker.postMessage({
|
nextWorker.postMessage({
|
||||||
uuid: this._uuid,
|
uuid: this._contextId,
|
||||||
type: MemberSearchWorkerMessageTypes.QUERY_CLEAR,
|
type: MemberSearchWorkerMessageTypes.SEARCH_CANCEL,
|
||||||
} as QueryClearMessage);
|
} as QueryClearMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this._currentQuery != null) {
|
if (this._inFlightQuery != null) {
|
||||||
nextWorker.postMessage({
|
nextWorker.postMessage({
|
||||||
uuid: this._uuid,
|
uuid: this._contextId,
|
||||||
type: MemberSearchWorkerMessageTypes.QUERY_SET,
|
type: MemberSearchWorkerMessageTypes.SEARCH_BEGIN,
|
||||||
payload: this._currentQuery,
|
payload: this._inFlightQuery,
|
||||||
} as QuerySetMessage);
|
} as QuerySetMessage);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._setNextQuery();
|
this._flushQueuedQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
clearQuery(): void {
|
cancelSearch(): void {
|
||||||
this._currentQuery = false;
|
this._inFlightQuery = false;
|
||||||
this._nextQuery = null;
|
this._queuedQuery = null;
|
||||||
if (this._attachedWorker != null) {
|
if (this._attachedWorker != null) {
|
||||||
this._attachedWorker.postMessage({
|
this._attachedWorker.postMessage({
|
||||||
uuid: this._uuid,
|
uuid: this._contextId,
|
||||||
type: MemberSearchWorkerMessageTypes.QUERY_CLEAR,
|
type: MemberSearchWorkerMessageTypes.SEARCH_CANCEL,
|
||||||
} as QueryClearMessage);
|
} as QueryClearMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setQuery(
|
beginSearch(
|
||||||
query: string,
|
query: string,
|
||||||
filters: MemberSearchFilters = {},
|
filters: MemberSearchFilters = {},
|
||||||
blacklist: QueryBlacklist = new Set(),
|
blacklist: QueryBlacklist = new Set(),
|
||||||
@@ -259,29 +260,29 @@ export class SearchContext {
|
|||||||
}
|
}
|
||||||
const generation = this._nextGeneration++;
|
const generation = this._nextGeneration++;
|
||||||
this._latestGeneration = generation;
|
this._latestGeneration = generation;
|
||||||
this._nextQuery = {
|
this._queuedQuery = {
|
||||||
query,
|
query,
|
||||||
filters,
|
filters,
|
||||||
blacklist: Array.from(blacklist),
|
blacklist: Array.from(blacklist),
|
||||||
whitelist: Array.from(whitelist),
|
whitelist: Array.from(whitelist),
|
||||||
boosters,
|
boosters,
|
||||||
limit: this._limit,
|
limit: this._maxResults,
|
||||||
generation,
|
generation,
|
||||||
};
|
};
|
||||||
this._setNextQuery();
|
this._flushQueuedQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _setNextQuery(): void {
|
private _flushQueuedQuery(): void {
|
||||||
if (this._currentQuery || !this._nextQuery) {
|
if (this._inFlightQuery || !this._queuedQuery) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this._currentQuery = this._nextQuery;
|
this._inFlightQuery = this._queuedQuery;
|
||||||
this._nextQuery = null;
|
this._queuedQuery = null;
|
||||||
if (this._attachedWorker != null) {
|
if (this._attachedWorker != null) {
|
||||||
this._attachedWorker.postMessage({
|
this._attachedWorker.postMessage({
|
||||||
uuid: this._uuid,
|
uuid: this._contextId,
|
||||||
type: MemberSearchWorkerMessageTypes.QUERY_SET,
|
type: MemberSearchWorkerMessageTypes.SEARCH_BEGIN,
|
||||||
payload: this._currentQuery,
|
payload: this._inFlightQuery,
|
||||||
} as QuerySetMessage);
|
} as QuerySetMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -290,7 +291,7 @@ export class SearchContext {
|
|||||||
class MemberSearch {
|
class MemberSearch {
|
||||||
private logger = new Logger('MemberSearch');
|
private logger = new Logger('MemberSearch');
|
||||||
private initialized: boolean = false;
|
private initialized: boolean = false;
|
||||||
private readonly inFlightFetches = new Map<string, Promise<void>>();
|
private readonly recentFetches = new Map<string, number>();
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
makeAutoObservable(this);
|
makeAutoObservable(this);
|
||||||
@@ -455,7 +456,7 @@ class MemberSearch {
|
|||||||
cleanup(): void {
|
cleanup(): void {
|
||||||
this.terminate();
|
this.terminate();
|
||||||
this.initialized = false;
|
this.initialized = false;
|
||||||
this.inFlightFetches.clear();
|
this.recentFetches.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchMembersInBackground(query: string, guildIds: Array<string>, priorityGuildId?: string): Promise<void> {
|
async fetchMembersInBackground(query: string, guildIds: Array<string>, priorityGuildId?: string): Promise<void> {
|
||||||
@@ -483,24 +484,25 @@ class MemberSearch {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const key = `${eligibleGuildIds.join(',')}:${trimmed.toLowerCase()}`;
|
const key = `${eligibleGuildIds.join(',')}:${trimmed.toLowerCase()}`;
|
||||||
const existing = this.inFlightFetches.get(key);
|
const now = Date.now();
|
||||||
if (existing) {
|
for (const [previousKey, requestedAt] of this.recentFetches) {
|
||||||
await existing;
|
if (now - requestedAt >= MEMBER_FETCH_DEDUPE_MS) {
|
||||||
|
this.recentFetches.delete(previousKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.recentFetches.has(key)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const promise = new Promise<void>((resolve) => {
|
this.recentFetches.set(key, now);
|
||||||
GuildMembers.requestMembersInBackground({
|
GuildMembers.requestMembersInBackground({
|
||||||
guildIds: eligibleGuildIds,
|
guildIds: eligibleGuildIds,
|
||||||
query: trimmed,
|
query: trimmed,
|
||||||
limit: 25,
|
limit: 25,
|
||||||
presences: true,
|
presences: true,
|
||||||
});
|
});
|
||||||
setTimeout(resolve, BACKGROUND_FETCH_DEDUP_WINDOW_MS);
|
await new Promise<void>((resolve) => {
|
||||||
}).finally(() => {
|
setTimeout(resolve, MEMBER_FETCH_SETTLE_MS);
|
||||||
this.inFlightFetches.delete(key);
|
|
||||||
});
|
});
|
||||||
this.inFlightFetches.set(key, promise);
|
|
||||||
await promise;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||||
|
|
||||||
export enum MessageTypes {
|
export enum MessageTypes {
|
||||||
UPDATE_USERS = 'UPDATE_USERS',
|
INGEST_DIRECTORY = 'INGEST_DIRECTORY',
|
||||||
USER_RESULTS = 'USER_RESULTS',
|
MATCHES_READY = 'MATCHES_READY',
|
||||||
QUERY_SET = 'QUERY_SET',
|
SEARCH_BEGIN = 'SEARCH_BEGIN',
|
||||||
QUERY_CLEAR = 'QUERY_CLEAR',
|
SEARCH_CANCEL = 'SEARCH_CANCEL',
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TransformedUser {
|
interface TransformedUser {
|
||||||
@@ -23,7 +23,7 @@ interface TransformedUser {
|
|||||||
interface SearchResult {
|
interface SearchResult {
|
||||||
id: string;
|
id: string;
|
||||||
username: string;
|
username: string;
|
||||||
comparator: string;
|
rankLabel: string;
|
||||||
score: number;
|
score: number;
|
||||||
isBot?: boolean;
|
isBot?: boolean;
|
||||||
}
|
}
|
||||||
@@ -54,7 +54,19 @@ interface UpdateUsersPayload {
|
|||||||
users: Array<TransformedUser>;
|
users: Array<TransformedUser>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const userIndex: Map<string, TransformedUser> = new Map();
|
interface SearchForm {
|
||||||
|
value: string;
|
||||||
|
lower: string;
|
||||||
|
stripped: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IndexedUser {
|
||||||
|
user: TransformedUser;
|
||||||
|
named: Array<SearchForm>;
|
||||||
|
perGuild: Record<string, SearchForm> | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userIndex: Map<string, IndexedUser> = new Map();
|
||||||
const activeQueries: Map<string, SearchQuery> = new Map();
|
const activeQueries: Map<string, SearchQuery> = new Map();
|
||||||
const pendingSearches: Set<string> = new Set();
|
const pendingSearches: Set<string> = new Set();
|
||||||
const SCORE_EXACT_PREFIX = 10;
|
const SCORE_EXACT_PREFIX = 10;
|
||||||
@@ -65,43 +77,59 @@ const FRIEND_KEY = 'isFriend';
|
|||||||
const BOT_KEY = 'isBot';
|
const BOT_KEY = 'isBot';
|
||||||
const USERNAME_KEY = 'username';
|
const USERNAME_KEY = 'username';
|
||||||
const IGNORED_KEYS = new Set([BOT_KEY, FRIEND_KEY, USERNAME_KEY, 'guildIds', 'guildNicknames']);
|
const IGNORED_KEYS = new Set([BOT_KEY, FRIEND_KEY, USERNAME_KEY, 'guildIds', 'guildNicknames']);
|
||||||
|
const COMBINING_MARKS = /[\u0300-\u036f]/g;
|
||||||
|
|
||||||
function getSearchValues(user: TransformedUser, filters?: SearchFilters): Array<string> {
|
function stripCombiningMarks(text: string): string {
|
||||||
const values: Array<string> = [];
|
const decomposed = text.normalize('NFD');
|
||||||
if (user.username.length > 0) {
|
const withoutMarks = decomposed.replace(COMBINING_MARKS, '');
|
||||||
values.push(user.username);
|
if (withoutMarks.length === decomposed.length) {
|
||||||
|
return text;
|
||||||
}
|
}
|
||||||
for (const key of Object.keys(user)) {
|
return withoutMarks.normalize('NFC');
|
||||||
if (IGNORED_KEYS.has(key) || key === '_delete' || key === '_removeGuild') {
|
}
|
||||||
continue;
|
|
||||||
}
|
function toSearchForm(value: string): SearchForm {
|
||||||
const value = user[key];
|
const lower = value.toLowerCase();
|
||||||
if (typeof value === 'string' && value.length > 0) {
|
return {value, lower, stripped: stripCombiningMarks(lower)};
|
||||||
values.push(value);
|
}
|
||||||
}
|
|
||||||
|
function indexUser(user: TransformedUser): IndexedUser {
|
||||||
|
const named: Array<SearchForm> = [];
|
||||||
|
if (user.username.length > 0) {
|
||||||
|
named.push(toSearchForm(user.username));
|
||||||
|
}
|
||||||
|
const globalName = user.globalName;
|
||||||
|
if (typeof globalName === 'string' && globalName.length > 0) {
|
||||||
|
named.push(toSearchForm(globalName));
|
||||||
}
|
}
|
||||||
const guildNicknames = user.guildNicknames;
|
const guildNicknames = user.guildNicknames;
|
||||||
if (guildNicknames == null) {
|
if (guildNicknames == null) {
|
||||||
return values;
|
return {user, named, perGuild: null};
|
||||||
|
}
|
||||||
|
let perGuild: Record<string, SearchForm> | null = null;
|
||||||
|
for (const [guildId, nickname] of Object.entries(guildNicknames)) {
|
||||||
|
if (typeof nickname !== 'string' || nickname.length === 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (perGuild == null) {
|
||||||
|
perGuild = {};
|
||||||
|
}
|
||||||
|
perGuild[guildId] = toSearchForm(nickname);
|
||||||
|
}
|
||||||
|
return {user, named, perGuild};
|
||||||
|
}
|
||||||
|
|
||||||
|
function getSearchForms(entry: IndexedUser, filters?: SearchFilters): Array<SearchForm> {
|
||||||
|
const {named, perGuild} = entry;
|
||||||
|
if (perGuild == null) {
|
||||||
|
return named;
|
||||||
}
|
}
|
||||||
const guildId = filters == null ? null : filters.guild;
|
const guildId = filters == null ? null : filters.guild;
|
||||||
if (guildId != null && guildId.length > 0) {
|
if (guildId != null && guildId.length > 0) {
|
||||||
const nickname = guildNicknames[guildId];
|
const nickname = perGuild[guildId];
|
||||||
if (typeof nickname === 'string' && nickname.length > 0) {
|
return nickname == null ? named : [...named, nickname];
|
||||||
values.push(nickname);
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
}
|
}
|
||||||
for (const nickname of Object.values(guildNicknames)) {
|
return [...named, ...Object.values(perGuild)];
|
||||||
if (typeof nickname === 'string' && nickname.length > 0) {
|
|
||||||
values.push(nickname);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return values;
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeRegex(text: string): string {
|
|
||||||
return text.replace(/[-[\]/{}()*+?.\\^$|]/g, '\\$&');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function fuzzyMatch(needle: string, haystack: string): boolean {
|
function fuzzyMatch(needle: string, haystack: string): boolean {
|
||||||
@@ -121,10 +149,10 @@ function fuzzyMatch(needle: string, haystack: string): boolean {
|
|||||||
|
|
||||||
function sortByMatchScore(a: SearchResult, b: SearchResult): number {
|
function sortByMatchScore(a: SearchResult, b: SearchResult): number {
|
||||||
if (a.score === b.score) {
|
if (a.score === b.score) {
|
||||||
const aComp = a.comparator.toLowerCase();
|
const aLabel = a.rankLabel.toLowerCase();
|
||||||
const bComp = b.comparator.toLowerCase();
|
const bLabel = b.rankLabel.toLowerCase();
|
||||||
if (aComp < bComp) return -1;
|
if (aLabel < bLabel) return -1;
|
||||||
if (aComp > bComp) return 1;
|
if (aLabel > bLabel) return 1;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return b.score - a.score;
|
return b.score - a.score;
|
||||||
@@ -141,24 +169,24 @@ function normalizeSearchLimit(limit: number): number {
|
|||||||
return Math.min(normalizedLimit, MAX_SEARCH_RESULTS);
|
return Math.min(normalizedLimit, MAX_SEARCH_RESULTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertSearchResult(results: Array<SearchResult>, candidate: SearchResult, limit: number): void {
|
function insertSearchResult(shortlist: Array<SearchResult>, candidate: SearchResult, limit: number): void {
|
||||||
if (results.length === 0) {
|
if (shortlist.length === 0) {
|
||||||
results.push(candidate);
|
shortlist.push(candidate);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let insertIndex = results.length;
|
let insertIndex = shortlist.length;
|
||||||
for (let index = 0; index < results.length; index += 1) {
|
for (let index = 0; index < shortlist.length; index += 1) {
|
||||||
if (sortByMatchScore(candidate, results[index]) < 0) {
|
if (sortByMatchScore(candidate, shortlist[index]) < 0) {
|
||||||
insertIndex = index;
|
insertIndex = index;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (insertIndex === results.length && results.length >= limit) {
|
if (insertIndex === shortlist.length && shortlist.length >= limit) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
results.splice(insertIndex, 0, candidate);
|
shortlist.splice(insertIndex, 0, candidate);
|
||||||
if (results.length > limit) {
|
if (shortlist.length > limit) {
|
||||||
results.pop();
|
shortlist.pop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,26 +208,26 @@ function shouldIncludeUser(
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function calculateScore(baseScore: number, booster?: number): number {
|
function calculateScore(tierScore: number, booster?: number): number {
|
||||||
return baseScore * (booster ?? 1);
|
return tierScore * (booster ?? 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
function postSearchResults(uuid: string, results: Array<SearchResult>, generation: number): void {
|
function postSearchResults(uuid: string, shortlist: Array<SearchResult>, generation: number): void {
|
||||||
const payload = results.map((r) => ({
|
const payload = shortlist.map((hit) => ({
|
||||||
id: r.id,
|
id: hit.id,
|
||||||
username: r.username,
|
username: hit.username,
|
||||||
isBot: r.isBot,
|
isBot: hit.isBot,
|
||||||
}));
|
}));
|
||||||
const message: WorkerMessage<typeof payload> = {
|
const message: WorkerMessage<typeof payload> = {
|
||||||
uuid,
|
uuid,
|
||||||
type: MessageTypes.USER_RESULTS,
|
type: MessageTypes.MATCHES_READY,
|
||||||
payload,
|
payload,
|
||||||
generation,
|
generation,
|
||||||
};
|
};
|
||||||
postMessage(message);
|
postMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getEmptyQueryComparator(user: TransformedUser, filters?: SearchFilters): string {
|
function getFallbackLabel(user: TransformedUser, filters?: SearchFilters): string {
|
||||||
const guildNicknames = user.guildNicknames;
|
const guildNicknames = user.guildNicknames;
|
||||||
const guildId = filters == null ? null : filters.guild;
|
const guildId = filters == null ? null : filters.guild;
|
||||||
if (guildNicknames != null && guildId != null && guildId.length > 0) {
|
if (guildNicknames != null && guildId != null && guildId.length > 0) {
|
||||||
@@ -214,82 +242,108 @@ function getEmptyQueryComparator(user: TransformedUser, filters?: SearchFilters)
|
|||||||
return user.username;
|
return user.username;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function scoreSearchForm(form: SearchForm, loweredQuery: string, strippedQuery: string): number {
|
||||||
|
const needsStrippedPass = form.stripped !== form.lower || strippedQuery !== loweredQuery;
|
||||||
|
if (form.lower.startsWith(loweredQuery)) {
|
||||||
|
return SCORE_EXACT_PREFIX;
|
||||||
|
}
|
||||||
|
if (needsStrippedPass && form.stripped.startsWith(strippedQuery)) {
|
||||||
|
return SCORE_EXACT_PREFIX;
|
||||||
|
}
|
||||||
|
if (form.lower.includes(loweredQuery)) {
|
||||||
|
return SCORE_CONTAINS;
|
||||||
|
}
|
||||||
|
if (needsStrippedPass && form.stripped.includes(strippedQuery)) {
|
||||||
|
return SCORE_CONTAINS;
|
||||||
|
}
|
||||||
|
if (fuzzyMatch(loweredQuery, form.lower)) {
|
||||||
|
return SCORE_FUZZY;
|
||||||
|
}
|
||||||
|
if (needsStrippedPass && fuzzyMatch(strippedQuery, form.stripped)) {
|
||||||
|
return SCORE_FUZZY;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
function executeSearch(uuid: string, searchQuery: SearchQuery): void {
|
function executeSearch(uuid: string, searchQuery: SearchQuery): void {
|
||||||
const {query, limit, filters, blacklist, whitelist, boosters, generation = 0} = searchQuery;
|
const {query, limit, filters, blacklist, whitelist, boosters, generation = 0} = searchQuery;
|
||||||
const normalizedLimit = normalizeSearchLimit(limit);
|
const normalizedLimit = normalizeSearchLimit(limit);
|
||||||
const results: Array<SearchResult> = [];
|
const hits: Array<SearchResult> = [];
|
||||||
if (normalizedLimit === 0) {
|
if (normalizedLimit === 0) {
|
||||||
postSearchResults(uuid, results, generation);
|
postSearchResults(uuid, hits, generation);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (query === '') {
|
if (query === '') {
|
||||||
userIndex.forEach((user, userId) => {
|
userIndex.forEach((entry, userId) => {
|
||||||
|
const user = entry.user;
|
||||||
if (!shouldIncludeUser(userId, user, filters, blacklist, whitelist)) {
|
if (!shouldIncludeUser(userId, user, filters, blacklist, whitelist)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
insertSearchResult(
|
insertSearchResult(
|
||||||
results,
|
hits,
|
||||||
{
|
{
|
||||||
id: userId,
|
id: userId,
|
||||||
username: user.username,
|
username: user.username,
|
||||||
comparator: getEmptyQueryComparator(user, filters),
|
rankLabel: getFallbackLabel(user, filters),
|
||||||
score: 0,
|
score: 0,
|
||||||
isBot: user.isBot,
|
isBot: user.isBot,
|
||||||
},
|
},
|
||||||
normalizedLimit,
|
normalizedLimit,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
postSearchResults(uuid, results, generation);
|
postSearchResults(uuid, hits, generation);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const exactPrefixRegex = new RegExp(`^${escapeRegex(query)}`, 'i');
|
const loweredQuery = query.toLowerCase();
|
||||||
const containsRegex = new RegExp(escapeRegex(query), 'i');
|
const strippedQuery = stripCombiningMarks(loweredQuery);
|
||||||
const queryLower = query.toLowerCase();
|
userIndex.forEach((entry, userId) => {
|
||||||
userIndex.forEach((user, userId) => {
|
const user = entry.user;
|
||||||
if (!shouldIncludeUser(userId, user, filters, blacklist, whitelist)) {
|
if (!shouldIncludeUser(userId, user, filters, blacklist, whitelist)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const username = user.username;
|
const booster = boosters == null ? undefined : boosters[userId];
|
||||||
let bestMatch: SearchResult | null = null;
|
if (query === userId) {
|
||||||
for (const value of getSearchValues(user, filters)) {
|
insertSearchResult(
|
||||||
let matchResult: SearchResult | null = null;
|
hits,
|
||||||
if (exactPrefixRegex.test(value)) {
|
{
|
||||||
matchResult = {
|
|
||||||
id: userId,
|
id: userId,
|
||||||
username,
|
username: user.username,
|
||||||
comparator: value,
|
rankLabel: getFallbackLabel(user, filters),
|
||||||
score: calculateScore(SCORE_EXACT_PREFIX, boosters?.[userId]),
|
score: calculateScore(SCORE_EXACT_PREFIX, booster),
|
||||||
isBot: user.isBot,
|
isBot: user.isBot,
|
||||||
};
|
},
|
||||||
} else if (containsRegex.test(value)) {
|
normalizedLimit,
|
||||||
matchResult = {
|
);
|
||||||
id: userId,
|
return;
|
||||||
username,
|
}
|
||||||
comparator: value,
|
let bestScore = 0;
|
||||||
score: calculateScore(SCORE_CONTAINS, boosters?.[userId]),
|
let bestForm: SearchForm | null = null;
|
||||||
isBot: user.isBot,
|
for (const form of getSearchForms(entry, filters)) {
|
||||||
};
|
const score = scoreSearchForm(form, loweredQuery, strippedQuery);
|
||||||
} else if (fuzzyMatch(queryLower, value.toLowerCase())) {
|
if (score > bestScore) {
|
||||||
matchResult = {
|
bestScore = score;
|
||||||
id: userId,
|
bestForm = form;
|
||||||
username,
|
|
||||||
comparator: value,
|
|
||||||
score: calculateScore(SCORE_FUZZY, boosters?.[userId]),
|
|
||||||
isBot: user.isBot,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (matchResult && (!bestMatch || bestMatch.score < matchResult.score)) {
|
|
||||||
bestMatch = matchResult;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bestMatch) {
|
if (bestForm == null) {
|
||||||
insertSearchResult(results, bestMatch, normalizedLimit);
|
return;
|
||||||
}
|
}
|
||||||
|
insertSearchResult(
|
||||||
|
hits,
|
||||||
|
{
|
||||||
|
id: userId,
|
||||||
|
username: user.username,
|
||||||
|
rankLabel: bestForm.value,
|
||||||
|
score: calculateScore(bestScore, booster),
|
||||||
|
isBot: user.isBot,
|
||||||
|
},
|
||||||
|
normalizedLimit,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
postSearchResults(uuid, results, generation);
|
postSearchResults(uuid, hits, generation);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateUsers(users: Array<TransformedUser>): void {
|
function applyUserUpdates(users: Array<TransformedUser>): void {
|
||||||
let shouldTriggerSearch = false;
|
let shouldTriggerSearch = false;
|
||||||
const updatedGuilds = new Set<string>();
|
const updatedGuilds = new Set<string>();
|
||||||
for (const update of users) {
|
for (const update of users) {
|
||||||
@@ -299,11 +353,11 @@ function updateUsers(users: Array<TransformedUser>): void {
|
|||||||
shouldTriggerSearch = true;
|
shouldTriggerSearch = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const existingUser = userIndex.get(userId);
|
const existingEntry = userIndex.get(userId);
|
||||||
if (update._removeGuild && existingUser == null) {
|
if (update._removeGuild && existingEntry == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const baseUser: TransformedUser = existingUser == null ? {id: userId, username: ''} : existingUser;
|
const baseUser: TransformedUser = existingEntry == null ? {id: userId, username: ''} : existingEntry.user;
|
||||||
const mergedUser: TransformedUser = {...baseUser, ...update};
|
const mergedUser: TransformedUser = {...baseUser, ...update};
|
||||||
if (update._removeGuild && baseUser.username.length > 0) {
|
if (update._removeGuild && baseUser.username.length > 0) {
|
||||||
mergedUser.username = baseUser.username;
|
mergedUser.username = baseUser.username;
|
||||||
@@ -345,7 +399,7 @@ function updateUsers(users: Array<TransformedUser>): void {
|
|||||||
}
|
}
|
||||||
const wasFriend = Boolean(baseUser.isFriend);
|
const wasFriend = Boolean(baseUser.isFriend);
|
||||||
const isFriendNow = Boolean(mergedUser.isFriend);
|
const isFriendNow = Boolean(mergedUser.isFriend);
|
||||||
userIndex.set(userId, mergedUser);
|
userIndex.set(userId, indexUser(mergedUser));
|
||||||
if (activeQueries.size > 0) {
|
if (activeQueries.size > 0) {
|
||||||
if (isFriendNow || wasFriend !== isFriendNow) {
|
if (isFriendNow || wasFriend !== isFriendNow) {
|
||||||
shouldTriggerSearch = true;
|
shouldTriggerSearch = true;
|
||||||
@@ -374,12 +428,12 @@ function updateUsers(users: Array<TransformedUser>): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setQuery(uuid: string, query: SearchQuery): void {
|
function registerQuery(uuid: string, query: SearchQuery): void {
|
||||||
activeQueries.set(uuid, query);
|
activeQueries.set(uuid, query);
|
||||||
executeSearch(uuid, query);
|
executeSearch(uuid, query);
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearQuery(uuid: string): void {
|
function unregisterQuery(uuid: string): void {
|
||||||
activeQueries.delete(uuid);
|
activeQueries.delete(uuid);
|
||||||
pendingSearches.delete(uuid);
|
pendingSearches.delete(uuid);
|
||||||
}
|
}
|
||||||
@@ -409,21 +463,21 @@ addEventListener('message', (event: MessageEvent<WorkerMessage>) => {
|
|||||||
}
|
}
|
||||||
const {uuid, type, payload} = data;
|
const {uuid, type, payload} = data;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case MessageTypes.UPDATE_USERS: {
|
case MessageTypes.INGEST_DIRECTORY: {
|
||||||
const p = payload as UpdateUsersPayload | undefined;
|
const p = payload as UpdateUsersPayload | undefined;
|
||||||
if (p?.users) {
|
if (p?.users) {
|
||||||
updateUsers(p.users);
|
applyUserUpdates(p.users);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MessageTypes.QUERY_SET: {
|
case MessageTypes.SEARCH_BEGIN: {
|
||||||
if (!uuid) return;
|
if (!uuid) return;
|
||||||
setQuery(uuid, payload as SearchQuery);
|
registerQuery(uuid, payload as SearchQuery);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case MessageTypes.QUERY_CLEAR: {
|
case MessageTypes.SEARCH_CANCEL: {
|
||||||
if (!uuid) return;
|
if (!uuid) return;
|
||||||
clearQuery(uuid);
|
unregisterQuery(uuid);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ export function useTextareaAutocomplete({
|
|||||||
autocompleteTriggerType === 'commandArg';
|
autocompleteTriggerType === 'commandArg';
|
||||||
if (!isMentionTrigger || !channel?.guildId) {
|
if (!isMentionTrigger || !channel?.guildId) {
|
||||||
currentGuildIdRef.current = null;
|
currentGuildIdRef.current = null;
|
||||||
context.clearQuery();
|
context.cancelSearch();
|
||||||
setMemberSearchResults([]);
|
setMemberSearchResults([]);
|
||||||
setIsMemberSearchLoading(false);
|
setIsMemberSearchLoading(false);
|
||||||
if (memberFetchDebounceTimerRef.current) {
|
if (memberFetchDebounceTimerRef.current) {
|
||||||
@@ -309,7 +309,7 @@ export function useTextareaAutocomplete({
|
|||||||
}
|
}
|
||||||
setIsMemberSearchLoading(true);
|
setIsMemberSearchLoading(true);
|
||||||
const boosters = MentionFrecency.getBoosters(guildId);
|
const boosters = MentionFrecency.getBoosters(guildId);
|
||||||
context.setQuery(searchQuery, {guild: guildId}, new Set(), new Set(), boosters);
|
context.beginSearch(searchQuery, {guild: guildId}, new Set(), new Set(), boosters);
|
||||||
if (memberFetchDebounceTimerRef.current) {
|
if (memberFetchDebounceTimerRef.current) {
|
||||||
clearTimeout(memberFetchDebounceTimerRef.current);
|
clearTimeout(memberFetchDebounceTimerRef.current);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -328,7 +328,7 @@ class QuickSwitcher {
|
|||||||
const rawSearch = query['slice'](1).trim();
|
const rawSearch = query['slice'](1).trim();
|
||||||
if (rawSearch.length === 0) {
|
if (rawSearch.length === 0) {
|
||||||
if (this.memberSearchContext) {
|
if (this.memberSearchContext) {
|
||||||
this.memberSearchContext.clearQuery();
|
this.memberSearchContext.cancelSearch();
|
||||||
}
|
}
|
||||||
if (this.memberFetchDebounceTimer) {
|
if (this.memberFetchDebounceTimer) {
|
||||||
clearTimeout(this.memberFetchDebounceTimer);
|
clearTimeout(this.memberFetchDebounceTimer);
|
||||||
@@ -351,7 +351,7 @@ class QuickSwitcher {
|
|||||||
});
|
});
|
||||||
}, MEMBER_SEARCH_LIMIT);
|
}, MEMBER_SEARCH_LIMIT);
|
||||||
}
|
}
|
||||||
this.memberSearchContext.setQuery(rawSearch);
|
this.memberSearchContext.beginSearch(rawSearch);
|
||||||
if (this.memberFetchDebounceTimer) {
|
if (this.memberFetchDebounceTimer) {
|
||||||
clearTimeout(this.memberFetchDebounceTimer);
|
clearTimeout(this.memberFetchDebounceTimer);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user