[Glitch] Remove custom emojis from Redux
Port 5d9796afb2ab07f64e9876273c363c35ab864bd5 to glitch-soc Signed-off-by: Claire <claire.github-309c@sitedethib.com>
This commit is contained in:
parent
f37da7d231
commit
11f3393825
@ -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,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@ -1,6 +1,5 @@
|
|||||||
import { Provider } from 'react-redux';
|
import { Provider } from 'react-redux';
|
||||||
|
|
||||||
import { fetchCustomEmojis } from 'flavours/glitch/actions/custom_emojis';
|
|
||||||
import { fetchServer } from 'flavours/glitch/actions/server';
|
import { fetchServer } from 'flavours/glitch/actions/server';
|
||||||
import { hydrateStore } from 'flavours/glitch/actions/store';
|
import { hydrateStore } from 'flavours/glitch/actions/store';
|
||||||
import { Router } from 'flavours/glitch/components/router';
|
import { Router } from 'flavours/glitch/components/router';
|
||||||
@ -13,7 +12,6 @@ if (initialState) {
|
|||||||
store.dispatch(hydrateStore(initialState));
|
store.dispatch(hydrateStore(initialState));
|
||||||
}
|
}
|
||||||
|
|
||||||
store.dispatch(fetchCustomEmojis());
|
|
||||||
store.dispatch(fetchServer());
|
store.dispatch(fetchServer());
|
||||||
|
|
||||||
const ComposeContainer = () => (
|
const ComposeContainer = () => (
|
||||||
|
|||||||
@ -5,8 +5,6 @@ import { Route } from 'react-router-dom';
|
|||||||
|
|
||||||
import { Provider as ReduxProvider } from 'react-redux';
|
import { Provider as ReduxProvider } from 'react-redux';
|
||||||
|
|
||||||
|
|
||||||
import { fetchCustomEmojis } from 'flavours/glitch/actions/custom_emojis';
|
|
||||||
import { checkDeprecatedLocalSettings } from 'flavours/glitch/actions/local_settings';
|
import { checkDeprecatedLocalSettings } from 'flavours/glitch/actions/local_settings';
|
||||||
import { hydrateStore } from 'flavours/glitch/actions/store';
|
import { hydrateStore } from 'flavours/glitch/actions/store';
|
||||||
import { connectUserStream } from 'flavours/glitch/actions/streaming';
|
import { connectUserStream } from 'flavours/glitch/actions/streaming';
|
||||||
@ -31,10 +29,6 @@ store.dispatch(hydrateAction);
|
|||||||
// check for deprecated local settings
|
// check for deprecated local settings
|
||||||
store.dispatch(checkDeprecatedLocalSettings());
|
store.dispatch(checkDeprecatedLocalSettings());
|
||||||
|
|
||||||
if (initialState.meta.me) {
|
|
||||||
store.dispatch(fetchCustomEmojis());
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class Mastodon extends PureComponent {
|
export default class Mastodon extends PureComponent {
|
||||||
identity = createIdentityContext(initialState);
|
identity = createIdentityContext(initialState);
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,6 @@ import { defineMessages, FormattedMessage } from 'react-intl';
|
|||||||
|
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
|
||||||
|
|
||||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||||
import Overlay from 'react-overlays/Overlay';
|
import Overlay from 'react-overlays/Overlay';
|
||||||
|
|
||||||
@ -14,7 +12,6 @@ import MoodIcon from '@/material-icons/400-20px/mood.svg?react';
|
|||||||
import { IconButton } from 'flavours/glitch/components/icon_button';
|
import { IconButton } from 'flavours/glitch/components/icon_button';
|
||||||
import { injectIntl } from '@/flavours/glitch/components/intl';
|
import { injectIntl } from '@/flavours/glitch/components/intl';
|
||||||
|
|
||||||
import { buildCustomEmojis, categoriesFromEmojis } from '../../emoji/emoji';
|
|
||||||
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
|
import { EmojiPicker as EmojiPickerAsync } from '../../ui/util/async-components';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
@ -156,7 +153,6 @@ class ModifierPicker extends PureComponent {
|
|||||||
class EmojiPickerMenuImpl extends PureComponent {
|
class EmojiPickerMenuImpl extends PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
custom_emojis: ImmutablePropTypes.list,
|
|
||||||
frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string),
|
frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string),
|
||||||
loading: PropTypes.bool,
|
loading: PropTypes.bool,
|
||||||
onClose: PropTypes.func.isRequired,
|
onClose: PropTypes.func.isRequired,
|
||||||
@ -254,7 +250,7 @@ class EmojiPickerMenuImpl extends PureComponent {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { loading, style, intl, custom_emojis, skinTone, frequentlyUsedEmojis } = this.props;
|
const { loading, style, intl, skinTone, frequentlyUsedEmojis } = this.props;
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <div style={{ width: 299 }} />;
|
return <div style={{ width: 299 }} />;
|
||||||
@ -264,32 +260,16 @@ class EmojiPickerMenuImpl extends PureComponent {
|
|||||||
|
|
||||||
const { modifierOpen } = this.state;
|
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 (
|
return (
|
||||||
<div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}>
|
<div className={classNames('emoji-picker-dropdown__menu', { selecting: modifierOpen })} style={style} ref={this.setRef}>
|
||||||
<EmojiPicker
|
<EmojiPicker
|
||||||
perLine={8}
|
perLine={8}
|
||||||
emojiSize={22}
|
emojiSize={22}
|
||||||
custom={buildCustomEmojis(custom_emojis)}
|
|
||||||
color=''
|
color=''
|
||||||
emoji=''
|
emoji=''
|
||||||
title={title}
|
title={title}
|
||||||
i18n={this.getI18n()}
|
i18n={this.getI18n()}
|
||||||
onClick={this.handleClick}
|
onClick={this.handleClick}
|
||||||
include={categoriesSort}
|
|
||||||
recent={frequentlyUsedEmojis}
|
recent={frequentlyUsedEmojis}
|
||||||
skin={skinTone}
|
skin={skinTone}
|
||||||
showPreview={false}
|
showPreview={false}
|
||||||
@ -315,9 +295,7 @@ class EmojiPickerMenuImpl extends PureComponent {
|
|||||||
const EmojiPickerMenu = injectIntl(EmojiPickerMenuImpl);
|
const EmojiPickerMenu = injectIntl(EmojiPickerMenuImpl);
|
||||||
|
|
||||||
class EmojiPickerDropdown extends PureComponent {
|
class EmojiPickerDropdown extends PureComponent {
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
custom_emojis: ImmutablePropTypes.list,
|
|
||||||
frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string),
|
frequentlyUsedEmojis: PropTypes.arrayOf(PropTypes.string),
|
||||||
intl: PropTypes.object.isRequired,
|
intl: PropTypes.object.isRequired,
|
||||||
onPickEmoji: PropTypes.func.isRequired,
|
onPickEmoji: PropTypes.func.isRequired,
|
||||||
@ -346,7 +324,9 @@ class EmojiPickerDropdown extends PureComponent {
|
|||||||
EmojiPicker = EmojiMart.Picker;
|
EmojiPicker = EmojiMart.Picker;
|
||||||
Emoji = EmojiMart.Emoji;
|
Emoji = EmojiMart.Emoji;
|
||||||
|
|
||||||
this.setState({ loading: false });
|
void EmojiMart.loadCustomEmojiData().then(() => {
|
||||||
|
this.setState({ loading: false });
|
||||||
|
});
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
this.setState({ loading: false, active: false });
|
this.setState({ loading: false, active: false });
|
||||||
});
|
});
|
||||||
@ -408,7 +388,6 @@ class EmojiPickerDropdown extends PureComponent {
|
|||||||
<div {...props} style={{ ...props.style }}>
|
<div {...props} style={{ ...props.style }}>
|
||||||
<div className={`dropdown-animation ${placement}`}>
|
<div className={`dropdown-animation ${placement}`}>
|
||||||
<EmojiPickerMenu
|
<EmojiPickerMenu
|
||||||
custom_emojis={this.props.custom_emojis}
|
|
||||||
loading={loading}
|
loading={loading}
|
||||||
onClose={this.onHideDropdown}
|
onClose={this.onHideDropdown}
|
||||||
onPick={onPickEmoji}
|
onPick={onPickEmoji}
|
||||||
|
|||||||
@ -47,23 +47,7 @@ const getFrequentlyUsedEmojis = createSelector([
|
|||||||
return emojis;
|
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 => ({
|
const mapStateToProps = state => ({
|
||||||
custom_emojis: getCustomEmojis(state),
|
|
||||||
skinTone: state.getIn(['settings', 'skinTone']),
|
skinTone: state.getIn(['settings', 'skinTone']),
|
||||||
frequentlyUsedEmojis: getFrequentlyUsedEmojis(state),
|
frequentlyUsedEmojis: getFrequentlyUsedEmojis(state),
|
||||||
});
|
});
|
||||||
|
|||||||
@ -168,29 +168,3 @@ const emojify = (str, customEmojis = {}) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default emojify;
|
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']));
|
|
||||||
|
|||||||
@ -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 EmojiRaw from 'emoji-mart/dist-es/components/emoji/nimble-emoji';
|
||||||
import PickerRaw from 'emoji-mart/dist-es/components/picker/nimble-picker';
|
import PickerRaw from 'emoji-mart/dist-es/components/picker/nimble-picker';
|
||||||
|
|
||||||
|
import { autoPlayGif } from '@/flavours/glitch/initial_state';
|
||||||
import { assetHost } from 'flavours/glitch/utils/config';
|
import { assetHost } from 'flavours/glitch/utils/config';
|
||||||
|
|
||||||
import { EMOJI_MODE_NATIVE } from './constants';
|
import { EMOJI_MODE_NATIVE } from './constants';
|
||||||
import EmojiData from './emoji_data.json';
|
import EmojiData from './emoji_data.json';
|
||||||
import { useEmojiAppState } from './mode';
|
import { useEmojiAppState } from './mode';
|
||||||
|
import { emojiLogger } from './utils';
|
||||||
|
|
||||||
const backgroundImageFnDefault = () => `${assetHost}/emoji/sheet_16_0.png`;
|
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',
|
set = 'twitter',
|
||||||
sheetSize = 32,
|
sheetSize = 32,
|
||||||
sheetColumns = 62,
|
sheetColumns = 62,
|
||||||
sheetRows = 62,
|
sheetRows = 62,
|
||||||
backgroundImageFn = backgroundImageFnDefault,
|
backgroundImageFn = backgroundImageFnDefault,
|
||||||
...props
|
...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();
|
const { mode } = useEmojiAppState();
|
||||||
return (
|
return (
|
||||||
<EmojiRaw
|
<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}
|
data={EmojiData}
|
||||||
set={set}
|
set={set}
|
||||||
sheetSize={sheetSize}
|
sheetSize={sheetSize}
|
||||||
@ -55,5 +136,3 @@ const Picker = ({
|
|||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export { Picker, Emoji };
|
|
||||||
|
|||||||
@ -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('@/flavours/glitch/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;
|
|
||||||
}
|
|
||||||
@ -11,7 +11,6 @@ import announcements from './announcements';
|
|||||||
import { composeReducer } from './compose';
|
import { composeReducer } from './compose';
|
||||||
import { contextsReducer } from './contexts';
|
import { contextsReducer } from './contexts';
|
||||||
import conversations from './conversations';
|
import conversations from './conversations';
|
||||||
import custom_emojis from './custom_emojis';
|
|
||||||
import { dropdownMenuReducer } from './dropdown_menu';
|
import { dropdownMenuReducer } from './dropdown_menu';
|
||||||
import filters from './filters';
|
import filters from './filters';
|
||||||
import height_cache from './height_cache';
|
import height_cache from './height_cache';
|
||||||
@ -69,7 +68,6 @@ const reducers = {
|
|||||||
notifications,
|
notifications,
|
||||||
notificationGroups: notificationGroupsReducer,
|
notificationGroups: notificationGroupsReducer,
|
||||||
height_cache,
|
height_cache,
|
||||||
custom_emojis,
|
|
||||||
lists: listsReducer,
|
lists: listsReducer,
|
||||||
followedTags: followedTagsReducer,
|
followedTags: followedTagsReducer,
|
||||||
filters,
|
filters,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user