Join some notification filters, add filter for event-related notifications
Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
parent
3d12df2c37
commit
5d28e9609a
8 changed files with 60 additions and 70 deletions
|
@ -45,6 +45,19 @@ const NOTIFICATIONS_MARK_READ_FAIL = 'NOTIFICATIONS_MARK_READ_FAIL';
|
||||||
|
|
||||||
const MAX_QUEUED_NOTIFICATIONS = 40;
|
const MAX_QUEUED_NOTIFICATIONS = 40;
|
||||||
|
|
||||||
|
const FILTER_TYPES = {
|
||||||
|
all: undefined,
|
||||||
|
mention: ['mention'],
|
||||||
|
favourite: ['favourite', 'pleroma:emoji_reaction'],
|
||||||
|
reblog: ['reblog'],
|
||||||
|
poll: ['poll'],
|
||||||
|
status: ['status'],
|
||||||
|
follow: ['follow', 'follow_request'],
|
||||||
|
events: ['pleroma:event_reminder', 'pleroma:participation_request', 'pleroma:participation_accepted'],
|
||||||
|
};
|
||||||
|
|
||||||
|
type FilterType = keyof typeof FILTER_TYPES;
|
||||||
|
|
||||||
defineMessages({
|
defineMessages({
|
||||||
mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
|
mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
|
||||||
group: { id: 'notifications.group', defaultMessage: '{count, plural, one {# notification} other {# notifications}}' },
|
group: { id: 'notifications.group', defaultMessage: '{count, plural, one {# notification} other {# notifications}}' },
|
||||||
|
@ -168,26 +181,33 @@ const dequeueNotifications = () =>
|
||||||
dispatch(markReadNotifications());
|
dispatch(markReadNotifications());
|
||||||
};
|
};
|
||||||
|
|
||||||
const excludeTypesFromFilter = (filter: string) => {
|
const excludeTypesFromFilter = (filters: string[]) => {
|
||||||
return NOTIFICATION_TYPES.filter(item => item !== filter);
|
return NOTIFICATION_TYPES.filter(item => !filters.includes(item));
|
||||||
};
|
};
|
||||||
|
|
||||||
const noOp = () => new Promise(f => f(undefined));
|
const noOp = () => new Promise(f => f(undefined));
|
||||||
|
|
||||||
const expandNotifications = ({ maxId }: Record<string, any> = {}, done: () => any = noOp) =>
|
let abortExpandNotifications = new AbortController();
|
||||||
|
|
||||||
|
const expandNotifications = ({ maxId }: Record<string, any> = {}, done: () => any = noOp, abort?: boolean) =>
|
||||||
(dispatch: AppDispatch, getState: () => RootState) => {
|
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
if (!isLoggedIn(getState)) return dispatch(noOp);
|
if (!isLoggedIn(getState)) return dispatch(noOp);
|
||||||
|
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const features = getFeatures(state.instance);
|
const features = getFeatures(state.instance);
|
||||||
const activeFilter = getSettings(state).getIn(['notifications', 'quickFilter', 'active']) as string;
|
const activeFilter = getSettings(state).getIn(['notifications', 'quickFilter', 'active']) as FilterType;
|
||||||
const notifications = state.notifications;
|
const notifications = state.notifications;
|
||||||
const isLoadingMore = !!maxId;
|
const isLoadingMore = !!maxId;
|
||||||
|
|
||||||
if (notifications.isLoading) {
|
if (notifications.isLoading) {
|
||||||
|
if (abort) {
|
||||||
|
abortExpandNotifications.abort();
|
||||||
|
abortExpandNotifications = new AbortController();
|
||||||
|
} else {
|
||||||
done();
|
done();
|
||||||
return dispatch(noOp);
|
return dispatch(noOp);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const params: Record<string, any> = {
|
const params: Record<string, any> = {
|
||||||
max_id: maxId,
|
max_id: maxId,
|
||||||
|
@ -200,10 +220,11 @@ const expandNotifications = ({ maxId }: Record<string, any> = {}, done: () => an
|
||||||
params.exclude_types = EXCLUDE_TYPES;
|
params.exclude_types = EXCLUDE_TYPES;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
const filtered = FILTER_TYPES[activeFilter] || [activeFilter];
|
||||||
if (features.notificationsIncludeTypes) {
|
if (features.notificationsIncludeTypes) {
|
||||||
params.types = [activeFilter];
|
params.types = filtered;
|
||||||
} else {
|
} else {
|
||||||
params.exclude_types = excludeTypesFromFilter(activeFilter);
|
params.exclude_types = excludeTypesFromFilter(filtered);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -213,7 +234,7 @@ const expandNotifications = ({ maxId }: Record<string, any> = {}, done: () => an
|
||||||
|
|
||||||
dispatch(expandNotificationsRequest(isLoadingMore));
|
dispatch(expandNotificationsRequest(isLoadingMore));
|
||||||
|
|
||||||
return api(getState).get('/api/v1/notifications', { params }).then(response => {
|
return api(getState).get('/api/v1/notifications', { params, signal: abortExpandNotifications.signal }).then(response => {
|
||||||
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
||||||
|
|
||||||
const entries = (response.data as APIEntity[]).reduce((acc, item) => {
|
const entries = (response.data as APIEntity[]).reduce((acc, item) => {
|
||||||
|
@ -286,14 +307,14 @@ const scrollTopNotifications = (top: boolean) =>
|
||||||
dispatch(markReadNotifications());
|
dispatch(markReadNotifications());
|
||||||
};
|
};
|
||||||
|
|
||||||
const setFilter = (filterType: string) =>
|
const setFilter = (filterType: FilterType, abort?: boolean) =>
|
||||||
(dispatch: AppDispatch) => {
|
(dispatch: AppDispatch) => {
|
||||||
dispatch({
|
dispatch({
|
||||||
type: NOTIFICATIONS_FILTER_SET,
|
type: NOTIFICATIONS_FILTER_SET,
|
||||||
path: ['notifications', 'quickFilter', 'active'],
|
path: ['notifications', 'quickFilter', 'active'],
|
||||||
value: filterType,
|
value: filterType,
|
||||||
});
|
});
|
||||||
dispatch(expandNotifications());
|
dispatch(expandNotifications(undefined, undefined, abort));
|
||||||
dispatch(saveSettings());
|
dispatch(saveSettings());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -343,6 +364,7 @@ export {
|
||||||
NOTIFICATIONS_MARK_READ_SUCCESS,
|
NOTIFICATIONS_MARK_READ_SUCCESS,
|
||||||
NOTIFICATIONS_MARK_READ_FAIL,
|
NOTIFICATIONS_MARK_READ_FAIL,
|
||||||
MAX_QUEUED_NOTIFICATIONS,
|
MAX_QUEUED_NOTIFICATIONS,
|
||||||
|
type FilterType,
|
||||||
updateNotifications,
|
updateNotifications,
|
||||||
updateNotificationsQueue,
|
updateNotificationsQueue,
|
||||||
dequeueNotifications,
|
dequeueNotifications,
|
||||||
|
|
|
@ -81,17 +81,6 @@ const defaultSettings = ImmutableMap({
|
||||||
advanced: false,
|
advanced: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
shows: ImmutableMap({
|
|
||||||
follow: true,
|
|
||||||
follow_request: true,
|
|
||||||
favourite: true,
|
|
||||||
reblog: true,
|
|
||||||
mention: true,
|
|
||||||
poll: true,
|
|
||||||
move: true,
|
|
||||||
'pleroma:emoji_reaction': true,
|
|
||||||
}),
|
|
||||||
|
|
||||||
sounds: ImmutableMap({
|
sounds: ImmutableMap({
|
||||||
follow: false,
|
follow: false,
|
||||||
follow_request: false,
|
follow_request: false,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { defineMessages, useIntl } from 'react-intl';
|
import { defineMessages, useIntl } from 'react-intl';
|
||||||
|
|
||||||
import { setFilter } from 'soapbox/actions/notifications';
|
import { type FilterType, setFilter } from 'soapbox/actions/notifications';
|
||||||
import { Icon, Tabs } from 'soapbox/components/ui';
|
import { Icon, Tabs } from 'soapbox/components/ui';
|
||||||
import { useAppDispatch, useFeatures, useSettings } from 'soapbox/hooks';
|
import { useAppDispatch, useFeatures, useSettings } from 'soapbox/hooks';
|
||||||
|
|
||||||
|
@ -10,12 +10,12 @@ import type { Item } from 'soapbox/components/ui/tabs/tabs';
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
all: { id: 'notifications.filter.all', defaultMessage: 'All' },
|
all: { id: 'notifications.filter.all', defaultMessage: 'All' },
|
||||||
mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
|
mentions: { id: 'notifications.filter.mentions', defaultMessage: 'Mentions' },
|
||||||
|
statuses: { id: 'notifications.filter.statuses', defaultMessage: 'Updates from people you follow' },
|
||||||
favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Likes' },
|
favourites: { id: 'notifications.filter.favourites', defaultMessage: 'Likes' },
|
||||||
boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Reposts' },
|
boosts: { id: 'notifications.filter.boosts', defaultMessage: 'Reposts' },
|
||||||
polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
|
polls: { id: 'notifications.filter.polls', defaultMessage: 'Poll results' },
|
||||||
|
events: { id: 'notifications.filter.events', defaultMessage: 'Events' },
|
||||||
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
|
follows: { id: 'notifications.filter.follows', defaultMessage: 'Follows' },
|
||||||
emoji_reacts: { id: 'notifications.filter.emoji_reacts', defaultMessage: 'Emoji reacts' },
|
|
||||||
statuses: { id: 'notifications.filter.statuses', defaultMessage: 'Updates from people you follow' },
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const NotificationFilterBar = () => {
|
const NotificationFilterBar = () => {
|
||||||
|
@ -27,7 +27,13 @@ const NotificationFilterBar = () => {
|
||||||
const selectedFilter = settings.notifications.quickFilter.active;
|
const selectedFilter = settings.notifications.quickFilter.active;
|
||||||
const advancedMode = settings.notifications.quickFilter.advanced;
|
const advancedMode = settings.notifications.quickFilter.advanced;
|
||||||
|
|
||||||
const onClick = (notificationType: string) => () => dispatch(setFilter(notificationType));
|
const onClick = (notificationType: FilterType) => () => {
|
||||||
|
try {
|
||||||
|
dispatch(setFilter(notificationType, true));
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const items: Item[] = [
|
const items: Item[] = [
|
||||||
{
|
{
|
||||||
|
@ -50,18 +56,18 @@ const NotificationFilterBar = () => {
|
||||||
action: onClick('mention'),
|
action: onClick('mention'),
|
||||||
name: 'mention',
|
name: 'mention',
|
||||||
});
|
});
|
||||||
|
if (features.accountNotifies || features.accountSubscriptions) items.push({
|
||||||
|
text: <Icon className='h-4 w-4' src={require('@tabler/icons/outline/bell-ringing.svg')} />,
|
||||||
|
title: intl.formatMessage(messages.statuses),
|
||||||
|
action: onClick('status'),
|
||||||
|
name: 'status',
|
||||||
|
});
|
||||||
items.push({
|
items.push({
|
||||||
text: <Icon className='h-4 w-4' src={require('@tabler/icons/outline/heart.svg')} />,
|
text: <Icon className='h-4 w-4' src={require('@tabler/icons/outline/heart.svg')} />,
|
||||||
title: intl.formatMessage(messages.favourites),
|
title: intl.formatMessage(messages.favourites),
|
||||||
action: onClick('favourite'),
|
action: onClick('favourite'),
|
||||||
name: 'favourite',
|
name: 'favourite',
|
||||||
});
|
});
|
||||||
if (features.emojiReacts) items.push({
|
|
||||||
text: <Icon className='h-4 w-4' src={require('@tabler/icons/outline/mood-smile.svg')} />,
|
|
||||||
title: intl.formatMessage(messages.emoji_reacts),
|
|
||||||
action: onClick('pleroma:emoji_reaction'),
|
|
||||||
name: 'pleroma:emoji_reaction',
|
|
||||||
});
|
|
||||||
items.push({
|
items.push({
|
||||||
text: <Icon className='h-4 w-4' src={require('@tabler/icons/outline/repeat.svg')} />,
|
text: <Icon className='h-4 w-4' src={require('@tabler/icons/outline/repeat.svg')} />,
|
||||||
title: intl.formatMessage(messages.boosts),
|
title: intl.formatMessage(messages.boosts),
|
||||||
|
@ -74,11 +80,11 @@ const NotificationFilterBar = () => {
|
||||||
action: onClick('poll'),
|
action: onClick('poll'),
|
||||||
name: 'poll',
|
name: 'poll',
|
||||||
});
|
});
|
||||||
if (features.accountNotifies || features.accountSubscriptions) items.push({
|
if (features.events) items.push({
|
||||||
text: <Icon className='h-4 w-4' src={require('@tabler/icons/outline/bell-ringing.svg')} />,
|
text: <Icon className='h-4 w-4' src={require('@tabler/icons/outline/calendar.svg')} />,
|
||||||
title: intl.formatMessage(messages.statuses),
|
title: intl.formatMessage(messages.events),
|
||||||
action: onClick('status'),
|
action: onClick('events'),
|
||||||
name: 'status',
|
name: 'events',
|
||||||
});
|
});
|
||||||
items.push({
|
items.push({
|
||||||
text: <Icon className='h-4 w-4' src={require('@tabler/icons/outline/user-plus.svg')} />,
|
text: <Icon className='h-4 w-4' src={require('@tabler/icons/outline/user-plus.svg')} />,
|
||||||
|
|
|
@ -43,9 +43,7 @@ const icons: Record<NotificationType, string> = {
|
||||||
follow_request: require('@tabler/icons/outline/user-plus.svg'),
|
follow_request: require('@tabler/icons/outline/user-plus.svg'),
|
||||||
mention: require('@tabler/icons/outline/at.svg'),
|
mention: require('@tabler/icons/outline/at.svg'),
|
||||||
favourite: require('@tabler/icons/outline/heart.svg'),
|
favourite: require('@tabler/icons/outline/heart.svg'),
|
||||||
group_favourite: require('@tabler/icons/outline/heart.svg'),
|
|
||||||
reblog: require('@tabler/icons/outline/repeat.svg'),
|
reblog: require('@tabler/icons/outline/repeat.svg'),
|
||||||
group_reblog: require('@tabler/icons/outline/repeat.svg'),
|
|
||||||
status: require('@tabler/icons/outline/bell-ringing.svg'),
|
status: require('@tabler/icons/outline/bell-ringing.svg'),
|
||||||
poll: require('@tabler/icons/outline/chart-bar.svg'),
|
poll: require('@tabler/icons/outline/chart-bar.svg'),
|
||||||
move: require('@tabler/icons/outline/briefcase.svg'),
|
move: require('@tabler/icons/outline/briefcase.svg'),
|
||||||
|
@ -75,18 +73,10 @@ const messages: Record<NotificationType, MessageDescriptor> = defineMessages({
|
||||||
id: 'notification.favourite',
|
id: 'notification.favourite',
|
||||||
defaultMessage: '{name} liked your post',
|
defaultMessage: '{name} liked your post',
|
||||||
},
|
},
|
||||||
group_favourite: {
|
|
||||||
id: 'notification.group_favourite',
|
|
||||||
defaultMessage: '{name} liked your group post',
|
|
||||||
},
|
|
||||||
reblog: {
|
reblog: {
|
||||||
id: 'notification.reblog',
|
id: 'notification.reblog',
|
||||||
defaultMessage: '{name} reposted your post',
|
defaultMessage: '{name} reposted your post',
|
||||||
},
|
},
|
||||||
group_reblog: {
|
|
||||||
id: 'notification.group_reblog',
|
|
||||||
defaultMessage: '{name} reposted your group post',
|
|
||||||
},
|
|
||||||
status: {
|
status: {
|
||||||
id: 'notification.status',
|
id: 'notification.status',
|
||||||
defaultMessage: '{name} just posted',
|
defaultMessage: '{name} just posted',
|
||||||
|
@ -307,10 +297,8 @@ const Notification: React.FC<INotification> = (props) => {
|
||||||
/>
|
/>
|
||||||
) : null;
|
) : null;
|
||||||
case 'favourite':
|
case 'favourite':
|
||||||
case 'group_favourite':
|
|
||||||
case 'mention':
|
case 'mention':
|
||||||
case 'reblog':
|
case 'reblog':
|
||||||
case 'group_reblog':
|
|
||||||
case 'status':
|
case 'status':
|
||||||
case 'poll':
|
case 'poll':
|
||||||
case 'update':
|
case 'update':
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import clsx from 'clsx';
|
import clsx from 'clsx';
|
||||||
import { List as ImmutableList, Map as ImmutableMap } from 'immutable';
|
import { List as ImmutableList } from 'immutable';
|
||||||
import debounce from 'lodash/debounce';
|
import debounce from 'lodash/debounce';
|
||||||
import React, { useCallback, useEffect, useRef } from 'react';
|
import React, { useCallback, useEffect, useRef } from 'react';
|
||||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||||
|
@ -10,7 +10,6 @@ import {
|
||||||
scrollTopNotifications,
|
scrollTopNotifications,
|
||||||
dequeueNotifications,
|
dequeueNotifications,
|
||||||
} from 'soapbox/actions/notifications';
|
} from 'soapbox/actions/notifications';
|
||||||
import { getSettings } from 'soapbox/actions/settings';
|
|
||||||
import PullToRefresh from 'soapbox/components/pull-to-refresh';
|
import PullToRefresh from 'soapbox/components/pull-to-refresh';
|
||||||
import ScrollTopButton from 'soapbox/components/scroll-top-button';
|
import ScrollTopButton from 'soapbox/components/scroll-top-button';
|
||||||
import ScrollableList from 'soapbox/components/scrollable-list';
|
import ScrollableList from 'soapbox/components/scrollable-list';
|
||||||
|
@ -23,7 +22,6 @@ import Notification from './components/notification';
|
||||||
|
|
||||||
import type { VirtuosoHandle } from 'react-virtuoso';
|
import type { VirtuosoHandle } from 'react-virtuoso';
|
||||||
import type { RootState } from 'soapbox/store';
|
import type { RootState } from 'soapbox/store';
|
||||||
import type { Notification as NotificationEntity } from 'soapbox/types/entities';
|
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
title: { id: 'column.notifications', defaultMessage: 'Notifications' },
|
title: { id: 'column.notifications', defaultMessage: 'Notifications' },
|
||||||
|
@ -31,19 +29,8 @@ const messages = defineMessages({
|
||||||
});
|
});
|
||||||
|
|
||||||
const getNotifications = createSelector([
|
const getNotifications = createSelector([
|
||||||
state => getSettings(state).getIn(['notifications', 'quickFilter', 'show']),
|
|
||||||
state => getSettings(state).getIn(['notifications', 'quickFilter', 'active']),
|
|
||||||
state => ImmutableList((getSettings(state).getIn(['notifications', 'shows']) as ImmutableMap<string, boolean>).filter(item => !item).keys()),
|
|
||||||
(state: RootState) => state.notifications.items.toList(),
|
(state: RootState) => state.notifications.items.toList(),
|
||||||
], (showFilterBar, allowedType, excludedTypes, notifications: ImmutableList<NotificationEntity>) => {
|
], (notifications) => notifications.filter(item => item !== null));
|
||||||
if (!showFilterBar || allowedType === 'all') {
|
|
||||||
// used if user changed the notification settings after loading the notifications from the server
|
|
||||||
// otherwise a list of notifications will come pre-filtered from the backend
|
|
||||||
// we need to turn it off for FilterBar in order not to block ourselves from seeing a specific category
|
|
||||||
return notifications.filterNot(item => item !== null && excludedTypes.includes(item.get('type')));
|
|
||||||
}
|
|
||||||
return notifications.filter(item => item !== null && allowedType === item.get('type'));
|
|
||||||
});
|
|
||||||
|
|
||||||
const Notifications = () => {
|
const Notifications = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
@ -164,9 +151,8 @@ const Notifications = () => {
|
||||||
onLoadMore={handleLoadOlder}
|
onLoadMore={handleLoadOlder}
|
||||||
onScrollToTop={handleScrollToTop}
|
onScrollToTop={handleScrollToTop}
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
listClassName={clsx({
|
listClassName={clsx('divide-y divide-solid divide-gray-200 black:divide-gray-800 dark:divide-primary-800', {
|
||||||
'divide-y divide-gray-200 black:divide-gray-800 dark:divide-primary-800 divide-solid': notifications.size > 0,
|
'animate-pulse': notifications.size === 0,
|
||||||
'space-y-2': notifications.size === 0,
|
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{scrollableContent as ImmutableList<JSX.Element>}
|
{scrollableContent as ImmutableList<JSX.Element>}
|
||||||
|
|
|
@ -8,7 +8,7 @@ import PlaceholderStatusContent from './placeholder-status-content';
|
||||||
|
|
||||||
/** Fake notification to display while data is loading. */
|
/** Fake notification to display while data is loading. */
|
||||||
const PlaceholderNotification = () => (
|
const PlaceholderNotification = () => (
|
||||||
<div className='bg-white px-4 py-6 black:bg-black sm:p-6 dark:bg-primary-900'>
|
<div className='bg-white p-4 black:bg-black dark:bg-primary-900'>
|
||||||
<div className='w-full animate-pulse'>
|
<div className='w-full animate-pulse'>
|
||||||
<div className='mb-2'>
|
<div className='mb-2'>
|
||||||
<PlaceholderStatusContent minLength={20} maxLength={20} />
|
<PlaceholderStatusContent minLength={20} maxLength={20} />
|
||||||
|
|
|
@ -208,6 +208,7 @@ export default function notifications(state: State = ReducerRecord(), action: An
|
||||||
case NOTIFICATIONS_EXPAND_REQUEST:
|
case NOTIFICATIONS_EXPAND_REQUEST:
|
||||||
return state.set('isLoading', true);
|
return state.set('isLoading', true);
|
||||||
case NOTIFICATIONS_EXPAND_FAIL:
|
case NOTIFICATIONS_EXPAND_FAIL:
|
||||||
|
if (action.error?.message === 'canceled') return state;
|
||||||
return state.set('isLoading', false);
|
return state.set('isLoading', false);
|
||||||
case NOTIFICATIONS_FILTER_SET:
|
case NOTIFICATIONS_FILTER_SET:
|
||||||
return state.set('items', ImmutableOrderedMap()).set('hasMore', true);
|
return state.set('items', ImmutableOrderedMap()).set('hasMore', true);
|
||||||
|
|
|
@ -5,8 +5,6 @@ const NOTIFICATION_TYPES = [
|
||||||
'mention',
|
'mention',
|
||||||
'reblog',
|
'reblog',
|
||||||
'favourite',
|
'favourite',
|
||||||
'group_favourite',
|
|
||||||
'group_reblog',
|
|
||||||
'poll',
|
'poll',
|
||||||
'status',
|
'status',
|
||||||
'move',
|
'move',
|
||||||
|
|
Loading…
Reference in a new issue