Fetch custom_emojis with a hook

This commit is contained in:
Alex Gleason 2024-11-17 15:17:38 -06:00
parent eebdb46b7d
commit 62ca4c7a2a
No known key found for this signature in database
GPG Key ID: 7211D1F99744FBB7
19 changed files with 123 additions and 201 deletions

View File

@ -24,7 +24,7 @@ import { createStatus } from './statuses.ts';
import type { EditorState } from 'lexical';
import type { AutoSuggestion } from 'soapbox/components/autosuggest-input.tsx';
import type { Emoji } from 'soapbox/features/emoji/index.ts';
import type { Account, Group } from 'soapbox/schemas/index.ts';
import type { Account, CustomEmoji, Group } from 'soapbox/schemas/index.ts';
import type { AppDispatch, RootState } from 'soapbox/store.ts';
import type { APIEntity, Status, Tag } from 'soapbox/types/entities.ts';
import type { History } from 'soapbox/types/history.ts';
@ -512,9 +512,8 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, composeId,
});
}, 200, { leading: true, trailing: true });
const fetchComposeSuggestionsEmojis = (dispatch: AppDispatch, getState: () => RootState, composeId: string, token: string) => {
const state = getState();
const results = emojiSearch(token.replace(':', ''), { maxResults: 10 }, state.custom_emojis);
const fetchComposeSuggestionsEmojis = (dispatch: AppDispatch, composeId: string, token: string, customEmojis: CustomEmoji[]) => {
const results = emojiSearch(token.replace(':', ''), { maxResults: 10 }, customEmojis);
dispatch(readyComposeSuggestionsEmojis(composeId, token, results));
};
@ -553,11 +552,11 @@ const fetchComposeSuggestionsTags = (dispatch: AppDispatch, getState: () => Root
});
};
const fetchComposeSuggestions = (composeId: string, token: string) =>
const fetchComposeSuggestions = (composeId: string, token: string, customEmojis: CustomEmoji[]) =>
(dispatch: AppDispatch, getState: () => RootState) => {
switch (token[0]) {
case ':':
fetchComposeSuggestionsEmojis(dispatch, getState, composeId, token);
fetchComposeSuggestionsEmojis(dispatch, composeId, token, customEmojis);
break;
case '#':
fetchComposeSuggestionsTags(dispatch, getState, composeId, token);

View File

@ -1,49 +0,0 @@
import api from '../api/index.ts';
import type { AppDispatch, RootState } from 'soapbox/store.ts';
import type { APIEntity } from 'soapbox/types/entities.ts';
const CUSTOM_EMOJIS_FETCH_REQUEST = 'CUSTOM_EMOJIS_FETCH_REQUEST';
const CUSTOM_EMOJIS_FETCH_SUCCESS = 'CUSTOM_EMOJIS_FETCH_SUCCESS';
const CUSTOM_EMOJIS_FETCH_FAIL = 'CUSTOM_EMOJIS_FETCH_FAIL';
const fetchCustomEmojis = () =>
(dispatch: AppDispatch, getState: () => RootState) => {
const me = getState().me;
if (!me) return;
dispatch(fetchCustomEmojisRequest());
api(getState).get('/api/v1/custom_emojis').then(response => {
dispatch(fetchCustomEmojisSuccess(response.data));
}).catch(error => {
dispatch(fetchCustomEmojisFail(error));
});
};
const fetchCustomEmojisRequest = () => ({
type: CUSTOM_EMOJIS_FETCH_REQUEST,
skipLoading: true,
});
const fetchCustomEmojisSuccess = (custom_emojis: APIEntity[]) => ({
type: CUSTOM_EMOJIS_FETCH_SUCCESS,
custom_emojis,
skipLoading: true,
});
const fetchCustomEmojisFail = (error: unknown) => ({
type: CUSTOM_EMOJIS_FETCH_FAIL,
error,
skipLoading: true,
});
export {
CUSTOM_EMOJIS_FETCH_REQUEST,
CUSTOM_EMOJIS_FETCH_SUCCESS,
CUSTOM_EMOJIS_FETCH_FAIL,
fetchCustomEmojis,
fetchCustomEmojisRequest,
fetchCustomEmojisSuccess,
fetchCustomEmojisFail,
};

View File

@ -0,0 +1,29 @@
import { useQuery } from '@tanstack/react-query';
import { autosuggestPopulate } from 'soapbox/features/emoji/search.ts';
import { useApi } from 'soapbox/hooks/useApi.ts';
import { CustomEmoji, customEmojiSchema } from 'soapbox/schemas/custom-emoji.ts';
import { filteredArray } from 'soapbox/schemas/utils.ts';
/** Get the Instance for the current backend. */
export function useCustomEmojis() {
const api = useApi();
const { data: customEmojis = [], ...rest } = useQuery<CustomEmoji[]>({
queryKey: ['customEmojis', api.baseUrl],
queryFn: async () => {
const response = await api.get('/api/v1/custom_emojis');
const data = await response.json();
const customEmojis = filteredArray(customEmojiSchema).parse(data);
// Add custom emojis to the search index.
autosuggestPopulate(customEmojis);
return customEmojis;
},
placeholderData: [],
retryOnMount: false,
});
return { customEmojis, ...rest };
}

View File

@ -8,15 +8,13 @@ import { getTextDirection } from 'soapbox/utils/rtl.ts';
import AnnouncementContent from './announcement-content.tsx';
import ReactionsBar from './reactions-bar.tsx';
import type { Map as ImmutableMap } from 'immutable';
import type { Announcement as AnnouncementEntity } from 'soapbox/schemas/index.ts';
interface IAnnouncement {
announcement: AnnouncementEntity;
emojiMap: ImmutableMap<string, ImmutableMap<string, string>>;
}
const Announcement: React.FC<IAnnouncement> = ({ announcement, emojiMap }) => {
const Announcement: React.FC<IAnnouncement> = ({ announcement }) => {
const features = useFeatures();
const startsAt = announcement.starts_at && new Date(announcement.starts_at);
@ -64,7 +62,6 @@ const Announcement: React.FC<IAnnouncement> = ({ announcement, emojiMap }) => {
<ReactionsBar
reactions={announcement.reactions}
announcementId={announcement.id}
emojiMap={emojiMap}
/>
)}
</Stack>

View File

@ -1,26 +1,17 @@
import clsx from 'clsx';
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import { useState } from 'react';
import { FormattedMessage } from 'react-intl';
import ReactSwipeableViews from 'react-swipeable-views';
import { createSelector } from 'reselect';
import { useAnnouncements } from 'soapbox/api/hooks/announcements/index.ts';
import { Card } from 'soapbox/components/ui/card.tsx';
import HStack from 'soapbox/components/ui/hstack.tsx';
import Widget from 'soapbox/components/ui/widget.tsx';
import { useAppSelector } from 'soapbox/hooks/useAppSelector.ts';
import Announcement from './announcement.tsx';
import type { RootState } from 'soapbox/store.ts';
const customEmojiMap = createSelector([(state: RootState) => state.custom_emojis], items => (items as ImmutableList<ImmutableMap<string, string>>).reduce((map, emoji) => map.set(emoji.get('shortcode')!, emoji), ImmutableMap<string, ImmutableMap<string, string>>()));
const AnnouncementsPanel = () => {
const emojiMap = useAppSelector(state => customEmojiMap(state));
const [index, setIndex] = useState(0);
const { data: announcements } = useAnnouncements();
if (!announcements || announcements.length === 0) return null;
@ -37,7 +28,6 @@ const AnnouncementsPanel = () => {
<Announcement
key={announcement.id}
announcement={announcement}
emojiMap={emojiMap}
/>
)).reverse()}
</ReactSwipeableViews>

View File

@ -1,34 +1,20 @@
import unicodeMapping from 'soapbox/features/emoji/mapping.ts';
import { useCustomEmojis } from 'soapbox/api/hooks/useCustomEmojis.ts';
import NativeEmoji from 'soapbox/components/ui/emoji.tsx';
import { useSettings } from 'soapbox/hooks/useSettings.ts';
import type { Map as ImmutableMap } from 'immutable';
interface IEmoji {
emoji: string;
emojiMap: ImmutableMap<string, ImmutableMap<string, string>>;
hovered: boolean;
}
const Emoji: React.FC<IEmoji> = ({ emoji, emojiMap, hovered }) => {
const Emoji: React.FC<IEmoji> = ({ emoji, hovered }) => {
const { autoPlayGif } = useSettings();
const { customEmojis } = useCustomEmojis();
// @ts-ignore
if (unicodeMapping[emoji]) {
// @ts-ignore
const { filename, shortCode } = unicodeMapping[emoji];
const title = shortCode ? `:${shortCode}:` : '';
const custom = customEmojis.find((x) => x.shortcode === emoji);
return (
<img
draggable='false'
className='emojione m-0 block'
alt={emoji}
title={title}
src={`/packs/emoji/${filename}.svg`}
/>
);
} else if (emojiMap.get(emoji as any)) {
const filename = (autoPlayGif || hovered) ? emojiMap.getIn([emoji, 'url']) : emojiMap.getIn([emoji, 'static_url']);
if (custom) {
const filename = (autoPlayGif || hovered) ? custom.url : custom.static_url;
const shortCode = `:${emoji}:`;
return (
@ -40,9 +26,9 @@ const Emoji: React.FC<IEmoji> = ({ emoji, emojiMap, hovered }) => {
src={filename as string}
/>
);
} else {
return null;
}
return <NativeEmoji emoji={emoji} />;
};
export default Emoji;

View File

@ -7,17 +7,15 @@ import unicodeMapping from 'soapbox/features/emoji/mapping.ts';
import Emoji from './emoji.tsx';
import type { Map as ImmutableMap } from 'immutable';
import type { AnnouncementReaction } from 'soapbox/schemas/index.ts';
interface IReaction {
announcementId: string;
reaction: AnnouncementReaction;
emojiMap: ImmutableMap<string, ImmutableMap<string, string>>;
style: React.CSSProperties;
}
const Reaction: React.FC<IReaction> = ({ announcementId, reaction, emojiMap, style }) => {
const Reaction: React.FC<IReaction> = ({ announcementId, reaction, style }) => {
const [hovered, setHovered] = useState(false);
const { addReaction, removeReaction } = useAnnouncements();
@ -55,7 +53,7 @@ const Reaction: React.FC<IReaction> = ({ announcementId, reaction, emojiMap, sty
style={style}
>
<span className='block size-4'>
<Emoji hovered={hovered} emoji={reaction.name} emojiMap={emojiMap} />
<Emoji hovered={hovered} emoji={reaction.name} />
</span>
<span className='block min-w-[9px] text-center text-xs font-medium text-primary-600 dark:text-white'>
<AnimatedNumber value={reaction.count} />

View File

@ -7,17 +7,15 @@ import { useSettings } from 'soapbox/hooks/useSettings.ts';
import Reaction from './reaction.tsx';
import type { Map as ImmutableMap } from 'immutable';
import type { Emoji, NativeEmoji } from 'soapbox/features/emoji/index.ts';
import type { AnnouncementReaction } from 'soapbox/schemas/index.ts';
interface IReactionsBar {
announcementId: string;
reactions: AnnouncementReaction[];
emojiMap: ImmutableMap<string, ImmutableMap<string, string>>;
}
const ReactionsBar: React.FC<IReactionsBar> = ({ announcementId, reactions, emojiMap }) => {
const ReactionsBar: React.FC<IReactionsBar> = ({ announcementId, reactions }) => {
const { reduceMotion } = useSettings();
const { addReaction } = useAnnouncements();
@ -47,7 +45,6 @@ const ReactionsBar: React.FC<IReactionsBar> = ({ announcementId, reactions, emoj
reaction={data}
style={{ transform: `scale(${style.scale})`, position: style.scale < 0.5 ? 'absolute' : 'static' }}
announcementId={announcementId}
emojiMap={emojiMap}
/>
))}

View File

@ -15,6 +15,7 @@ import {
selectComposeSuggestion,
uploadCompose,
} from 'soapbox/actions/compose.ts';
import { useCustomEmojis } from 'soapbox/api/hooks/useCustomEmojis.ts';
import AutosuggestInput, { AutoSuggestion } from 'soapbox/components/autosuggest-input.tsx';
import Button from 'soapbox/components/ui/button.tsx';
import HStack from 'soapbox/components/ui/hstack.tsx';
@ -109,6 +110,7 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
const spoilerTextRef = useRef<AutosuggestInput>(null);
const editorRef = useRef<LexicalEditor>(null);
const { isDraggedOver } = useDraggedFiles(formRef);
const { customEmojis } = useCustomEmojis();
const text = editorRef.current?.getEditorState().read(() => $getRoot().getTextContent()) ?? '';
const fulltext = [spoilerText, countableText(text)].join('');
@ -161,8 +163,8 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
dispatch(clearComposeSuggestions(id));
};
const onSuggestionsFetchRequested = (token: string | number) => {
dispatch(fetchComposeSuggestions(id, token as string));
const onSuggestionsFetchRequested = (token: string) => {
dispatch(fetchComposeSuggestions(id, token, customEmojis));
};
const onSpoilerSuggestionSelected = (tokenStart: number, token: string | null, value: AutoSuggestion) => {

View File

@ -1,6 +1,7 @@
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
import { addPollOption, changePollOption, changePollSettings, clearComposeSuggestions, fetchComposeSuggestions, removePoll, removePollOption, selectComposeSuggestion } from 'soapbox/actions/compose.ts';
import { useCustomEmojis } from 'soapbox/api/hooks/useCustomEmojis.ts';
import AutosuggestInput from 'soapbox/components/autosuggest-input.tsx';
import Button from 'soapbox/components/ui/button.tsx';
import Divider from 'soapbox/components/ui/divider.tsx';
@ -55,6 +56,7 @@ const Option: React.FC<IOption> = ({
const intl = useIntl();
const suggestions = useCompose(composeId).suggestions;
const { customEmojis } = useCustomEmojis();
const handleOptionTitleChange = (event: React.ChangeEvent<HTMLInputElement>) => onChange(index, event.target.value);
@ -68,7 +70,7 @@ const Option: React.FC<IOption> = ({
const onSuggestionsClearRequested = () => dispatch(clearComposeSuggestions(composeId));
const onSuggestionsFetchRequested = (token: string) => dispatch(fetchComposeSuggestions(composeId, token));
const onSuggestionsFetchRequested = (token: string) => dispatch(fetchComposeSuggestions(composeId, token, customEmojis));
const onSuggestionSelected = (tokenStart: number, token: string | null, value: AutoSuggestion) => {
if (token && typeof token === 'string') {

View File

@ -35,6 +35,7 @@ import ReactDOM from 'react-dom';
import { clearComposeSuggestions, fetchComposeSuggestions } from 'soapbox/actions/compose.ts';
import { chooseEmoji } from 'soapbox/actions/emojis.ts';
import { useCustomEmojis } from 'soapbox/api/hooks/useCustomEmojis.ts';
import AutosuggestEmoji from 'soapbox/components/autosuggest-emoji.tsx';
import { isNativeEmoji } from 'soapbox/features/emoji/index.ts';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
@ -278,6 +279,7 @@ const AutosuggestPlugin = ({
setSuggestionsHidden,
}: AutosuggestPluginProps): JSX.Element | null => {
const { suggestions } = useCompose(composeId);
const { customEmojis } = useCustomEmojis();
const dispatch = useAppDispatch();
const [editor] = useLexicalComposerContext();
@ -410,7 +412,7 @@ const AutosuggestPlugin = ({
return;
}
dispatch(fetchComposeSuggestions(composeId, match.matchingString.trim()));
dispatch(fetchComposeSuggestions(composeId, match.matchingString.trim(), customEmojis));
if (!isSelectionOnEntityBoundary(editor, match.leadOffset)) {
const isRangePositioned = tryToPositionRange(match.leadOffset, range);

View File

@ -5,6 +5,7 @@ import { createSelector } from 'reselect';
import { chooseEmoji } from 'soapbox/actions/emojis.ts';
import { changeSetting } from 'soapbox/actions/settings.ts';
import { useCustomEmojis } from 'soapbox/api/hooks/useCustomEmojis.ts';
import { buildCustomEmojis } from 'soapbox/features/emoji/index.ts';
import { EmojiPicker } from 'soapbox/features/ui/util/async-components.ts';
import { useAppDispatch } from 'soapbox/hooks/useAppDispatch.ts';
@ -13,6 +14,7 @@ import { useTheme } from 'soapbox/hooks/useTheme.ts';
import { RootState } from 'soapbox/store.ts';
import type { Emoji, CustomEmoji, NativeEmoji } from 'soapbox/features/emoji/index.ts';
import type { CustomEmoji as MastodonCustomEmoji } from 'soapbox/schemas/custom-emoji.ts';
export const messages = defineMessages({
emoji: { id: 'emoji_button.label', defaultMessage: 'Insert emoji' },
@ -90,20 +92,21 @@ export const getFrequentlyUsedEmojis = createSelector([
return emojis;
});
const getCustomEmojis = createSelector([
(state: RootState) => state.custom_emojis,
], emojis => emojis.filter(e => e.get('visible_in_picker')).sort((a, b) => {
const aShort = a.get('shortcode')!.toLowerCase();
const bShort = b.get('shortcode')!.toLowerCase();
/** Filter custom emojis to only ones visible in the picker, and sort them alphabetically. */
function filterCustomEmojis(customEmojis: MastodonCustomEmoji[]) {
return customEmojis.filter(e => e.visible_in_picker).sort((a, b) => {
const aShort = a.shortcode.toLowerCase();
const bShort = b.shortcode.toLowerCase();
if (aShort < bShort) {
return -1;
} else if (aShort > bShort) {
return 1;
} else {
return 0;
}
}));
if (aShort < bShort) {
return -1;
} else if (aShort > bShort) {
return 1;
} else {
return 0;
}
});
}
interface IRenderAfter {
children: React.ReactNode;
@ -137,7 +140,7 @@ const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({
const title = intl.formatMessage(messages.emoji);
const theme = useTheme();
const customEmojis = useAppSelector((state) => getCustomEmojis(state));
const { customEmojis } = useCustomEmojis();
const frequentlyUsedEmojis = useAppSelector((state) => getFrequentlyUsedEmojis(state));
const handlePick = (emoji: any) => {
@ -226,7 +229,7 @@ const EmojiPickerDropdown: React.FC<IEmojiPickerDropdown> = ({
<Suspense>
<RenderAfter update={update ?? (() => {})}>
<EmojiPicker
custom={withCustom ? [{ emojis: buildCustomEmojis(customEmojis) }] : undefined}
custom={withCustom ? [{ emojis: buildCustomEmojis(filterCustomEmojis(customEmojis)) }] : undefined}
title={title}
onEmojiSelect={handlePick}
recent={frequentlyUsedEmojis}

View File

@ -3,6 +3,7 @@ import split from 'graphemesplit';
import unicodeMapping from './mapping.ts';
import type { Emoji as EmojiMart, CustomEmoji as EmojiMartCustom } from 'soapbox/features/emoji/data.ts';
import type { CustomEmoji as MastodonCustomEmoji } from 'soapbox/schemas/custom-emoji.ts';
/*
* TODO: Consolate emoji object types
@ -206,13 +207,13 @@ const emojify = (str: string, customEmojis = {}) => {
export default emojify;
export const buildCustomEmojis = (customEmojis: any) => {
export function buildCustomEmojis(customEmojis: MastodonCustomEmoji[]): EmojiMart<EmojiMartCustom>[] {
const emojis: EmojiMart<EmojiMartCustom>[] = [];
customEmojis.forEach((emoji: any) => {
const shortcode = emoji.get('shortcode');
const url = emoji.get('static_url');
const name = shortcode.replace(':', '');
customEmojis.forEach((emoji) => {
const shortcode = emoji.shortcode;
const url = emoji.url;
const name = shortcode.replace(':', '');
emojis.push({
id: name,
@ -223,4 +224,4 @@ export const buildCustomEmojis = (customEmojis: any) => {
});
return emojis;
};
}

View File

@ -1,4 +1,3 @@
import { List, Map } from 'immutable';
import pick from 'lodash/pick';
import { describe, expect, it } from 'vitest';
@ -35,13 +34,19 @@ describe('emoji_index', () => {
id: 'mastodon',
name: 'mastodon',
keywords: ['mastodon'],
skins: { src: 'http://example.com' },
skins: [{ src: 'http://example.com' }],
},
];
const custom_emojis = List([
Map({ static_url: 'http://example.com', shortcode: 'mastodon' }),
]);
const customEmojis = [
{
category: '',
url: 'http://example.com/mastodon.png',
static_url: 'http://example.com/mastodon.png',
shortcode: 'mastodon',
visible_in_picker: true,
},
];
const lightExpected = [
{
@ -51,7 +56,7 @@ describe('emoji_index', () => {
];
addCustomToPool(custom);
expect(search('masto', {}, custom_emojis).map(trimEmojis)).toEqual(lightExpected);
expect(search('masto', {}, customEmojis).map(trimEmojis)).toEqual(lightExpected);
});
it('updates custom emoji if another is passed', () => {
@ -60,7 +65,7 @@ describe('emoji_index', () => {
id: 'mastodon',
name: 'mastodon',
keywords: ['mastodon'],
skins: { src: 'http://example.com' },
skins: [{ src: 'http://example.com' }],
},
];
@ -71,18 +76,24 @@ describe('emoji_index', () => {
id: 'pleroma',
name: 'pleroma',
keywords: ['pleroma'],
skins: { src: 'http://example.com' },
skins: [{ src: 'http://example.com' }],
},
];
addCustomToPool(custom2);
const custom_emojis = List([
Map({ static_url: 'http://example.com', shortcode: 'pleroma' }),
]);
const customEmojis = [
{
category: '',
url: 'http://example.com/pleroma.png',
static_url: 'http://example.com/pleroma.png',
shortcode: 'pleroma',
visible_in_picker: true,
},
];
const expected: any = [];
expect(search('masto', {}, custom_emojis).map(trimEmojis)).toEqual(expected);
expect(search('masto', {}, customEmojis).map(trimEmojis)).toEqual(expected);
});
it('does an emoji whose unified name is irregular', () => {

View File

@ -1,10 +1,10 @@
// @ts-ignore
import Index from '@akryum/flexsearch-es';
import { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import data from './data.ts';
import data, { Emoji as EmojiMart, CustomEmoji as EmojiMartCustom } from 'soapbox/features/emoji/data.ts';
import { CustomEmoji } from 'soapbox/schemas/custom-emoji.ts';
import type { Emoji } from './index.ts';
import { buildCustomEmojis, type Emoji } from './index.ts';
// @ts-ignore Wrong default export.
const index: Index.Index = new Index({
@ -23,8 +23,7 @@ export interface searchOptions {
custom?: any;
}
export const addCustomToPool = (customEmojis: any[]) => {
// @ts-ignore
export function addCustomToPool(customEmojis: EmojiMart<EmojiMartCustom>[]): void {
for (const key in index.register) {
if (key[0] === 'c') {
index.remove(key); // remove old custom emojis
@ -36,27 +35,27 @@ export const addCustomToPool = (customEmojis: any[]) => {
for (const emoji of customEmojis) {
index.add('c' + i++, emoji.id);
}
};
}
// we can share an index by prefixing custom emojis with 'c' and native with 'n'
const search = (
str: string, { maxResults = 5 }: searchOptions = {},
custom_emojis?: ImmutableList<ImmutableMap<string, string>>,
customEmojis?: CustomEmoji[],
): Emoji[] => {
return index.search(str, maxResults)
.flatMap((id: any) => {
if (typeof id !== 'string') return;
if (id[0] === 'c' && custom_emojis) {
if (id[0] === 'c' && customEmojis) {
const index = Number(id.slice(1));
const custom = custom_emojis.get(index);
const custom = customEmojis[index];
if (custom) {
return {
id: custom.get('shortcode', ''),
colons: ':' + custom.get('shortcode', '') + ':',
id: custom.shortcode,
colons: ':' + custom.shortcode + ':',
custom: true,
imageUrl: custom.get('static_url', ''),
imageUrl: custom.url,
};
}
}
@ -74,4 +73,9 @@ const search = (
}).filter(Boolean) as Emoji[];
};
/** Import Mastodon custom emojis as emoji mart custom emojis. */
export function autosuggestPopulate(emojis: CustomEmoji[]) {
addCustomToPool(buildCustomEmojis(emojis));
}
export default search;

View File

@ -4,7 +4,6 @@ import { Switch, useHistory, useLocation, Redirect } from 'react-router-dom';
import { fetchFollowRequests } from 'soapbox/actions/accounts.ts';
import { fetchReports, fetchUsers, fetchConfig } from 'soapbox/actions/admin.ts';
import { fetchCustomEmojis } from 'soapbox/actions/custom-emojis.ts';
import { fetchFilters } from 'soapbox/actions/filters.ts';
import { fetchMarker } from 'soapbox/actions/markers.ts';
import { expandNotifications } from 'soapbox/actions/notifications.ts';
@ -13,6 +12,7 @@ import { fetchScheduledStatuses } from 'soapbox/actions/scheduled-statuses.ts';
import { fetchSuggestionsForTimeline } from 'soapbox/actions/suggestions.ts';
import { expandHomeTimeline } from 'soapbox/actions/timelines.ts';
import { useUserStream } from 'soapbox/api/hooks/index.ts';
import { useCustomEmojis } from 'soapbox/api/hooks/useCustomEmojis.ts';
import SidebarNavigation from 'soapbox/components/sidebar-navigation.tsx';
import ThumbNavigation from 'soapbox/components/thumb-navigation.tsx';
import Layout from 'soapbox/components/ui/layout.tsx';
@ -472,11 +472,11 @@ const UI: React.FC<IUI> = ({ children }) => {
}, []);
useUserStream();
useCustomEmojis();
// The user has logged in
useEffect(() => {
loadAccountData();
dispatch(fetchCustomEmojis());
}, [!!account]);
useEffect(() => {

View File

@ -1,10 +0,0 @@
import { List as ImmutableList } from 'immutable';
import { describe, expect, it } from 'vitest';
import reducer from './custom-emojis.ts';
describe('custom_emojis reducer', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {} as any)).toEqual(ImmutableList());
});
});

View File

@ -1,38 +0,0 @@
import { List as ImmutableList, Map as ImmutableMap, fromJS } from 'immutable';
import emojiData from 'soapbox/features/emoji/data.ts';
import { buildCustomEmojis } from 'soapbox/features/emoji/index.ts';
import { addCustomToPool } from 'soapbox/features/emoji/search.ts';
import { CUSTOM_EMOJIS_FETCH_SUCCESS } from '../actions/custom-emojis.ts';
import type { AnyAction } from 'redux';
import type { APIEntity } from 'soapbox/types/entities.ts';
const initialState = ImmutableList<ImmutableMap<string, string>>();
// Populate custom emojis for composer autosuggest
const autosuggestPopulate = (emojis: ImmutableList<ImmutableMap<string, string>>) => {
addCustomToPool(buildCustomEmojis(emojis));
};
const importEmojis = (customEmojis: APIEntity[]) => {
const emojis = (fromJS(customEmojis) as ImmutableList<ImmutableMap<string, string>>).filter((emoji) => {
// If a custom emoji has the shortcode of a Unicode emoji, skip it.
// Otherwise it breaks EmojiMart.
// https://gitlab.com/soapbox-pub/soapbox/-/issues/610
const shortcode = emoji.get('shortcode', '').toLowerCase();
return !emojiData.emojis[shortcode];
});
autosuggestPopulate(emojis);
return emojis;
};
export default function custom_emojis(state = initialState, action: AnyAction) {
if (action.type === CUSTOM_EMOJIS_FETCH_SUCCESS) {
return importEmojis(action.custom_emojis);
}
return state;
}

View File

@ -14,7 +14,6 @@ import compose_event from './compose-event.ts';
import compose from './compose.ts';
import contexts from './contexts.ts';
import conversations from './conversations.ts';
import custom_emojis from './custom-emojis.ts';
import domain_lists from './domain-lists.ts';
import dropdown_menu from './dropdown-menu.ts';
import filters from './filters.ts';
@ -69,7 +68,6 @@ export default combineReducers({
compose_event,
contexts,
conversations,
custom_emojis,
domain_lists,
dropdown_menu,
entities,