Fixes custom emoji not appearing in autocomplete (#38854)

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
Echo 2026-04-29 21:44:42 +02:00 committed by GitHub
parent 5b395774c0
commit a47ed31047
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 101 additions and 82 deletions

View File

@ -8,6 +8,7 @@ import { browserHistory } from 'mastodon/components/router';
import { countableText } from 'mastodon/features/compose/util/counter';
import { search as emojiSearch } from 'mastodon/features/emoji/emoji_mart_search_light';
import { tagHistory } from 'mastodon/settings';
import { fetchCustomEmojiData } from '@/mastodon/features/emoji/picker';
import { showAlert, showAlertForError } from './alerts';
import { useEmoji } from './emojis';
@ -563,7 +564,7 @@ export function clearComposeSuggestions() {
};
}
const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => {
const fetchComposeSuggestionsAccounts = throttle((dispatch, token) => {
if (fetchComposeSuggestionsAccountsController) {
fetchComposeSuggestionsAccountsController.abort();
}
@ -590,12 +591,13 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) =>
});
}, 200, { leading: true, trailing: true });
const fetchComposeSuggestionsEmojis = (dispatch, getState, token) => {
const results = emojiSearch(token.replace(':', ''), { maxResults: 5 });
const fetchComposeSuggestionsEmojis = async (dispatch, token) => {
const custom = await fetchCustomEmojiData();
const results = emojiSearch(token.replace(':', ''), { maxResults: 5, custom });
dispatch(readyComposeSuggestionsEmojis(token, results));
};
const fetchComposeSuggestionsTags = throttle((dispatch, getState, token) => {
const fetchComposeSuggestionsTags = throttle((dispatch, token) => {
if (fetchComposeSuggestionsTagsController) {
fetchComposeSuggestionsTagsController.abort();
}
@ -629,14 +631,14 @@ export function fetchComposeSuggestions(token) {
return (dispatch, getState) => {
switch (token[0]) {
case ':':
fetchComposeSuggestionsEmojis(dispatch, getState, token);
void fetchComposeSuggestionsEmojis(dispatch, token);
break;
case '#':
case '':
fetchComposeSuggestionsTags(dispatch, getState, token);
fetchComposeSuggestionsTags(dispatch, token);
break;
default:
fetchComposeSuggestionsAccounts(dispatch, getState, token);
fetchComposeSuggestionsAccounts(dispatch, token);
break;
}
};

View File

@ -1,39 +1,19 @@
import type { FC } from 'react';
import { useEffect, useState } from 'react';
import type {
CategoryName,
CustomEmoji,
EmojiProps,
PickerProps,
} from 'emoji-mart';
import type { EmojiProps, PickerProps } from 'emoji-mart';
import EmojiRaw from 'emoji-mart/dist-es/components/emoji/nimble-emoji';
import PickerRaw from 'emoji-mart/dist-es/components/picker/nimble-picker';
import { autoPlayGif } from '@/mastodon/initial_state';
import { assetHost } from 'mastodon/utils/config';
import { assetHost } from '@/mastodon/utils/config';
import { EMOJI_MODE_NATIVE } from './constants';
import EmojiData from './emoji_data.json';
import { useEmojiAppState } from './mode';
import { emojiLogger } from './utils';
import { usePickerEmojis } from './picker';
const backgroundImageFnDefault = () => `${assetHost}/emoji/sheet_16_0.png`;
let customEmojis: CustomEmoji[] | null = null;
let customCategories = [
'recent',
'people',
'nature',
'foods',
'activity',
'places',
'objects',
'symbols',
'flags',
] as CategoryName[];
const log = emojiLogger('picker');
export { fetchCustomEmojiData as loadCustomEmojiData } from './picker';
export const Picker: FC<PickerProps> = ({
set = 'twitter',
@ -44,24 +24,16 @@ export const Picker: FC<PickerProps> = ({
...props
}) => {
const { mode } = useEmojiAppState();
const [isLoaded, setLoaded] = useState(customEmojis !== null);
const { customCategories, customEmojis } = usePickerEmojis();
useEffect(() => {
if (customEmojis === null) {
void loadCustomEmojiData().then(() => {
setLoaded(true);
});
}
}, []);
if (!isLoaded) {
if (!customEmojis) {
return null;
}
return (
<PickerRaw
data={EmojiData}
custom={customEmojis ?? []}
custom={customEmojis}
include={customCategories}
set={set}
sheetSize={sheetSize}
@ -74,46 +46,6 @@ export const Picker: FC<PickerProps> = ({
);
};
export async function loadCustomEmojiData() {
const { loadAllCustomEmoji } = await import('./database');
const emojisRaw = await loadAllCustomEmoji();
if (emojisRaw.length === 0) {
return;
}
const categories = new Set(['custom']);
const emojis = [];
for (const emoji of emojisRaw) {
const name = emoji.shortcode.replaceAll(':', '');
emojis.push({
name,
id: name,
custom: true,
short_names: [name],
imageUrl: autoPlayGif ? emoji.url : emoji.static_url,
customCategory: emoji.category,
});
if (emoji.category) {
categories.add(`custom-${emoji.category}`);
}
}
customEmojis = emojis.toSorted((a, b) => {
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
});
customCategories = customCategories.toSpliced(
1,
0,
...(Array.from(categories).toSorted() as CategoryName[]),
);
log(
'loaded %d custom emojis in %d categories',
customEmojis.length,
categories.size,
);
}
export const Emoji: FC<EmojiProps> = ({
set = 'twitter',
sheetSize = 32,

View File

@ -0,0 +1,85 @@
import { useEffect, useState } from 'react';
import type { CategoryName, CustomEmoji } from 'emoji-mart';
import { autoPlayGif } from '@/mastodon/initial_state';
import { emojiLogger } from './utils';
const log = emojiLogger('picker');
let customEmojis: CustomEmoji[] | null = null;
let customCategories = [
'recent',
'people',
'nature',
'foods',
'activity',
'places',
'objects',
'symbols',
'flags',
] as CategoryName[];
export async function fetchCustomEmojiData() {
if (customEmojis !== null) {
return customEmojis;
}
const { loadAllCustomEmoji } = await import('./database');
const emojisRaw = await loadAllCustomEmoji();
if (emojisRaw.length === 0) {
return [];
}
const categories = new Set(['custom']);
const emojis = [];
for (const emoji of emojisRaw) {
const name = emoji.shortcode.replaceAll(':', '');
emojis.push({
name,
id: name,
custom: true,
short_names: [name],
imageUrl: autoPlayGif ? emoji.url : emoji.static_url,
customCategory: emoji.category,
});
if (emoji.category) {
categories.add(`custom-${emoji.category}`);
}
}
customEmojis = emojis.toSorted((a, b) => {
return a.name.toLowerCase().localeCompare(b.name.toLowerCase());
});
customCategories = customCategories.toSpliced(
1,
0,
...(Array.from(categories).toSorted() as CategoryName[]),
);
log(
'loaded %d custom emojis in %d categories',
customEmojis.length,
categories.size,
);
return customEmojis;
}
export function usePickerEmojis() {
const [, setLoaded] = useState(customEmojis !== null);
useEffect(() => {
if (customEmojis === null) {
void fetchCustomEmojiData().then(() => {
setLoaded(true);
});
}
}, []);
return {
customEmojis,
customCategories,
};
}