Cancel emoji search requests (#39947)

This commit is contained in:
Echo 2026-07-28 10:35:21 +02:00 committed by Tarrien
parent 5d7e3bbd17
commit ced453a5fe
4 changed files with 81 additions and 36 deletions

View File

@ -19,6 +19,8 @@ import { updateTimeline } from './timelines';
let fetchComposeSuggestionsAccountsController;
/** @type {AbortController | undefined} */
let fetchComposeSuggestionsTagsController;
/** @type {AbortController | undefined} */
let searchComposeSuggestionsEmojiController;
export const COMPOSE_CHANGE = 'COMPOSE_CHANGE';
export const COMPOSE_SUBMIT_REQUEST = 'COMPOSE_SUBMIT_REQUEST';
@ -499,9 +501,8 @@ export function undoUploadCompose(media_id) {
}
export function clearComposeSuggestions() {
if (fetchComposeSuggestionsAccountsController) {
fetchComposeSuggestionsAccountsController.abort();
}
fetchComposeSuggestionsAccountsController?.abort();
searchComposeSuggestionsEmojiController?.abort();
return {
type: COMPOSE_SUGGESTIONS_CLEAR,
};
@ -534,12 +535,25 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, token) => {
});
}, 200, { leading: true, trailing: true });
const fetchComposeSuggestionsEmojis = async (dispatch, token) => {
// Right now we are hard-coding the locale to English since the picker search only supports English.
// Once we replace the legacy picker we can remove this and use the actual locale of the user.
const results = await emojiMartSearch(token, 'en', 5);
dispatch(readyComposeSuggestionsEmojis(token, results));
};
const fetchComposeSuggestionsEmojis = (dispatch, token) => {
dispatch(clearComposeSuggestions());
searchComposeSuggestionsEmojiController = new AbortController();
void emojiMartSearch({
token,
// Right now we are hard-coding the locale to English since the picker search only supports English.
// Once we replace the legacy picker we can remove this and use the actual locale of the user.
locale: 'en',
limit: 5,
signal: searchComposeSuggestionsEmojiController.signal,
}).then((results) => {
if (results) {
dispatch(readyComposeSuggestionsEmojis(token, results));
}
}).finally(() => {
searchComposeSuggestionsEmojiController = undefined;
});
}
const fetchComposeSuggestionsTags = throttle((dispatch, token) => {
if (fetchComposeSuggestionsTagsController) {

View File

@ -22,34 +22,50 @@ type LegacyEmoji =
};
// Replicates the old legacy search function.
export async function emojiMartSearch(
token: string,
locale: string,
export async function emojiMartSearch({
token,
locale,
limit = 5,
): Promise<LegacyEmoji[]> {
const query = token.replace(':', '').trim();
if (!query.length) {
return [];
signal,
}: {
token: string;
locale: string;
limit?: number;
signal?: AbortSignal;
}): Promise<LegacyEmoji[] | null> {
try {
const query = token.replace(':', '').trim();
if (!query.length) {
return [];
}
const cacheKey = `${query}|${locale}|${limit}`;
const cachedResult = searchCache.get(cacheKey);
if (cachedResult) {
return cachedResult;
}
const results = await search({
query,
locale,
limit,
signal,
});
const legacyResults = results.map((emoji) =>
'shortcode' in emoji
? ({ id: emoji.shortcode, custom: true } as const)
: {
id: emoji.label.replaceAll(' ', '_').toLowerCase(),
native: emoji.unicode,
},
);
searchCache.set(cacheKey, legacyResults);
return legacyResults;
} catch {
log('aborted search for "%s"', token);
return null;
}
const cacheKey = `${query}|${locale}|${limit}`;
const cachedResult = searchCache.get(cacheKey);
if (cachedResult) {
return cachedResult;
}
const results = await search({ query, locale, limit });
const legacyResults = results.map((emoji) =>
'shortcode' in emoji
? ({ id: emoji.shortcode, custom: true } as const)
: {
id: emoji.label.replaceAll(' ', '_').toLowerCase(),
native: emoji.unicode,
},
);
searchCache.set(cacheKey, legacyResults);
return legacyResults;
}
const defaultCategories = [

View File

@ -56,13 +56,18 @@ export async function search({
query: rawQuery,
locale: localeString,
limit = 0,
signal,
}: {
query: string;
locale: string;
limit?: number;
signal?: AbortSignal;
}) {
log('searching for "%s"', rawQuery);
performance.mark('emoji-search-start');
signal?.throwIfAborted();
// Get the locale, and extract tokens from the query.
const locale = toSupportedLocale(localeString);
const segmenter = localeToSegmenter(locale);
@ -93,6 +98,7 @@ export async function search({
locale,
i === queryTokens.length - 1,
);
signal?.throwIfAborted();
const resultMap: ScoreMap = new Map();
const checkedSet = new Set<string>();
@ -121,6 +127,7 @@ export async function search({
}
// Score based on legacy shortcodes, using the higher score if there's a match.
signal?.throwIfAborted();
for (const shortcodeResult of shortcodeResults) {
const emoji =
resultMap.get(shortcodeResult.hexcode) ??
@ -183,7 +190,9 @@ export async function search({
// If there are no results, try a cursor-based custom emoji search instead.
if (mixedResults.length === 0 || mixedResults.length < limit) {
signal?.throwIfAborted();
const customEmojisFound = await fullCustomSearch(query, allEmojiIds);
signal?.throwIfAborted();
if (customEmojisFound.length > 0) {
log(
'cursor search found %d results for "%s"',
@ -373,6 +382,8 @@ async function fullCustomSearch(query: string, existing = new Set<string>()) {
// First iterate over chunks of 1,000 custom emoji keys and find any matches.
const chunkSize = 1_000;
const maxIterations = 10;
let index = 0;
let lastKey: string | null = null;
let keys: string[] = [];
do {
@ -389,6 +400,10 @@ async function fullCustomSearch(query: string, existing = new Set<string>()) {
foundEmojis.add(key);
}
}
index++;
if (index >= maxIterations) {
break;
}
} while (keys.length === chunkSize);
// Next get the full emojis for all matches.

View File

@ -518,7 +518,7 @@ body > [data-popper-placement] {
}
.autosuggest-account .account__avatar,
.autosuggest-emoji img {
.autosuggest-emoji .emojione {
display: block;
width: 24px;
height: 24px;