mirror of
https://github.com/glitch-soc/mastodon.git
synced 2024-11-28 11:00:26 -05:00
911cc14481
* Add follow_request notification type The notification type already existed in the backend but was never pushed to the front-end. This also means translation strings were also available for the backend, from the notification mailer. Unlike other notification types, these are off by default, to match what I remember of Gargron's view on the topic: that follow requests should not clutter notifications and should instead be reviewed at the user's own leisure in the dedicated column. Since follow requests have their own column, I've deemed it unnecessary to add a specific tab for them in the notification quick filter. * Show follow request link in single-column if there are pending requests, even if account isn't locked * Push follow requests from notifications to the follow_requests list * Offer to accept or reject follow request from the notification * Redesign follow request notification
230 lines
7.6 KiB
JavaScript
230 lines
7.6 KiB
JavaScript
import api, { getLinks } from '../api';
|
|
import IntlMessageFormat from 'intl-messageformat';
|
|
import { fetchRelationships } from './accounts';
|
|
import {
|
|
importFetchedAccount,
|
|
importFetchedAccounts,
|
|
importFetchedStatus,
|
|
importFetchedStatuses,
|
|
} from './importer';
|
|
import { saveSettings } from './settings';
|
|
import { defineMessages } from 'react-intl';
|
|
import { List as ImmutableList } from 'immutable';
|
|
import { unescapeHTML } from '../utils/html';
|
|
import { getFiltersRegex } from '../selectors';
|
|
import { usePendingItems as preferPendingItems } from 'mastodon/initial_state';
|
|
import compareId from 'mastodon/compare_id';
|
|
import { searchTextFromRawStatus } from 'mastodon/actions/importer/normalizer';
|
|
|
|
export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';
|
|
export const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP';
|
|
|
|
export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST';
|
|
export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS';
|
|
export const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL';
|
|
|
|
export const NOTIFICATIONS_FILTER_SET = 'NOTIFICATIONS_FILTER_SET';
|
|
|
|
export const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR';
|
|
export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';
|
|
export const NOTIFICATIONS_LOAD_PENDING = 'NOTIFICATIONS_LOAD_PENDING';
|
|
|
|
export const NOTIFICATIONS_MOUNT = 'NOTIFICATIONS_MOUNT';
|
|
export const NOTIFICATIONS_UNMOUNT = 'NOTIFICATIONS_UNMOUNT';
|
|
|
|
defineMessages({
|
|
mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
|
|
group: { id: 'notifications.group', defaultMessage: '{count} notifications' },
|
|
});
|
|
|
|
const fetchRelatedRelationships = (dispatch, notifications) => {
|
|
const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id);
|
|
|
|
if (accountIds.length > 0) {
|
|
dispatch(fetchRelationships(accountIds));
|
|
}
|
|
};
|
|
|
|
export const loadPending = () => ({
|
|
type: NOTIFICATIONS_LOAD_PENDING,
|
|
});
|
|
|
|
export function updateNotifications(notification, intlMessages, intlLocale) {
|
|
return (dispatch, getState) => {
|
|
const showInColumn = getState().getIn(['settings', 'notifications', 'shows', notification.type], true);
|
|
const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true);
|
|
const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true);
|
|
const filters = getFiltersRegex(getState(), { contextType: 'notifications' });
|
|
|
|
let filtered = false;
|
|
|
|
if (notification.type === 'mention') {
|
|
const dropRegex = filters[0];
|
|
const regex = filters[1];
|
|
const searchIndex = searchTextFromRawStatus(notification.status);
|
|
|
|
if (dropRegex && dropRegex.test(searchIndex)) {
|
|
return;
|
|
}
|
|
|
|
filtered = regex && regex.test(searchIndex);
|
|
}
|
|
|
|
if (showInColumn) {
|
|
dispatch(importFetchedAccount(notification.account));
|
|
|
|
if (notification.status) {
|
|
dispatch(importFetchedStatus(notification.status));
|
|
}
|
|
|
|
dispatch({
|
|
type: NOTIFICATIONS_UPDATE,
|
|
notification,
|
|
usePendingItems: preferPendingItems,
|
|
meta: (playSound && !filtered) ? { sound: 'boop' } : undefined,
|
|
});
|
|
|
|
fetchRelatedRelationships(dispatch, [notification]);
|
|
} else if (playSound && !filtered) {
|
|
dispatch({
|
|
type: NOTIFICATIONS_UPDATE_NOOP,
|
|
meta: { sound: 'boop' },
|
|
});
|
|
}
|
|
|
|
// Desktop notifications
|
|
if (typeof window.Notification !== 'undefined' && showAlert && !filtered) {
|
|
const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username });
|
|
const body = (notification.status && notification.status.spoiler_text.length > 0) ? notification.status.spoiler_text : unescapeHTML(notification.status ? notification.status.content : '');
|
|
|
|
const notify = new Notification(title, { body, icon: notification.account.avatar, tag: notification.id });
|
|
|
|
notify.addEventListener('click', () => {
|
|
window.focus();
|
|
notify.close();
|
|
});
|
|
}
|
|
};
|
|
};
|
|
|
|
const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();
|
|
|
|
const excludeTypesFromFilter = filter => {
|
|
const allTypes = ImmutableList(['follow', 'follow_request', 'favourite', 'reblog', 'mention', 'poll']);
|
|
return allTypes.filterNot(item => item === filter).toJS();
|
|
};
|
|
|
|
const noOp = () => {};
|
|
|
|
export function expandNotifications({ maxId } = {}, done = noOp) {
|
|
return (dispatch, getState) => {
|
|
const activeFilter = getState().getIn(['settings', 'notifications', 'quickFilter', 'active']);
|
|
const notifications = getState().get('notifications');
|
|
const isLoadingMore = !!maxId;
|
|
|
|
if (notifications.get('isLoading')) {
|
|
done();
|
|
return;
|
|
}
|
|
|
|
const params = {
|
|
max_id: maxId,
|
|
exclude_types: activeFilter === 'all'
|
|
? excludeTypesFromSettings(getState())
|
|
: excludeTypesFromFilter(activeFilter),
|
|
};
|
|
|
|
if (!params.max_id && (notifications.get('items', ImmutableList()).size + notifications.get('pendingItems', ImmutableList()).size) > 0) {
|
|
const a = notifications.getIn(['pendingItems', 0, 'id']);
|
|
const b = notifications.getIn(['items', 0, 'id']);
|
|
|
|
if (a && b && compareId(a, b) > 0) {
|
|
params.since_id = a;
|
|
} else {
|
|
params.since_id = b || a;
|
|
}
|
|
}
|
|
|
|
const isLoadingRecent = !!params.since_id;
|
|
|
|
dispatch(expandNotificationsRequest(isLoadingMore));
|
|
|
|
api(getState).get('/api/v1/notifications', { params }).then(response => {
|
|
const next = getLinks(response).refs.find(link => link.rel === 'next');
|
|
|
|
dispatch(importFetchedAccounts(response.data.map(item => item.account)));
|
|
dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status)));
|
|
|
|
dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore, isLoadingRecent, isLoadingRecent && preferPendingItems));
|
|
fetchRelatedRelationships(dispatch, response.data);
|
|
done();
|
|
}).catch(error => {
|
|
dispatch(expandNotificationsFail(error, isLoadingMore));
|
|
done();
|
|
});
|
|
};
|
|
};
|
|
|
|
export function expandNotificationsRequest(isLoadingMore) {
|
|
return {
|
|
type: NOTIFICATIONS_EXPAND_REQUEST,
|
|
skipLoading: !isLoadingMore,
|
|
};
|
|
};
|
|
|
|
export function expandNotificationsSuccess(notifications, next, isLoadingMore, isLoadingRecent, usePendingItems) {
|
|
return {
|
|
type: NOTIFICATIONS_EXPAND_SUCCESS,
|
|
notifications,
|
|
next,
|
|
isLoadingRecent: isLoadingRecent,
|
|
usePendingItems,
|
|
skipLoading: !isLoadingMore,
|
|
};
|
|
};
|
|
|
|
export function expandNotificationsFail(error, isLoadingMore) {
|
|
return {
|
|
type: NOTIFICATIONS_EXPAND_FAIL,
|
|
error,
|
|
skipLoading: !isLoadingMore,
|
|
};
|
|
};
|
|
|
|
export function clearNotifications() {
|
|
return (dispatch, getState) => {
|
|
dispatch({
|
|
type: NOTIFICATIONS_CLEAR,
|
|
});
|
|
|
|
api(getState).post('/api/v1/notifications/clear');
|
|
};
|
|
};
|
|
|
|
export function scrollTopNotifications(top) {
|
|
return {
|
|
type: NOTIFICATIONS_SCROLL_TOP,
|
|
top,
|
|
};
|
|
};
|
|
|
|
export function setFilter (filterType) {
|
|
return dispatch => {
|
|
dispatch({
|
|
type: NOTIFICATIONS_FILTER_SET,
|
|
path: ['notifications', 'quickFilter', 'active'],
|
|
value: filterType,
|
|
});
|
|
dispatch(expandNotifications());
|
|
dispatch(saveSettings());
|
|
};
|
|
};
|
|
|
|
export const mountNotifications = () => ({
|
|
type: NOTIFICATIONS_MOUNT,
|
|
});
|
|
|
|
export const unmountNotifications = () => ({
|
|
type: NOTIFICATIONS_UNMOUNT,
|
|
});
|