Remove custom emojis from Redux (#38825)

This commit is contained in:
Echo 2026-04-28 15:49:10 +02:00 committed by GitHub
parent bd17c48ef9
commit 5d9796afb2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 111 additions and 161 deletions

View File

@ -1,40 +0,0 @@
import api from '../api';
export const CUSTOM_EMOJIS_FETCH_REQUEST = 'CUSTOM_EMOJIS_FETCH_REQUEST';
export const CUSTOM_EMOJIS_FETCH_SUCCESS = 'CUSTOM_EMOJIS_FETCH_SUCCESS';
export const CUSTOM_EMOJIS_FETCH_FAIL = 'CUSTOM_EMOJIS_FETCH_FAIL';
export function fetchCustomEmojis() {
return (dispatch) => {
dispatch(fetchCustomEmojisRequest());
api().get('/api/v1/custom_emojis').then(response => {
dispatch(fetchCustomEmojisSuccess(response.data));
}).catch(error => {
dispatch(fetchCustomEmojisFail(error));
});
};
}
export function fetchCustomEmojisRequest() {
return {
type: CUSTOM_EMOJIS_FETCH_REQUEST,
skipLoading: true,
};
}
export function fetchCustomEmojisSuccess(custom_emojis) {
return {
type: CUSTOM_EMOJIS_FETCH_SUCCESS,
custom_emojis,
skipLoading: true,
};
}
export function fetchCustomEmojisFail(error) {
return {
type: CUSTOM_EMOJIS_FETCH_FAIL,
error,
skipLoading: true,
};
}

View File

@ -1,6 +1,5 @@
import { Provider } from 'react-redux';
import { fetchCustomEmojis } from 'mastodon/actions/custom_emojis';
import { fetchServer } from 'mastodon/actions/server';
import { hydrateStore } from 'mastodon/actions/store';
import { Router } from 'mastodon/components/router';
@ -13,7 +12,6 @@ if (initialState) {
store.dispatch(hydrateStore(initialState));
}
store.dispatch(fetchCustomEmojis());
store.dispatch(fetchServer());
const ComposeContainer = () => (

View File

@ -5,8 +5,6 @@ import { Route } from 'react-router-dom';
import { Provider as ReduxProvider } from 'react-redux';
import { fetchCustomEmojis } from 'mastodon/actions/custom_emojis';
import { hydrateStore } from 'mastodon/actions/store';
import { connectUserStream } from 'mastodon/actions/streaming';
import ErrorBoundary from 'mastodon/components/error_boundary';
@ -26,9 +24,6 @@ const title = isProduction() ? siteTitle : `${siteTitle} (Dev)`;
const hydrateAction = hydrateStore(initialState);
store.dispatch(hydrateAction);
if (initialState.meta.me) {
store.dispatch(fetchCustomEmojis());
}
export default class Mastodon extends PureComponent {
identity = createIdentityContext(initialState);

View File

@ -5,8 +5,6 @@ import { defineMessages, FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { supportsPassiveEvents } from 'detect-passive-events';
import Overlay from 'react-overlays/Overlay';
@ -14,7 +12,6 @@ import MoodIcon from '@/material-icons/400-20px/mood.svg?react';
import { IconButton } from 'mastodon/components/icon_button';
import { injectIntl } from '@/mastodon/components/intl';
import { buildCustomEmojis, categoriesFromEmojis } from '../../emoji/emoji';
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
const messages = defineMessages({
@ -156,7 +153,6 @@ class ModifierPicker extends PureComponent {
class EmojiPickerMenuImpl extends PureComponent {
static propTypes = {
custom_emojis: ImmutablePropTypes.list,
frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string),
loading: PropTypes.bool,
onClose: PropTypes.func.isRequired,
@ -254,7 +250,7 @@ class EmojiPickerMenuImpl extends PureComponent {
};
render() {
const { loading, style, intl, custom_emojis, skinTone, frequentlyUsedEmojis } = this.props;
const { loading, style, intl, skinTone, frequentlyUsedEmojis } = this.props;
if (loading) {
return <div style={{ width: 299 }} />;
@ -264,32 +260,16 @@ class EmojiPickerMenuImpl extends PureComponent {
const { modifierOpen } = this.state;
const categoriesSort = [
'recent',
'people',
'nature',
'foods',
'activity',
'places',
'objects',
'symbols',
'flags',
];
categoriesSort.splice(1, 0, ...Array.from(categoriesFromEmojis(custom_emojis)).sort());
return (
<div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}>
<EmojiPicker
perLine={8}
emojiSize={22}
custom={buildCustomEmojis(custom_emojis)}
color=''
emoji=''
title={title}
i18n={this.getI18n()}
onClick={this.handleClick}
include={categoriesSort}
recent={frequentlyUsedEmojis}
skin={skinTone}
showPreview={false}
@ -315,9 +295,7 @@ class EmojiPickerMenuImpl extends PureComponent {
const EmojiPickerMenu = injectIntl(EmojiPickerMenuImpl);
class EmojiPickerDropdown extends PureComponent {
static propTypes = {
custom_emojis: ImmutablePropTypes.list,
frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string),
intl: PropTypes.object.isRequired,
onPickEmoji: PropTypes.func.isRequired,
@ -346,7 +324,9 @@ class EmojiPickerDropdown extends PureComponent {
EmojiPicker = EmojiMart.Picker;
Emoji = EmojiMart.Emoji;
this.setState({ loading: false });
void EmojiMart.loadCustomEmojiData().then(() => {
this.setState({ loading: false });
});
}).catch(() => {
this.setState({ loading: false, active: false });
});
@ -408,7 +388,6 @@ class EmojiPickerDropdown extends PureComponent {
<div {...props} style={{ ...props.style }}>
<div className={`dropdown-animation ${placement}`}>
<EmojiPickerMenu
custom_emojis={this.props.custom_emojis}
loading={loading}
onClose={this.onHideDropdown}
onPick={onPickEmoji}

View File

@ -47,23 +47,7 @@ const getFrequentlyUsedEmojis = createSelector([
return emojis;
});
const getCustomEmojis = createSelector([
state => state.get('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();
if (aShort < bShort) {
return -1;
} else if (aShort > bShort ) {
return 1;
} else {
return 0;
}
}));
const mapStateToProps = state => ({
custom_emojis: getCustomEmojis(state),
skinTone: state.getIn(['settings', 'skinTone']),
frequentlyUsedEmojis: getFrequentlyUsedEmojis(state),
});

View File

@ -168,29 +168,3 @@ const emojify = (str, customEmojis = {}) => {
};
export default emojify;
export const buildCustomEmojis = (customEmojis) => {
const emojis = [];
customEmojis.forEach(emoji => {
const shortcode = emoji.get('shortcode');
const url = autoPlayGif ? emoji.get('url') : emoji.get('static_url');
const name = shortcode.replace(':', '');
emojis.push({
id: name,
name,
short_names: [name],
text: '',
emoticons: [],
keywords: [name],
imageUrl: url,
custom: true,
customCategory: emoji.get('category'),
});
});
return emojis;
};
export const categoriesFromEmojis = customEmojis => customEmojis.reduce((set, emoji) => set.add(emoji.get('category') ? `custom-${emoji.get('category')}` : 'custom'), new Set(['custom']));

View File

@ -1,49 +1,130 @@
import type { EmojiProps, PickerProps } from 'emoji-mart';
import type { FC } from 'react';
import { useEffect, useState } from 'react';
import type {
CategoryName,
CustomEmoji,
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 { EMOJI_MODE_NATIVE } from './constants';
import EmojiData from './emoji_data.json';
import { useEmojiAppState } from './mode';
import { emojiLogger } from './utils';
const backgroundImageFnDefault = () => `${assetHost}/emoji/sheet_16_0.png`;
const Emoji = ({
let customEmojis: CustomEmoji[] | null = null;
let customCategories = [
'recent',
'people',
'nature',
'foods',
'activity',
'places',
'objects',
'symbols',
'flags',
] as CategoryName[];
const log = emojiLogger('picker');
export const Picker: FC<PickerProps> = ({
set = 'twitter',
sheetSize = 32,
sheetColumns = 62,
sheetRows = 62,
backgroundImageFn = backgroundImageFnDefault,
...props
}: EmojiProps) => {
}) => {
const { mode } = useEmojiAppState();
const [isLoaded, setLoaded] = useState(customEmojis !== null);
useEffect(() => {
if (customEmojis === null) {
void loadCustomEmojiData().then(() => {
setLoaded(true);
});
}
}, []);
if (!isLoaded) {
return null;
}
return (
<PickerRaw
data={EmojiData}
custom={customEmojis ?? []}
include={customCategories}
set={set}
sheetSize={sheetSize}
sheetColumns={sheetColumns}
sheetRows={sheetRows}
native={mode === EMOJI_MODE_NATIVE}
backgroundImageFn={backgroundImageFn}
{...props}
/>
);
};
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,
sheetColumns = 62,
sheetRows = 62,
backgroundImageFn = backgroundImageFnDefault,
...props
}) => {
const { mode } = useEmojiAppState();
return (
<EmojiRaw
data={EmojiData}
set={set}
sheetSize={sheetSize}
sheetColumns={sheetColumns}
sheetRows={sheetRows}
native={mode === EMOJI_MODE_NATIVE}
backgroundImageFn={backgroundImageFn}
{...props}
/>
);
};
const Picker = ({
set = 'twitter',
sheetSize = 32,
sheetColumns = 62,
sheetRows = 62,
backgroundImageFn = backgroundImageFnDefault,
...props
}: PickerProps) => {
const { mode } = useEmojiAppState();
return (
<PickerRaw
data={EmojiData}
set={set}
sheetSize={sheetSize}
@ -55,5 +136,3 @@ const Picker = ({
/>
);
};
export { Picker, Emoji };

View File

@ -1,17 +0,0 @@
import { List as ImmutableList, fromJS as ConvertToImmutable } from 'immutable';
import { CUSTOM_EMOJIS_FETCH_SUCCESS } from '../actions/custom_emojis';
import { buildCustomEmojis } from '../features/emoji/emoji';
import { search as emojiSearch } from '../features/emoji/emoji_mart_search_light';
/** @type {ImmutableList<import('@/mastodon/models/custom_emoji').CustomEmoji>} */
const initialState = ImmutableList([]);
export default function custom_emojis(state = initialState, action) {
if(action.type === CUSTOM_EMOJIS_FETCH_SUCCESS) {
state = ConvertToImmutable(action.custom_emojis);
emojiSearch('', { custom: buildCustomEmojis(state) });
}
return state;
}

View File

@ -11,7 +11,6 @@ import announcements from './announcements';
import { composeReducer } from './compose';
import { contextsReducer } from './contexts';
import conversations from './conversations';
import custom_emojis from './custom_emojis';
import { dropdownMenuReducer } from './dropdown_menu';
import filters from './filters';
import height_cache from './height_cache';
@ -67,7 +66,6 @@ const reducers = {
notifications,
notificationGroups: notificationGroupsReducer,
height_cache,
custom_emojis,
lists: listsReducer,
followedTags: followedTagsReducer,
filters,