Merge commit 'e0648a916ab81925545504173bf4f43ec64d4f3c' into glitch-soc/merge-upstream

Conflicts:
- `app/models/custom_emoji.rb`:
  An upstream refactor touched lines adjacent to ones modified in glitch-soc.
  Ported upstream's changes.
This commit is contained in:
Claire
2024-09-16 21:08:58 +02:00
81 changed files with 1501 additions and 663 deletions

View File

@@ -2,7 +2,7 @@ import { createAction } from '@reduxjs/toolkit';
import {
apiClearNotifications,
apiFetchNotifications,
apiFetchNotificationGroups,
} from 'mastodon/api/notifications';
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
import type {
@@ -71,7 +71,7 @@ function dispatchAssociatedRecords(
export const fetchNotifications = createDataLoadingThunk(
'notificationGroups/fetch',
async (_params, { getState }) =>
apiFetchNotifications({ exclude_types: getExcludedTypes(getState()) }),
apiFetchNotificationGroups({ exclude_types: getExcludedTypes(getState()) }),
({ notifications, accounts, statuses }, { dispatch }) => {
dispatch(importFetchedAccounts(accounts));
dispatch(importFetchedStatuses(statuses));
@@ -92,7 +92,7 @@ export const fetchNotifications = createDataLoadingThunk(
export const fetchNotificationsGap = createDataLoadingThunk(
'notificationGroups/fetchGap',
async (params: { gap: NotificationGap }, { getState }) =>
apiFetchNotifications({
apiFetchNotificationGroups({
max_id: params.gap.maxId,
exclude_types: getExcludedTypes(getState()),
}),
@@ -108,7 +108,7 @@ export const fetchNotificationsGap = createDataLoadingThunk(
export const pollRecentNotifications = createDataLoadingThunk(
'notificationGroups/pollRecentNotifications',
async (_params, { getState }) => {
return apiFetchNotifications({
return apiFetchNotificationGroups({
max_id: undefined,
exclude_types: getExcludedTypes(getState()),
// In slow mode, we don't want to include notifications that duplicate the already-displayed ones

View File

@@ -0,0 +1,234 @@
import {
apiFetchNotificationRequest,
apiFetchNotificationRequests,
apiFetchNotifications,
apiAcceptNotificationRequest,
apiDismissNotificationRequest,
apiAcceptNotificationRequests,
apiDismissNotificationRequests,
} from 'mastodon/api/notifications';
import type { ApiAccountJSON } from 'mastodon/api_types/accounts';
import type {
ApiNotificationGroupJSON,
ApiNotificationJSON,
} from 'mastodon/api_types/notifications';
import type { ApiStatusJSON } from 'mastodon/api_types/statuses';
import type { AppDispatch, RootState } from 'mastodon/store';
import { createDataLoadingThunk } from 'mastodon/store/typed_functions';
import { importFetchedAccounts, importFetchedStatuses } from './importer';
import { decreasePendingNotificationsCount } from './notification_policies';
// TODO: refactor with notification_groups
function dispatchAssociatedRecords(
dispatch: AppDispatch,
notifications: ApiNotificationGroupJSON[] | ApiNotificationJSON[],
) {
const fetchedAccounts: ApiAccountJSON[] = [];
const fetchedStatuses: ApiStatusJSON[] = [];
notifications.forEach((notification) => {
if (notification.type === 'admin.report') {
fetchedAccounts.push(notification.report.target_account);
}
if (notification.type === 'moderation_warning') {
fetchedAccounts.push(notification.moderation_warning.target_account);
}
if ('status' in notification && notification.status) {
fetchedStatuses.push(notification.status);
}
});
if (fetchedAccounts.length > 0)
dispatch(importFetchedAccounts(fetchedAccounts));
if (fetchedStatuses.length > 0)
dispatch(importFetchedStatuses(fetchedStatuses));
}
export const fetchNotificationRequests = createDataLoadingThunk(
'notificationRequests/fetch',
async (_params, { getState }) => {
let sinceId = undefined;
if (getState().notificationRequests.items.length > 0) {
sinceId = getState().notificationRequests.items[0]?.id;
}
return apiFetchNotificationRequests({
since_id: sinceId,
});
},
({ requests, links }, { dispatch }) => {
const next = links.refs.find((link) => link.rel === 'next');
dispatch(importFetchedAccounts(requests.map((request) => request.account)));
return { requests, next: next?.uri };
},
{
condition: (_params, { getState }) =>
!getState().notificationRequests.isLoading,
},
);
export const fetchNotificationRequest = createDataLoadingThunk(
'notificationRequest/fetch',
async ({ id }: { id: string }) => apiFetchNotificationRequest(id),
{
condition: ({ id }, { getState }) =>
!(
getState().notificationRequests.current.item?.id === id ||
getState().notificationRequests.current.isLoading
),
},
);
export const expandNotificationRequests = createDataLoadingThunk(
'notificationRequests/expand',
async (_, { getState }) => {
const nextUrl = getState().notificationRequests.next;
if (!nextUrl) throw new Error('missing URL');
return apiFetchNotificationRequests(undefined, nextUrl);
},
({ requests, links }, { dispatch }) => {
const next = links.refs.find((link) => link.rel === 'next');
dispatch(importFetchedAccounts(requests.map((request) => request.account)));
return { requests, next: next?.uri };
},
{
condition: (_, { getState }) =>
!!getState().notificationRequests.next &&
!getState().notificationRequests.isLoading,
},
);
export const fetchNotificationsForRequest = createDataLoadingThunk(
'notificationRequest/fetchNotifications',
async ({ accountId }: { accountId: string }, { getState }) => {
const sinceId =
// @ts-expect-error current.notifications.items is not yet typed
// eslint-disable-next-line @typescript-eslint/no-unsafe-call
getState().notificationRequests.current.notifications.items[0]?.get(
'id',
) as string | undefined;
return apiFetchNotifications({
since_id: sinceId,
account_id: accountId,
});
},
({ notifications, links }, { dispatch }) => {
const next = links.refs.find((link) => link.rel === 'next');
dispatchAssociatedRecords(dispatch, notifications);
return { notifications, next: next?.uri };
},
{
condition: ({ accountId }, { getState }) => {
const current = getState().notificationRequests.current;
return !(
current.item?.account_id === accountId &&
current.notifications.isLoading
);
},
},
);
export const expandNotificationsForRequest = createDataLoadingThunk(
'notificationRequest/expandNotifications',
async (_, { getState }) => {
const nextUrl = getState().notificationRequests.current.notifications.next;
if (!nextUrl) throw new Error('missing URL');
return apiFetchNotifications(undefined, nextUrl);
},
({ notifications, links }, { dispatch }) => {
const next = links.refs.find((link) => link.rel === 'next');
dispatchAssociatedRecords(dispatch, notifications);
return { notifications, next: next?.uri };
},
{
condition: ({ accountId }: { accountId: string }, { getState }) => {
const url = getState().notificationRequests.current.notifications.next;
return (
!!url &&
!getState().notificationRequests.current.notifications.isLoading &&
getState().notificationRequests.current.item?.account_id === accountId
);
},
},
);
const selectNotificationCountForRequest = (state: RootState, id: string) => {
const requests = state.notificationRequests.items;
const thisRequest = requests.find((request) => request.id === id);
return thisRequest ? thisRequest.notifications_count : 0;
};
export const acceptNotificationRequest = createDataLoadingThunk(
'notificationRequest/accept',
({ id }: { id: string }) => apiAcceptNotificationRequest(id),
(_data, { dispatch, getState, discardLoadData, actionArg: { id } }) => {
const count = selectNotificationCountForRequest(getState(), id);
dispatch(decreasePendingNotificationsCount(count));
// The payload is not used in any functions
return discardLoadData;
},
);
export const dismissNotificationRequest = createDataLoadingThunk(
'notificationRequest/dismiss',
({ id }: { id: string }) => apiDismissNotificationRequest(id),
(_data, { dispatch, getState, discardLoadData, actionArg: { id } }) => {
const count = selectNotificationCountForRequest(getState(), id);
dispatch(decreasePendingNotificationsCount(count));
// The payload is not used in any functions
return discardLoadData;
},
);
export const acceptNotificationRequests = createDataLoadingThunk(
'notificationRequests/acceptBulk',
({ ids }: { ids: string[] }) => apiAcceptNotificationRequests(ids),
(_data, { dispatch, getState, discardLoadData, actionArg: { ids } }) => {
const count = ids.reduce(
(count, id) => count + selectNotificationCountForRequest(getState(), id),
0,
);
dispatch(decreasePendingNotificationsCount(count));
// The payload is not used in any functions
return discardLoadData;
},
);
export const dismissNotificationRequests = createDataLoadingThunk(
'notificationRequests/dismissBulk',
({ ids }: { ids: string[] }) => apiDismissNotificationRequests(ids),
(_data, { dispatch, getState, discardLoadData, actionArg: { ids } }) => {
const count = ids.reduce(
(count, id) => count + selectNotificationCountForRequest(getState(), id),
0,
);
dispatch(decreasePendingNotificationsCount(count));
// The payload is not used in any functions
return discardLoadData;
},
);

View File

@@ -18,7 +18,6 @@ import {
importFetchedStatuses,
} from './importer';
import { submitMarkers } from './markers';
import { decreasePendingNotificationsCount } from './notification_policies';
import { notificationsUpdate } from "./notifications_typed";
import { register as registerPushNotifications } from './push_notifications';
import { saveSettings } from './settings';
@@ -44,26 +43,6 @@ export const NOTIFICATIONS_MARK_AS_READ = 'NOTIFICATIONS_MARK_AS_READ';
export const NOTIFICATIONS_SET_BROWSER_SUPPORT = 'NOTIFICATIONS_SET_BROWSER_SUPPORT';
export const NOTIFICATIONS_SET_BROWSER_PERMISSION = 'NOTIFICATIONS_SET_BROWSER_PERMISSION';
export const NOTIFICATION_REQUESTS_FETCH_REQUEST = 'NOTIFICATION_REQUESTS_FETCH_REQUEST';
export const NOTIFICATION_REQUESTS_FETCH_SUCCESS = 'NOTIFICATION_REQUESTS_FETCH_SUCCESS';
export const NOTIFICATION_REQUESTS_FETCH_FAIL = 'NOTIFICATION_REQUESTS_FETCH_FAIL';
export const NOTIFICATION_REQUESTS_EXPAND_REQUEST = 'NOTIFICATION_REQUESTS_EXPAND_REQUEST';
export const NOTIFICATION_REQUESTS_EXPAND_SUCCESS = 'NOTIFICATION_REQUESTS_EXPAND_SUCCESS';
export const NOTIFICATION_REQUESTS_EXPAND_FAIL = 'NOTIFICATION_REQUESTS_EXPAND_FAIL';
export const NOTIFICATION_REQUEST_FETCH_REQUEST = 'NOTIFICATION_REQUEST_FETCH_REQUEST';
export const NOTIFICATION_REQUEST_FETCH_SUCCESS = 'NOTIFICATION_REQUEST_FETCH_SUCCESS';
export const NOTIFICATION_REQUEST_FETCH_FAIL = 'NOTIFICATION_REQUEST_FETCH_FAIL';
export const NOTIFICATION_REQUEST_ACCEPT_REQUEST = 'NOTIFICATION_REQUEST_ACCEPT_REQUEST';
export const NOTIFICATION_REQUEST_ACCEPT_SUCCESS = 'NOTIFICATION_REQUEST_ACCEPT_SUCCESS';
export const NOTIFICATION_REQUEST_ACCEPT_FAIL = 'NOTIFICATION_REQUEST_ACCEPT_FAIL';
export const NOTIFICATION_REQUEST_DISMISS_REQUEST = 'NOTIFICATION_REQUEST_DISMISS_REQUEST';
export const NOTIFICATION_REQUEST_DISMISS_SUCCESS = 'NOTIFICATION_REQUEST_DISMISS_SUCCESS';
export const NOTIFICATION_REQUEST_DISMISS_FAIL = 'NOTIFICATION_REQUEST_DISMISS_FAIL';
export const NOTIFICATION_REQUESTS_ACCEPT_REQUEST = 'NOTIFICATION_REQUESTS_ACCEPT_REQUEST';
export const NOTIFICATION_REQUESTS_ACCEPT_SUCCESS = 'NOTIFICATION_REQUESTS_ACCEPT_SUCCESS';
export const NOTIFICATION_REQUESTS_ACCEPT_FAIL = 'NOTIFICATION_REQUESTS_ACCEPT_FAIL';
@@ -72,14 +51,6 @@ export const NOTIFICATION_REQUESTS_DISMISS_REQUEST = 'NOTIFICATION_REQUESTS_DISM
export const NOTIFICATION_REQUESTS_DISMISS_SUCCESS = 'NOTIFICATION_REQUESTS_DISMISS_SUCCESS';
export const NOTIFICATION_REQUESTS_DISMISS_FAIL = 'NOTIFICATION_REQUESTS_DISMISS_FAIL';
export const NOTIFICATIONS_FOR_REQUEST_FETCH_REQUEST = 'NOTIFICATIONS_FOR_REQUEST_FETCH_REQUEST';
export const NOTIFICATIONS_FOR_REQUEST_FETCH_SUCCESS = 'NOTIFICATIONS_FOR_REQUEST_FETCH_SUCCESS';
export const NOTIFICATIONS_FOR_REQUEST_FETCH_FAIL = 'NOTIFICATIONS_FOR_REQUEST_FETCH_FAIL';
export const NOTIFICATIONS_FOR_REQUEST_EXPAND_REQUEST = 'NOTIFICATIONS_FOR_REQUEST_EXPAND_REQUEST';
export const NOTIFICATIONS_FOR_REQUEST_EXPAND_SUCCESS = 'NOTIFICATIONS_FOR_REQUEST_EXPAND_SUCCESS';
export const NOTIFICATIONS_FOR_REQUEST_EXPAND_FAIL = 'NOTIFICATIONS_FOR_REQUEST_EXPAND_FAIL';
defineMessages({
mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
group: { id: 'notifications.group', defaultMessage: '{count} notifications' },
@@ -93,12 +64,6 @@ const fetchRelatedRelationships = (dispatch, notifications) => {
}
};
const selectNotificationCountForRequest = (state, id) => {
const requests = state.getIn(['notificationRequests', 'items']);
const thisRequest = requests.find(request => request.get('id') === id);
return thisRequest ? thisRequest.get('notifications_count') : 0;
};
export const loadPending = () => ({
type: NOTIFICATIONS_LOAD_PENDING,
});
@@ -343,296 +308,3 @@ export function setBrowserPermission (value) {
value,
};
}
export const fetchNotificationRequests = () => (dispatch, getState) => {
const params = {};
if (getState().getIn(['notificationRequests', 'isLoading'])) {
return;
}
if (getState().getIn(['notificationRequests', 'items'])?.size > 0) {
params.since_id = getState().getIn(['notificationRequests', 'items', 0, 'id']);
}
dispatch(fetchNotificationRequestsRequest());
api().get('/api/v1/notifications/requests', { params }).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data.map(x => x.account)));
dispatch(fetchNotificationRequestsSuccess(response.data, next ? next.uri : null));
}).catch(err => {
dispatch(fetchNotificationRequestsFail(err));
});
};
export const fetchNotificationRequestsRequest = () => ({
type: NOTIFICATION_REQUESTS_FETCH_REQUEST,
});
export const fetchNotificationRequestsSuccess = (requests, next) => ({
type: NOTIFICATION_REQUESTS_FETCH_SUCCESS,
requests,
next,
});
export const fetchNotificationRequestsFail = error => ({
type: NOTIFICATION_REQUESTS_FETCH_FAIL,
error,
});
export const expandNotificationRequests = () => (dispatch, getState) => {
const url = getState().getIn(['notificationRequests', 'next']);
if (!url || getState().getIn(['notificationRequests', 'isLoading'])) {
return;
}
dispatch(expandNotificationRequestsRequest());
api().get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data.map(x => x.account)));
dispatch(expandNotificationRequestsSuccess(response.data, next?.uri));
}).catch(err => {
dispatch(expandNotificationRequestsFail(err));
});
};
export const expandNotificationRequestsRequest = () => ({
type: NOTIFICATION_REQUESTS_EXPAND_REQUEST,
});
export const expandNotificationRequestsSuccess = (requests, next) => ({
type: NOTIFICATION_REQUESTS_EXPAND_SUCCESS,
requests,
next,
});
export const expandNotificationRequestsFail = error => ({
type: NOTIFICATION_REQUESTS_EXPAND_FAIL,
error,
});
export const fetchNotificationRequest = id => (dispatch, getState) => {
const current = getState().getIn(['notificationRequests', 'current']);
if (current.getIn(['item', 'id']) === id || current.get('isLoading')) {
return;
}
dispatch(fetchNotificationRequestRequest(id));
api().get(`/api/v1/notifications/requests/${id}`).then(({ data }) => {
dispatch(fetchNotificationRequestSuccess(data));
}).catch(err => {
dispatch(fetchNotificationRequestFail(id, err));
});
};
export const fetchNotificationRequestRequest = id => ({
type: NOTIFICATION_REQUEST_FETCH_REQUEST,
id,
});
export const fetchNotificationRequestSuccess = request => ({
type: NOTIFICATION_REQUEST_FETCH_SUCCESS,
request,
});
export const fetchNotificationRequestFail = (id, error) => ({
type: NOTIFICATION_REQUEST_FETCH_FAIL,
id,
error,
});
export const acceptNotificationRequest = (id) => (dispatch, getState) => {
const count = selectNotificationCountForRequest(getState(), id);
dispatch(acceptNotificationRequestRequest(id));
api().post(`/api/v1/notifications/requests/${id}/accept`).then(() => {
dispatch(acceptNotificationRequestSuccess(id));
dispatch(decreasePendingNotificationsCount(count));
}).catch(err => {
dispatch(acceptNotificationRequestFail(id, err));
});
};
export const acceptNotificationRequestRequest = id => ({
type: NOTIFICATION_REQUEST_ACCEPT_REQUEST,
id,
});
export const acceptNotificationRequestSuccess = id => ({
type: NOTIFICATION_REQUEST_ACCEPT_SUCCESS,
id,
});
export const acceptNotificationRequestFail = (id, error) => ({
type: NOTIFICATION_REQUEST_ACCEPT_FAIL,
id,
error,
});
export const dismissNotificationRequest = (id) => (dispatch, getState) => {
const count = selectNotificationCountForRequest(getState(), id);
dispatch(dismissNotificationRequestRequest(id));
api().post(`/api/v1/notifications/requests/${id}/dismiss`).then(() =>{
dispatch(dismissNotificationRequestSuccess(id));
dispatch(decreasePendingNotificationsCount(count));
}).catch(err => {
dispatch(dismissNotificationRequestFail(id, err));
});
};
export const dismissNotificationRequestRequest = id => ({
type: NOTIFICATION_REQUEST_DISMISS_REQUEST,
id,
});
export const dismissNotificationRequestSuccess = id => ({
type: NOTIFICATION_REQUEST_DISMISS_SUCCESS,
id,
});
export const dismissNotificationRequestFail = (id, error) => ({
type: NOTIFICATION_REQUEST_DISMISS_FAIL,
id,
error,
});
export const acceptNotificationRequests = (ids) => (dispatch, getState) => {
const count = ids.reduce((count, id) => count + selectNotificationCountForRequest(getState(), id), 0);
dispatch(acceptNotificationRequestsRequest(ids));
api().post(`/api/v1/notifications/requests/accept`, { id: ids }).then(() => {
dispatch(acceptNotificationRequestsSuccess(ids));
dispatch(decreasePendingNotificationsCount(count));
}).catch(err => {
dispatch(acceptNotificationRequestFail(ids, err));
});
};
export const acceptNotificationRequestsRequest = ids => ({
type: NOTIFICATION_REQUESTS_ACCEPT_REQUEST,
ids,
});
export const acceptNotificationRequestsSuccess = ids => ({
type: NOTIFICATION_REQUESTS_ACCEPT_SUCCESS,
ids,
});
export const acceptNotificationRequestsFail = (ids, error) => ({
type: NOTIFICATION_REQUESTS_ACCEPT_FAIL,
ids,
error,
});
export const dismissNotificationRequests = (ids) => (dispatch, getState) => {
const count = ids.reduce((count, id) => count + selectNotificationCountForRequest(getState(), id), 0);
dispatch(acceptNotificationRequestsRequest(ids));
api().post(`/api/v1/notifications/requests/dismiss`, { id: ids }).then(() => {
dispatch(dismissNotificationRequestsSuccess(ids));
dispatch(decreasePendingNotificationsCount(count));
}).catch(err => {
dispatch(dismissNotificationRequestFail(ids, err));
});
};
export const dismissNotificationRequestsRequest = ids => ({
type: NOTIFICATION_REQUESTS_DISMISS_REQUEST,
ids,
});
export const dismissNotificationRequestsSuccess = ids => ({
type: NOTIFICATION_REQUESTS_DISMISS_SUCCESS,
ids,
});
export const dismissNotificationRequestsFail = (ids, error) => ({
type: NOTIFICATION_REQUESTS_DISMISS_FAIL,
ids,
error,
});
export const fetchNotificationsForRequest = accountId => (dispatch, getState) => {
const current = getState().getIn(['notificationRequests', 'current']);
const params = { account_id: accountId };
if (current.getIn(['item', 'account']) === accountId) {
if (current.getIn(['notifications', 'isLoading'])) {
return;
}
if (current.getIn(['notifications', 'items'])?.size > 0) {
params.since_id = current.getIn(['notifications', 'items', 0, 'id']);
}
}
dispatch(fetchNotificationsForRequestRequest());
api().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(importFetchedAccounts(response.data.filter(item => item.report).map(item => item.report.target_account)));
dispatch(fetchNotificationsForRequestSuccess(response.data, next?.uri));
}).catch(err => {
dispatch(fetchNotificationsForRequestFail(err));
});
};
export const fetchNotificationsForRequestRequest = () => ({
type: NOTIFICATIONS_FOR_REQUEST_FETCH_REQUEST,
});
export const fetchNotificationsForRequestSuccess = (notifications, next) => ({
type: NOTIFICATIONS_FOR_REQUEST_FETCH_SUCCESS,
notifications,
next,
});
export const fetchNotificationsForRequestFail = (error) => ({
type: NOTIFICATIONS_FOR_REQUEST_FETCH_FAIL,
error,
});
export const expandNotificationsForRequest = () => (dispatch, getState) => {
const url = getState().getIn(['notificationRequests', 'current', 'notifications', 'next']);
if (!url || getState().getIn(['notificationRequests', 'current', 'notifications', 'isLoading'])) {
return;
}
dispatch(expandNotificationsForRequestRequest());
api().get(url).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(importFetchedAccounts(response.data.filter(item => item.report).map(item => item.report.target_account)));
dispatch(expandNotificationsForRequestSuccess(response.data, next?.uri));
}).catch(err => {
dispatch(expandNotificationsForRequestFail(err));
});
};
export const expandNotificationsForRequestRequest = () => ({
type: NOTIFICATIONS_FOR_REQUEST_EXPAND_REQUEST,
});
export const expandNotificationsForRequestSuccess = (notifications, next) => ({
type: NOTIFICATIONS_FOR_REQUEST_EXPAND_SUCCESS,
notifications,
next,
});
export const expandNotificationsForRequestFail = (error) => ({
type: NOTIFICATIONS_FOR_REQUEST_EXPAND_FAIL,
error,
});

View File

@@ -1,14 +1,43 @@
import api, { apiRequest, getLinks } from 'mastodon/api';
import type { ApiNotificationGroupsResultJSON } from 'mastodon/api_types/notifications';
import api, {
apiRequest,
getLinks,
apiRequestGet,
apiRequestPost,
} from 'mastodon/api';
import type {
ApiNotificationGroupsResultJSON,
ApiNotificationRequestJSON,
ApiNotificationJSON,
} from 'mastodon/api_types/notifications';
export const apiFetchNotifications = async (params?: {
export const apiFetchNotifications = async (
params?: {
account_id?: string;
since_id?: string;
},
url?: string,
) => {
const response = await api().request<ApiNotificationJSON[]>({
method: 'GET',
url: url ?? '/api/v1/notifications',
params,
});
return {
notifications: response.data,
links: getLinks(response),
};
};
export const apiFetchNotificationGroups = async (params?: {
url?: string;
exclude_types?: string[];
max_id?: string;
since_id?: string;
}) => {
const response = await api().request<ApiNotificationGroupsResultJSON>({
method: 'GET',
url: '/api/v2_alpha/notifications',
url: '/api/v2/notifications',
params,
});
@@ -24,3 +53,43 @@ export const apiFetchNotifications = async (params?: {
export const apiClearNotifications = () =>
apiRequest<undefined>('POST', 'v1/notifications/clear');
export const apiFetchNotificationRequests = async (
params?: {
since_id?: string;
},
url?: string,
) => {
const response = await api().request<ApiNotificationRequestJSON[]>({
method: 'GET',
url: url ?? '/api/v1/notifications/requests',
params,
});
return {
requests: response.data,
links: getLinks(response),
};
};
export const apiFetchNotificationRequest = async (id: string) => {
return apiRequestGet<ApiNotificationRequestJSON>(
`v1/notifications/requests/${id}`,
);
};
export const apiAcceptNotificationRequest = async (id: string) => {
return apiRequestPost(`v1/notifications/requests/${id}/accept`);
};
export const apiDismissNotificationRequest = async (id: string) => {
return apiRequestPost(`v1/notifications/requests/${id}/dismiss`);
};
export const apiAcceptNotificationRequests = async (id: string[]) => {
return apiRequestPost('v1/notifications/requests/accept', { id });
};
export const apiDismissNotificationRequests = async (id: string[]) => {
return apiRequestPost('v1/notifications/dismiss/dismiss', { id });
};

View File

@@ -149,3 +149,12 @@ export interface ApiNotificationGroupsResultJSON {
statuses: ApiStatusJSON[];
notification_groups: ApiNotificationGroupJSON[];
}
export interface ApiNotificationRequestJSON {
id: string;
created_at: string;
updated_at: string;
notifications_count: string;
account: ApiAccountJSON;
last_status?: ApiStatusJSON;
}

View File

@@ -12,7 +12,7 @@ import DeleteIcon from '@/material-icons/400-24px/delete.svg?react';
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
import { initBlockModal } from 'mastodon/actions/blocks';
import { initMuteModal } from 'mastodon/actions/mutes';
import { acceptNotificationRequest, dismissNotificationRequest } from 'mastodon/actions/notifications';
import { acceptNotificationRequest, dismissNotificationRequest } from 'mastodon/actions/notification_requests';
import { initReport } from 'mastodon/actions/reports';
import { Avatar } from 'mastodon/components/avatar';
import { CheckBox } from 'mastodon/components/check_box';
@@ -40,11 +40,11 @@ export const NotificationRequest = ({ id, accountId, notificationsCount, checked
const { push: historyPush } = useHistory();
const handleDismiss = useCallback(() => {
dispatch(dismissNotificationRequest(id));
dispatch(dismissNotificationRequest({ id }));
}, [dispatch, id]);
const handleAccept = useCallback(() => {
dispatch(acceptNotificationRequest(id));
dispatch(acceptNotificationRequest({ id }));
}, [dispatch, id]);
const handleMute = useCallback(() => {

View File

@@ -10,7 +10,13 @@ import { useSelector, useDispatch } from 'react-redux';
import DeleteIcon from '@/material-icons/400-24px/delete.svg?react';
import DoneIcon from '@/material-icons/400-24px/done.svg?react';
import InventoryIcon from '@/material-icons/400-24px/inventory_2.svg?react';
import { fetchNotificationRequest, fetchNotificationsForRequest, expandNotificationsForRequest, acceptNotificationRequest, dismissNotificationRequest } from 'mastodon/actions/notifications';
import {
fetchNotificationRequest,
fetchNotificationsForRequest,
expandNotificationsForRequest,
acceptNotificationRequest,
dismissNotificationRequest,
} from 'mastodon/actions/notification_requests';
import Column from 'mastodon/components/column';
import ColumnHeader from 'mastodon/components/column_header';
import { IconButton } from 'mastodon/components/icon_button';
@@ -44,28 +50,28 @@ export const NotificationRequest = ({ multiColumn, params: { id } }) => {
const columnRef = useRef();
const intl = useIntl();
const dispatch = useDispatch();
const notificationRequest = useSelector(state => state.getIn(['notificationRequests', 'current', 'item', 'id']) === id ? state.getIn(['notificationRequests', 'current', 'item']) : null);
const accountId = notificationRequest?.get('account');
const notificationRequest = useSelector(state => state.notificationRequests.current.item?.id === id ? state.notificationRequests.current.item : null);
const accountId = notificationRequest?.account_id;
const account = useSelector(state => state.getIn(['accounts', accountId]));
const notifications = useSelector(state => state.getIn(['notificationRequests', 'current', 'notifications', 'items']));
const isLoading = useSelector(state => state.getIn(['notificationRequests', 'current', 'notifications', 'isLoading']));
const hasMore = useSelector(state => !!state.getIn(['notificationRequests', 'current', 'notifications', 'next']));
const removed = useSelector(state => state.getIn(['notificationRequests', 'current', 'removed']));
const notifications = useSelector(state => state.notificationRequests.current.notifications.items);
const isLoading = useSelector(state => state.notificationRequests.current.notifications.isLoading);
const hasMore = useSelector(state => !!state.notificationRequests.current.notifications.next);
const removed = useSelector(state => state.notificationRequests.current.removed);
const handleHeaderClick = useCallback(() => {
columnRef.current?.scrollTop();
}, [columnRef]);
const handleLoadMore = useCallback(() => {
dispatch(expandNotificationsForRequest());
}, [dispatch]);
dispatch(expandNotificationsForRequest({ accountId }));
}, [dispatch, accountId]);
const handleDismiss = useCallback(() => {
dispatch(dismissNotificationRequest(id));
dispatch(dismissNotificationRequest({ id }));
}, [dispatch, id]);
const handleAccept = useCallback(() => {
dispatch(acceptNotificationRequest(id));
dispatch(acceptNotificationRequest({ id }));
}, [dispatch, id]);
const handleMoveUp = useCallback(id => {
@@ -79,12 +85,12 @@ export const NotificationRequest = ({ multiColumn, params: { id } }) => {
}, [columnRef, notifications]);
useEffect(() => {
dispatch(fetchNotificationRequest(id));
dispatch(fetchNotificationRequest({ id }));
}, [dispatch, id]);
useEffect(() => {
if (accountId) {
dispatch(fetchNotificationsForRequest(accountId));
dispatch(fetchNotificationsForRequest({ accountId }));
}
}, [dispatch, accountId]);

View File

@@ -11,7 +11,12 @@ import ArrowDropDownIcon from '@/material-icons/400-24px/arrow_drop_down.svg?rea
import InventoryIcon from '@/material-icons/400-24px/inventory_2.svg?react';
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
import { openModal } from 'mastodon/actions/modal';
import { fetchNotificationRequests, expandNotificationRequests, acceptNotificationRequests, dismissNotificationRequests } from 'mastodon/actions/notifications';
import {
fetchNotificationRequests,
expandNotificationRequests,
acceptNotificationRequests,
dismissNotificationRequests,
} from 'mastodon/actions/notification_requests';
import { changeSetting } from 'mastodon/actions/settings';
import { CheckBox } from 'mastodon/components/check_box';
import Column from 'mastodon/components/column';
@@ -84,7 +89,7 @@ const SelectRow = ({selectAllChecked, toggleSelectAll, selectedItems, selectionM
message: intl.formatMessage(messages.confirmAcceptMultipleMessage, { count: selectedItems.length }),
confirm: intl.formatMessage(messages.confirmAcceptMultipleButton, { count: selectedItems.length}),
onConfirm: () =>
dispatch(acceptNotificationRequests(selectedItems)),
dispatch(acceptNotificationRequests({ ids: selectedItems })),
},
}));
}, [dispatch, intl, selectedItems]);
@@ -97,7 +102,7 @@ const SelectRow = ({selectAllChecked, toggleSelectAll, selectedItems, selectionM
message: intl.formatMessage(messages.confirmDismissMultipleMessage, { count: selectedItems.length }),
confirm: intl.formatMessage(messages.confirmDismissMultipleButton, { count: selectedItems.length}),
onConfirm: () =>
dispatch(dismissNotificationRequests(selectedItems)),
dispatch(dismissNotificationRequests({ ids: selectedItems })),
},
}));
}, [dispatch, intl, selectedItems]);
@@ -161,9 +166,9 @@ export const NotificationRequests = ({ multiColumn }) => {
const columnRef = useRef();
const intl = useIntl();
const dispatch = useDispatch();
const isLoading = useSelector(state => state.getIn(['notificationRequests', 'isLoading']));
const notificationRequests = useSelector(state => state.getIn(['notificationRequests', 'items']));
const hasMore = useSelector(state => !!state.getIn(['notificationRequests', 'next']));
const isLoading = useSelector(state => state.notificationRequests.isLoading);
const notificationRequests = useSelector(state => state.notificationRequests.items);
const hasMore = useSelector(state => !!state.notificationRequests.next);
const [selectionMode, setSelectionMode] = useState(false);
const [checkedRequestIds, setCheckedRequestIds] = useState([]);
@@ -182,7 +187,7 @@ export const NotificationRequests = ({ multiColumn }) => {
else
ids.push(id);
setSelectAllChecked(ids.length === notificationRequests.size);
setSelectAllChecked(ids.length === notificationRequests.length);
return [...ids];
});
@@ -193,7 +198,7 @@ export const NotificationRequests = ({ multiColumn }) => {
if(checked)
setCheckedRequestIds([]);
else
setCheckedRequestIds(notificationRequests.map(request => request.get('id')).toArray());
setCheckedRequestIds(notificationRequests.map(request => request.id));
return !checked;
});
@@ -217,7 +222,7 @@ export const NotificationRequests = ({ multiColumn }) => {
multiColumn={multiColumn}
showBackButton
appendContent={
notificationRequests.size > 0 && (
notificationRequests.length > 0 && (
<SelectRow selectionMode={selectionMode} setSelectionMode={setSelectionMode} selectAllChecked={selectAllChecked} toggleSelectAll={toggleSelectAll} selectedItems={checkedRequestIds} />
)}
>
@@ -236,12 +241,12 @@ export const NotificationRequests = ({ multiColumn }) => {
>
{notificationRequests.map(request => (
<NotificationRequest
key={request.get('id')}
id={request.get('id')}
accountId={request.get('account')}
notificationsCount={request.get('notifications_count')}
key={request.id}
id={request.id}
accountId={request.account_id}
notificationsCount={request.notifications_count}
showCheckbox={selectionMode}
checked={checkedRequestIds.includes(request.get('id'))}
checked={checkedRequestIds.includes(request.id)}
toggleCheck={handleCheck}
/>
))}

View File

@@ -789,6 +789,7 @@
"status.edit": "Edita",
"status.edited": "Darrera edició {date}",
"status.edited_x_times": "Editat {count, plural, one {{count} vegada} other {{count} vegades}}",
"status.embed": "Obté el codi encastat",
"status.favourite": "Favorit",
"status.favourites": "{count, plural, one {favorit} other {favorits}}",
"status.filter": "Filtra aquest tut",

View File

@@ -789,6 +789,7 @@
"status.edit": "Edit",
"status.edited": "Last edited {date}",
"status.edited_x_times": "Edited {count, plural, one {{count} time} other {{count} times}}",
"status.embed": "Get embed code",
"status.favourite": "Favourite",
"status.favourites": "{count, plural, one {favorite} other {favorites}}",
"status.filter": "Filter this post",

View File

@@ -789,6 +789,7 @@
"status.edit": "Editar",
"status.edited": "Última edición: {date}",
"status.edited_x_times": "Editado {count, plural, one {{count} vez} other {{count} veces}}",
"status.embed": "Obtener código para insertar",
"status.favourite": "Marcar como favorito",
"status.favourites": "{count, plural, one {# voto} other {# votos}}",
"status.filter": "Filtrar este mensaje",

View File

@@ -376,7 +376,7 @@
"ignore_notifications_modal.ignore": "Ignorar notificaciones",
"ignore_notifications_modal.limited_accounts_title": "¿Ignorar notificaciones de cuentas moderadas?",
"ignore_notifications_modal.new_accounts_title": "¿Ignorar notificaciones de cuentas nuevas?",
"ignore_notifications_modal.not_followers_title": "¿Ignorar notificaciones de personas que no te sigue?",
"ignore_notifications_modal.not_followers_title": "¿Ignorar notificaciones de personas que no te siguen?",
"ignore_notifications_modal.not_following_title": "¿Ignorar notificaciones de personas a las que no sigues?",
"ignore_notifications_modal.private_mentions_title": "¿Ignorar notificaciones de menciones privadas no solicitadas?",
"interaction_modal.description.favourite": "Con una cuenta en Mastodon, puedes marcar como favorita esta publicación para que el autor sepa que te gusta, y guardala para más adelante.",
@@ -789,6 +789,7 @@
"status.edit": "Editar",
"status.edited": "Última edición {date}",
"status.edited_x_times": "Editado {count, plural, one {{count} time} other {{count} veces}}",
"status.embed": "Obtener código para incrustar",
"status.favourite": "Favorito",
"status.favourites": "{count, plural, one {favorito} other {favoritos}}",
"status.filter": "Filtrar esta publicación",

View File

@@ -376,7 +376,7 @@
"ignore_notifications_modal.ignore": "Ignorar notificaciones",
"ignore_notifications_modal.limited_accounts_title": "¿Ignorar notificaciones de cuentas moderadas?",
"ignore_notifications_modal.new_accounts_title": "¿Ignorar notificaciones de cuentas nuevas?",
"ignore_notifications_modal.not_followers_title": "¿Ignorar notificaciones de personas que no te sigue?",
"ignore_notifications_modal.not_followers_title": "¿Ignorar notificaciones de personas que no te siguen?",
"ignore_notifications_modal.not_following_title": "¿Ignorar notificaciones de personas a las que no sigues?",
"ignore_notifications_modal.private_mentions_title": "¿Ignorar notificaciones de menciones privadas no solicitadas?",
"interaction_modal.description.favourite": "Con una cuenta en Mastodon, puedes marcar como favorita esta publicación para que el autor sepa que te gusta, y guardala para más adelante.",
@@ -789,6 +789,7 @@
"status.edit": "Editar",
"status.edited": "Última edición {date}",
"status.edited_x_times": "Editado {count, plural, one {{count} vez} other {{count} veces}}",
"status.embed": "Obtener código para incrustar",
"status.favourite": "Favorito",
"status.favourites": "{count, plural, one {favorito} other {favoritos}}",
"status.filter": "Filtrar esta publicación",

View File

@@ -457,6 +457,7 @@
"lists.subheading": "Sinu nimekirjad",
"load_pending": "{count, plural, one {# uus kirje} other {# uut kirjet}}",
"loading_indicator.label": "Laadimine…",
"media_gallery.hide": "Peida",
"moved_to_account_banner.text": "Kontot {disabledAccount} ei ole praegu võimalik kasutada, sest kolisid kontole {movedToAccount}.",
"mute_modal.hide_from_notifications": "Peida teavituste hulgast",
"mute_modal.hide_options": "Peida valikud",
@@ -779,6 +780,7 @@
"status.bookmark": "Järjehoidja",
"status.cancel_reblog_private": "Lõpeta jagamine",
"status.cannot_reblog": "Seda postitust ei saa jagada",
"status.continued_thread": "Jätkatud lõim",
"status.copy": "Kopeeri postituse link",
"status.delete": "Kustuta",
"status.detailed_status": "Detailne vestluskuva",
@@ -787,6 +789,7 @@
"status.edit": "Muuda",
"status.edited": "Viimati muudetud {date}",
"status.edited_x_times": "Muudetud {count, plural, one{{count} kord} other {{count} korda}}",
"status.embed": "Hangi manustamiskood",
"status.favourite": "Lemmik",
"status.favourites": "{count, plural, one {lemmik} other {lemmikud}}",
"status.filter": "Filtreeri seda postitust",
@@ -811,6 +814,7 @@
"status.reblogs.empty": "Keegi pole seda postitust veel jaganud. Kui keegi seda teeb, näeb seda siin.",
"status.redraft": "Kustuta & alga uuesti",
"status.remove_bookmark": "Eemalda järjehoidja",
"status.replied_in_thread": "Vastatud lõimes",
"status.replied_to": "Vastas kasutajale {name}",
"status.reply": "Vasta",
"status.replyAll": "Vasta lõimele",

View File

@@ -457,6 +457,7 @@
"lists.subheading": "Zure zerrendak",
"load_pending": "{count, plural, one {elementu berri #} other {# elementu berri}}",
"loading_indicator.label": "Kargatzen…",
"media_gallery.hide": "Ezkutatu",
"moved_to_account_banner.text": "Zure {disabledAccount} kontua desgaituta dago une honetan, {movedToAccount} kontura aldatu zinelako.",
"mute_modal.hide_from_notifications": "Ezkutatu jakinarazpenetatik",
"mute_modal.hide_options": "Ezkutatu aukerak",
@@ -775,6 +776,7 @@
"status.bookmark": "Laster-marka",
"status.cancel_reblog_private": "Kendu bultzada",
"status.cannot_reblog": "Bidalketa honi ezin zaio bultzada eman",
"status.continued_thread": "Harian jarraitu zuen",
"status.copy": "Kopiatu bidalketaren esteka",
"status.delete": "Ezabatu",
"status.detailed_status": "Elkarrizketaren ikuspegi xehetsua",
@@ -783,6 +785,7 @@
"status.edit": "Editatu",
"status.edited": "Azken edizioa: {date}",
"status.edited_x_times": "{count, plural, one {behin} other {{count} aldiz}} editatua",
"status.embed": "Lortu txertatzeko kodea",
"status.favourite": "Gogokoa",
"status.favourites": "{count, plural, one {gogoko} other {gogoko}}",
"status.filter": "Iragazi bidalketa hau",
@@ -807,6 +810,7 @@
"status.reblogs.empty": "Inork ez dio bultzada eman bidalketa honi oraindik. Inork egiten badu, hemen agertuko da.",
"status.redraft": "Ezabatu eta berridatzi",
"status.remove_bookmark": "Kendu laster-marka",
"status.replied_in_thread": "Harian erantzun zuen",
"status.replied_to": "{name} erabiltzaileari erantzuna",
"status.reply": "Erantzun",
"status.replyAll": "Erantzun harian",

View File

@@ -789,6 +789,7 @@
"status.edit": "Rætta",
"status.edited": "Seinast broytt {date}",
"status.edited_x_times": "Rættað {count, plural, one {{count} ferð} other {{count} ferð}}",
"status.embed": "Fá kodu at seta inn",
"status.favourite": "Dámdur postur",
"status.favourites": "{count, plural, one {yndispostur} other {yndispostar}}",
"status.filter": "Filtrera hendan postin",

View File

@@ -36,6 +36,7 @@
"account.followers.empty": "Personne ne suit ce compte pour l'instant.",
"account.followers_counter": "{count, plural, one {{counter} abonné·e} other {{counter} abonné·e·s}}",
"account.following": "Abonné·e",
"account.following_counter": "{count, plural, one {{counter} abonnement} other {{counter} abonnements}}",
"account.follows.empty": "Ce compte ne suit personne présentement.",
"account.go_to_profile": "Voir ce profil",
"account.hide_reblogs": "Masquer les boosts de @{name}",
@@ -788,6 +789,7 @@
"status.edit": "Modifier",
"status.edited": "Dernière modification le {date}",
"status.edited_x_times": "Modifiée {count, plural, one {{count} fois} other {{count} fois}}",
"status.embed": "Obtenir le code d'intégration",
"status.favourite": "Ajouter aux favoris",
"status.favourites": "{count, plural, one {favori} other {favoris}}",
"status.filter": "Filtrer cette publication",
@@ -812,6 +814,7 @@
"status.reblogs.empty": "Personne na encore boosté cette publication. Lorsque quelquun le fera, elle apparaîtra ici.",
"status.redraft": "Supprimer et réécrire",
"status.remove_bookmark": "Retirer des signets",
"status.replied_in_thread": "A répondu dans un fil de discussion",
"status.replied_to": "A répondu à {name}",
"status.reply": "Répondre",
"status.replyAll": "Répondre à cette discussion",

View File

@@ -36,6 +36,7 @@
"account.followers.empty": "Personne ne suit cet·te utilisateur·rice pour linstant.",
"account.followers_counter": "{count, plural, one {{counter} abonné·e} other {{counter} abonné·e·s}}",
"account.following": "Abonnements",
"account.following_counter": "{count, plural, one {{counter} abonnement} other {{counter} abonnements}}",
"account.follows.empty": "Cet·te utilisateur·rice ne suit personne pour linstant.",
"account.go_to_profile": "Aller au profil",
"account.hide_reblogs": "Masquer les partages de @{name}",
@@ -788,6 +789,7 @@
"status.edit": "Modifier",
"status.edited": "Dernière modification le {date}",
"status.edited_x_times": "Modifié {count, plural, one {{count} fois} other {{count} fois}}",
"status.embed": "Obtenir le code d'intégration",
"status.favourite": "Ajouter aux favoris",
"status.favourites": "{count, plural, one {favori} other {favoris}}",
"status.filter": "Filtrer ce message",
@@ -812,6 +814,7 @@
"status.reblogs.empty": "Personne na encore partagé ce message. Lorsque quelquun le fera, il apparaîtra ici.",
"status.redraft": "Supprimer et réécrire",
"status.remove_bookmark": "Retirer des marque-pages",
"status.replied_in_thread": "A répondu dans un fil de discussion",
"status.replied_to": "En réponse à {name}",
"status.reply": "Répondre",
"status.replyAll": "Répondre au fil",

View File

@@ -457,6 +457,7 @@
"lists.subheading": "Do liostaí",
"load_pending": "{count, plural, one {# mír nua} two {# mír nua} few {# mír nua} many {# mír nua} other {# mír nua}}",
"loading_indicator.label": "Á lódáil…",
"media_gallery.hide": "Folaigh",
"moved_to_account_banner.text": "Tá do chuntas {disabledAccount} díchumasaithe faoi láthair toisc gur bhog tú go {movedToAccount}.",
"mute_modal.hide_from_notifications": "Folaigh ó fhógraí",
"mute_modal.hide_options": "Folaigh roghanna",
@@ -779,6 +780,7 @@
"status.bookmark": "Leabharmharcanna",
"status.cancel_reblog_private": "Dímhol",
"status.cannot_reblog": "Ní féidir an phostáil seo a mholadh",
"status.continued_thread": "Snáithe ar lean",
"status.copy": "Cóipeáil an nasc chuig an bpostáil",
"status.delete": "Scrios",
"status.detailed_status": "Amharc comhrá mionsonraithe",
@@ -787,6 +789,7 @@
"status.edit": "Cuir in eagar",
"status.edited": "Arna chuir in eagar anuas {date}",
"status.edited_x_times": "Curtha in eagar {count, plural, one {{count} uair amháin} two {{count} uair} few {{count} uair} many {{count} uair} other {{count} uair}}",
"status.embed": "Faigh cód leabú",
"status.favourite": "Is fearr leat",
"status.favourites": "{count, plural, one {a bhfuil grá agat do} two {gráite} few {gráite} many {gráite} other {gráite}}",
"status.filter": "Déan scagadh ar an bpostáil seo",
@@ -811,6 +814,7 @@
"status.reblogs.empty": "Níor mhol éinne an phostáil seo fós. Nuair a mholfaidh duine éigin í, taispeánfar anseo é sin.",
"status.redraft": "Scrios ⁊ athdhréachtaigh",
"status.remove_bookmark": "Bain leabharmharc",
"status.replied_in_thread": "D'fhreagair sa snáithe",
"status.replied_to": "D'fhreagair {name}",
"status.reply": "Freagair",
"status.replyAll": "Freagair le snáithe",

View File

@@ -457,6 +457,7 @@
"lists.subheading": "Na liostaichean agad",
"load_pending": "{count, plural, one {# nì ùr} two {# nì ùr} few {# nithean ùra} other {# nì ùr}}",
"loading_indicator.label": "Ga luchdadh…",
"media_gallery.hide": "Falaich",
"moved_to_account_banner.text": "Tha an cunntas {disabledAccount} agad à comas on a rinn thu imrich gu {movedToAccount}.",
"mute_modal.hide_from_notifications": "Falaich o na brathan",
"mute_modal.hide_options": "Roghainnean falaich",
@@ -779,6 +780,7 @@
"status.bookmark": "Cuir ris na comharran-lìn",
"status.cancel_reblog_private": "Na brosnaich tuilleadh",
"status.cannot_reblog": "Cha ghabh am post seo brosnachadh",
"status.continued_thread": "Pàirt de shnàithlean",
"status.copy": "Dèan lethbhreac dhen cheangal dhan phost",
"status.delete": "Sguab às",
"status.detailed_status": "Mion-shealladh a chòmhraidh",
@@ -787,6 +789,7 @@
"status.edit": "Deasaich",
"status.edited": "An deasachadh mu dheireadh {date}",
"status.edited_x_times": "Chaidh a dheasachadh {count, plural, one {{count} turas} two {{count} thuras} few {{count} tursan} other {{count} turas}}",
"status.embed": "Faigh còd leabachaidh",
"status.favourite": "Cuir ris na h-annsachdan",
"status.favourites": "{count, plural, one {annsachd} two {annsachd} few {annsachdan} other {annsachd}}",
"status.filter": "Criathraich am post seo",
@@ -811,6 +814,7 @@
"status.reblogs.empty": "Chan deach am post seo a bhrosnachadh le duine sam bith fhathast. Nuair a bhrosnaicheas cuideigin e, nochdaidh iad an-seo.",
"status.redraft": "Sguab às ⁊ dèan dreachd ùr",
"status.remove_bookmark": "Thoir an comharra-lìn air falbh",
"status.replied_in_thread": "Freagairt do shnàithlean",
"status.replied_to": "Air {name} fhreagairt",
"status.reply": "Freagair",
"status.replyAll": "Freagair dhan t-snàithlean",

View File

@@ -355,6 +355,7 @@
"hints.profiles.see_more_followers": "Vider plus de sequitores sur {domain}",
"hints.profiles.see_more_follows": "Vider plus de sequites sur {domain}",
"hints.profiles.see_more_posts": "Vider plus de messages sur {domain}",
"hints.threads.replies_may_be_missing": "Responsas de altere servitores pote esser perdite.",
"hints.threads.see_more": "Vider plus de responsas sur {domain}",
"home.column_settings.show_reblogs": "Monstrar impulsos",
"home.column_settings.show_replies": "Monstrar responsas",

View File

@@ -789,6 +789,7 @@
"status.edit": "Breyta",
"status.edited": "Síðast breytt {date}",
"status.edited_x_times": "Breytt {count, plural, one {{count} sinni} other {{count} sinnum}}",
"status.embed": "Ná í innfellanlegan kóða",
"status.favourite": "Eftirlæti",
"status.favourites": "{count, plural, one {eftirlæti} other {eftirlæti}}",
"status.filter": "Sía þessa færslu",

View File

@@ -371,6 +371,14 @@
"ignore_notifications_modal.disclaimer": "O Mastodon não pode informar utilizadores que ignoraste as notificações deles. Ignorar notificações não irá parar as mensagens serem enviadas.",
"ignore_notifications_modal.filter_instead": "Filtrar em vez disso",
"ignore_notifications_modal.filter_to_act_users": "Ainda poderá aceitar, rejeitar, ou reportar utilizadores",
"ignore_notifications_modal.filter_to_avoid_confusion": "A filtragem ajuda a evitar potenciais equívocos",
"ignore_notifications_modal.filter_to_review_separately": "Pode rever as notificações filtradas separadamente",
"ignore_notifications_modal.ignore": "Ignorar notificações",
"ignore_notifications_modal.limited_accounts_title": "Ignorar notificações de contas moderadas?",
"ignore_notifications_modal.new_accounts_title": "Ignorar notificações de contas novas?",
"ignore_notifications_modal.not_followers_title": "Ignorar notificações de pessoas que não o seguem?",
"ignore_notifications_modal.not_following_title": "Ignorar notificações de pessoas que não segue?",
"ignore_notifications_modal.private_mentions_title": "Ignorar notificações de Menções Privadas não solicitadas?",
"interaction_modal.description.favourite": "Com uma conta no Mastodon, pode adicionar assinalar esta publicação como favorita para que o autor saiba que gostou e guardá-la para mais tarde.",
"interaction_modal.description.follow": "Com uma conta no Mastodon, pode seguir {name} para receber as suas publicações na sua página inicial.",
"interaction_modal.description.reblog": "Com uma conta no Mastodon, pode impulsionar esta publicação para compartilhá-lo com os seus seguidores.",
@@ -449,6 +457,7 @@
"lists.subheading": "As tuas listas",
"load_pending": "{count, plural, one {# novo item} other {# novos itens}}",
"loading_indicator.label": "A carregar…",
"media_gallery.hide": "Esconder",
"moved_to_account_banner.text": "A sua conta {disabledAccount} está, no momento, desativada, porque você migrou para {movedToAccount}.",
"mute_modal.hide_from_notifications": "Ocultar das notificações",
"mute_modal.hide_options": "Ocultar opções",
@@ -460,6 +469,7 @@
"mute_modal.you_wont_see_mentions": "Não verá publicações que os mencionem.",
"mute_modal.you_wont_see_posts": "Eles podem continuar a ver as suas publicações, mas você não verá as deles.",
"navigation_bar.about": "Sobre",
"navigation_bar.administration": "Administração",
"navigation_bar.advanced_interface": "Abrir na interface web avançada",
"navigation_bar.blocks": "Utilizadores bloqueados",
"navigation_bar.bookmarks": "Marcadores",
@@ -476,6 +486,7 @@
"navigation_bar.follows_and_followers": "Seguindo e seguidores",
"navigation_bar.lists": "Listas",
"navigation_bar.logout": "Sair",
"navigation_bar.moderation": "Moderação",
"navigation_bar.mutes": "Utilizadores silenciados",
"navigation_bar.opened_in_classic_interface": "Por norma, publicações, contas, e outras páginas específicas são abertas na interface web clássica.",
"navigation_bar.personal": "Pessoal",
@@ -491,9 +502,13 @@
"notification.admin.report_statuses": "{name} denunicou {target} por {category}",
"notification.admin.report_statuses_other": "{name} denunciou {target}",
"notification.admin.sign_up": "{name} inscreveu-se",
"notification.admin.sign_up.name_and_others": "{name} e {count, plural, one {# outro} other {# outros}} inscreveram-se",
"notification.favourite": "{name} assinalou a sua publicação como favorita",
"notification.favourite.name_and_others_with_link": "{name} e <a>{count, plural, one {# outro} other {# outros}}</a> assinalou a sua publicação como favorita",
"notification.follow": "{name} começou a seguir-te",
"notification.follow.name_and_others": "{name} e {count, plural, one {# outro} other {# outros}} começaram a segui-lo",
"notification.follow_request": "{name} pediu para segui-lo",
"notification.follow_request.name_and_others": "{name} e {count, plural, one {# outro} other {# outros}} pediram para segui-lo",
"notification.label.mention": "Menção",
"notification.label.private_mention": "Menção privada",
"notification.label.private_reply": "Resposta privada",
@@ -511,6 +526,7 @@
"notification.own_poll": "A sua sondagem terminou",
"notification.poll": "Terminou uma sondagem em que votou",
"notification.reblog": "{name} reforçou a tua publicação",
"notification.reblog.name_and_others_with_link": "{name} e <a>{count, plural, one {# outro} other {# outros}}</a> reforçaram a sua publicação",
"notification.relationships_severance_event": "Perdeu as ligações com {name}",
"notification.relationships_severance_event.account_suspension": "Um administrador de {from} suspendeu {target}, o que significa que já não pode receber atualizações dele ou interagir com ele.",
"notification.relationships_severance_event.domain_block": "Um administrador de {from} bloqueou {target}, incluindo {followersCount} dos seus seguidores e {followingCount, plural, one {# conta} other {# contas}} que segue.",
@@ -519,13 +535,24 @@
"notification.status": "{name} acabou de publicar",
"notification.update": "{name} editou uma publicação",
"notification_requests.accept": "Aceitar",
"notification_requests.accept_multiple": "{count, plural, one {Aceitar # pedidos…} other {Aceitar # pedidos…}}",
"notification_requests.confirm_accept_multiple.button": "{count, plural, one {Aceitar pedido} other {Aceitar pedidos}}",
"notification_requests.confirm_accept_multiple.message": "Está prestes a aceitar {count, plural, one {um pedido de notificação} other {# pedidos de notificação}}. Tem a certeza de que pretende continuar?",
"notification_requests.confirm_accept_multiple.title": "Aceitar pedidos de notificação?",
"notification_requests.confirm_dismiss_multiple.button": "{count, plural, one {Rejeitar pedido} other {Rejeitar pedidos}}",
"notification_requests.confirm_dismiss_multiple.message": "Está prestes a rejeitar {count, plural, one {um pedido de notificação} other {# pedidos de notificação}}. Não será fácil voltar a {count, plural, one {aceder-lhe} other {aceder-lhes}}. Tem a certeza de que pretende continuar?",
"notification_requests.confirm_dismiss_multiple.title": "Rejeitar pedidos de notificação?",
"notification_requests.dismiss": "Descartar",
"notification_requests.dismiss_multiple": "{count, plural, one {Rejeitar # pedido…} other {Rejeitar # pedidos…}}",
"notification_requests.edit_selection": "Editar",
"notification_requests.exit_selection": "Concluído",
"notification_requests.explainer_for_limited_account": "As notificações desta conta foram filtradas porque a conta foi limitada por um moderador.",
"notification_requests.explainer_for_limited_remote_account": "As notificações desta conta foram filtradas porque a conta ou o seu servidor foram limitados por um moderador.",
"notification_requests.maximize": "Maximizar",
"notification_requests.minimize_banner": "Minimizar o cabeçalho das notificações filtradas",
"notification_requests.notifications_from": "Notificações de {name}",
"notification_requests.title": "Notificações filtradas",
"notification_requests.view": "Ver notificações",
"notifications.clear": "Limpar notificações",
"notifications.clear_confirmation": "Queres mesmo limpar todas as notificações?",
"notifications.clear_title": "Limpar notificações?",
@@ -562,6 +589,12 @@
"notifications.permission_denied": "Notificações no ambiente de trabalho não estão disponíveis porque a permissão, solicitada pelo navegador, foi recusada anteriormente",
"notifications.permission_denied_alert": "Notificações no ambiente de trabalho não podem ser ativadas, pois a permissão do navegador foi recusada anteriormente",
"notifications.permission_required": "Notificações no ambiente de trabalho não estão disponíveis porque a permissão necessária não foi concedida.",
"notifications.policy.accept": "Aceitar",
"notifications.policy.accept_hint": "Mostrar nas notificações",
"notifications.policy.drop": "Ignorar",
"notifications.policy.drop_hint": "Enviar para o vazio, para nunca mais ser visto",
"notifications.policy.filter": "Filtrar",
"notifications.policy.filter_hint": "Enviar para a caixa de notificações filtradas",
"notifications.policy.filter_limited_accounts_hint": "Limitado pelos moderadores do servidor",
"notifications.policy.filter_limited_accounts_title": "Contas moderadas",
"notifications.policy.filter_new_accounts.hint": "Criada nos últimos {days, plural, one {um dia} other {# dias}}",
@@ -572,6 +605,7 @@
"notifications.policy.filter_not_following_title": "Pessoas que você não segue",
"notifications.policy.filter_private_mentions_hint": "Filtrado, a menos que seja em resposta à sua própria menção ou se você seguir o remetente",
"notifications.policy.filter_private_mentions_title": "Menções privadas não solicitadas",
"notifications.policy.title": "Gerir notificações de…",
"notifications_permission_banner.enable": "Ativar notificações no ambiente de trabalho",
"notifications_permission_banner.how_to_control": "Para receber notificações quando o Mastodon não estiver aberto, ative as notificações no ambiente de trabalho. Depois da sua ativação, pode controlar precisamente quais tipos de interações geram notificações, através do botão {icon} acima.",
"notifications_permission_banner.title": "Nunca perca nada",
@@ -746,6 +780,7 @@
"status.bookmark": "Guardar nos marcadores",
"status.cancel_reblog_private": "Deixar de reforçar",
"status.cannot_reblog": "Não é possível partilhar esta publicação",
"status.continued_thread": "Continuação da conserva",
"status.copy": "Copiar hiperligação para a publicação",
"status.delete": "Eliminar",
"status.detailed_status": "Vista pormenorizada da conversa",
@@ -754,6 +789,7 @@
"status.edit": "Editar",
"status.edited": "Última edição em {date}",
"status.edited_x_times": "Editado {count, plural,one {{count} vez} other {{count} vezes}}",
"status.embed": "Obter código de incorporação",
"status.favourite": "Assinalar como favorito",
"status.favourites": "{count, plural, one {favorito} other {favoritos}}",
"status.filter": "Filtrar esta publicação",
@@ -778,6 +814,7 @@
"status.reblogs.empty": "Ainda ninguém reforçou esta publicação. Quando alguém o fizer, ele irá aparecer aqui.",
"status.redraft": "Apagar & reescrever",
"status.remove_bookmark": "Retirar dos marcadores",
"status.replied_in_thread": "Responder na conversa",
"status.replied_to": "Respondeu a {name}",
"status.reply": "Responder",
"status.replyAll": "Responder à conversa",

View File

@@ -457,6 +457,7 @@
"lists.subheading": "รายการของคุณ",
"load_pending": "{count, plural, other {# รายการใหม่}}",
"loading_indicator.label": "กำลังโหลด…",
"media_gallery.hide": "ซ่อน",
"moved_to_account_banner.text": "มีการปิดใช้งานบัญชีของคุณ {disabledAccount} ในปัจจุบันเนื่องจากคุณได้ย้ายไปยัง {movedToAccount}",
"mute_modal.hide_from_notifications": "ซ่อนจากการแจ้งเตือน",
"mute_modal.hide_options": "ซ่อนตัวเลือก",
@@ -779,6 +780,7 @@
"status.bookmark": "เพิ่มที่คั่นหน้า",
"status.cancel_reblog_private": "เลิกดัน",
"status.cannot_reblog": "ไม่สามารถดันโพสต์นี้",
"status.continued_thread": "กระทู้ต่อเนื่อง",
"status.copy": "คัดลอกลิงก์ไปยังโพสต์",
"status.delete": "ลบ",
"status.detailed_status": "มุมมองการสนทนาโดยละเอียด",
@@ -787,6 +789,7 @@
"status.edit": "แก้ไข",
"status.edited": "แก้ไขล่าสุดเมื่อ {date}",
"status.edited_x_times": "แก้ไข {count, plural, other {{count} ครั้ง}}",
"status.embed": "รับโค้ดฝังตัว",
"status.favourite": "ชื่นชอบ",
"status.favourites": "{count, plural, other {รายการโปรด}}",
"status.filter": "กรองโพสต์นี้",
@@ -811,6 +814,7 @@
"status.reblogs.empty": "ยังไม่มีใครดันโพสต์นี้ เมื่อใครสักคนดัน เขาจะปรากฏที่นี่",
"status.redraft": "ลบแล้วร่างใหม่",
"status.remove_bookmark": "เอาที่คั่นหน้าออก",
"status.replied_in_thread": "ตอบกลับในกระทู้",
"status.replied_to": "ตอบกลับ {name}",
"status.reply": "ตอบกลับ",
"status.replyAll": "ตอบกลับกระทู้",

View File

@@ -457,6 +457,7 @@
"lists.subheading": "Danh sách của bạn",
"load_pending": "{count, plural, one {# tút mới} other {# tút mới}}",
"loading_indicator.label": "Đang tải…",
"media_gallery.hide": "Ẩn",
"moved_to_account_banner.text": "Tài khoản {disabledAccount} của bạn hiện không khả dụng vì bạn đã chuyển sang {movedToAccount}.",
"mute_modal.hide_from_notifications": "Ẩn thông báo",
"mute_modal.hide_options": "Tùy chọn ẩn",
@@ -779,6 +780,7 @@
"status.bookmark": "Lưu",
"status.cancel_reblog_private": "Hủy đăng lại",
"status.cannot_reblog": "Không thể đăng lại tút này",
"status.continued_thread": "Tiếp tục trong chủ đề",
"status.copy": "Sao chép URL",
"status.delete": "Xóa",
"status.detailed_status": "Xem chi tiết thêm",
@@ -787,6 +789,7 @@
"status.edit": "Sửa",
"status.edited": "Sửa lần cuối {date}",
"status.edited_x_times": "Đã sửa {count, plural, other {{count} lần}}",
"status.embed": "Lấy mã nhúng",
"status.favourite": "Thích",
"status.favourites": "{count, plural, other {lượt thích}}",
"status.filter": "Lọc tút này",
@@ -811,6 +814,7 @@
"status.reblogs.empty": "Tút này chưa có ai đăng lại. Nếu có, nó sẽ hiển thị ở đây.",
"status.redraft": "Xóa và viết lại",
"status.remove_bookmark": "Bỏ lưu",
"status.replied_in_thread": "Trả lời trong chủ đề",
"status.replied_to": "Trả lời {name}",
"status.reply": "Trả lời",
"status.replyAll": "Trả lời",

View File

@@ -0,0 +1,19 @@
import type { ApiNotificationRequestJSON } from 'mastodon/api_types/notifications';
export interface NotificationRequest
extends Omit<ApiNotificationRequestJSON, 'account' | 'notifications_count'> {
account_id: string;
notifications_count: number;
}
export function createNotificationRequestFromJSON(
requestJSON: ApiNotificationRequestJSON,
): NotificationRequest {
const { account, notifications_count, ...request } = requestJSON;
return {
account_id: account.id,
notifications_count: +notifications_count,
...request,
};
}

View File

@@ -1,114 +0,0 @@
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
import { blockAccountSuccess, muteAccountSuccess } from 'mastodon/actions/accounts';
import {
NOTIFICATION_REQUESTS_EXPAND_REQUEST,
NOTIFICATION_REQUESTS_EXPAND_SUCCESS,
NOTIFICATION_REQUESTS_EXPAND_FAIL,
NOTIFICATION_REQUESTS_FETCH_REQUEST,
NOTIFICATION_REQUESTS_FETCH_SUCCESS,
NOTIFICATION_REQUESTS_FETCH_FAIL,
NOTIFICATION_REQUEST_FETCH_REQUEST,
NOTIFICATION_REQUEST_FETCH_SUCCESS,
NOTIFICATION_REQUEST_FETCH_FAIL,
NOTIFICATION_REQUEST_ACCEPT_REQUEST,
NOTIFICATION_REQUEST_DISMISS_REQUEST,
NOTIFICATION_REQUESTS_ACCEPT_REQUEST,
NOTIFICATION_REQUESTS_DISMISS_REQUEST,
NOTIFICATIONS_FOR_REQUEST_FETCH_REQUEST,
NOTIFICATIONS_FOR_REQUEST_FETCH_SUCCESS,
NOTIFICATIONS_FOR_REQUEST_FETCH_FAIL,
NOTIFICATIONS_FOR_REQUEST_EXPAND_REQUEST,
NOTIFICATIONS_FOR_REQUEST_EXPAND_SUCCESS,
NOTIFICATIONS_FOR_REQUEST_EXPAND_FAIL,
} from 'mastodon/actions/notifications';
import { notificationToMap } from './notifications';
const initialState = ImmutableMap({
items: ImmutableList(),
isLoading: false,
next: null,
current: ImmutableMap({
isLoading: false,
item: null,
removed: false,
notifications: ImmutableMap({
items: ImmutableList(),
isLoading: false,
next: null,
}),
}),
});
const normalizeRequest = request => fromJS({
...request,
account: request.account.id,
});
const removeRequest = (state, id) => {
if (state.getIn(['current', 'item', 'id']) === id) {
state = state.setIn(['current', 'removed'], true);
}
return state.update('items', list => list.filterNot(item => item.get('id') === id));
};
const removeRequestByAccount = (state, account_id) => {
if (state.getIn(['current', 'item', 'account']) === account_id) {
state = state.setIn(['current', 'removed'], true);
}
return state.update('items', list => list.filterNot(item => item.get('account') === account_id));
};
export const notificationRequestsReducer = (state = initialState, action) => {
switch(action.type) {
case NOTIFICATION_REQUESTS_FETCH_SUCCESS:
return state.withMutations(map => {
map.update('items', list => ImmutableList(action.requests.map(normalizeRequest)).concat(list));
map.set('isLoading', false);
map.update('next', next => next ?? action.next);
});
case NOTIFICATION_REQUESTS_EXPAND_SUCCESS:
return state.withMutations(map => {
map.update('items', list => list.concat(ImmutableList(action.requests.map(normalizeRequest))));
map.set('isLoading', false);
map.set('next', action.next);
});
case NOTIFICATION_REQUESTS_EXPAND_REQUEST:
case NOTIFICATION_REQUESTS_FETCH_REQUEST:
return state.set('isLoading', true);
case NOTIFICATION_REQUESTS_EXPAND_FAIL:
case NOTIFICATION_REQUESTS_FETCH_FAIL:
return state.set('isLoading', false);
case NOTIFICATION_REQUEST_ACCEPT_REQUEST:
case NOTIFICATION_REQUEST_DISMISS_REQUEST:
return removeRequest(state, action.id);
case NOTIFICATION_REQUESTS_ACCEPT_REQUEST:
case NOTIFICATION_REQUESTS_DISMISS_REQUEST:
return action.ids.reduce((state, id) => removeRequest(state, id), state);
case blockAccountSuccess.type:
return removeRequestByAccount(state, action.payload.relationship.id);
case muteAccountSuccess.type:
return action.payload.relationship.muting_notifications ? removeRequestByAccount(state, action.payload.relationship.id) : state;
case NOTIFICATION_REQUEST_FETCH_REQUEST:
return state.set('current', initialState.get('current').set('isLoading', true));
case NOTIFICATION_REQUEST_FETCH_SUCCESS:
return state.update('current', map => map.set('isLoading', false).set('item', normalizeRequest(action.request)));
case NOTIFICATION_REQUEST_FETCH_FAIL:
return state.update('current', map => map.set('isLoading', false));
case NOTIFICATIONS_FOR_REQUEST_FETCH_REQUEST:
case NOTIFICATIONS_FOR_REQUEST_EXPAND_REQUEST:
return state.setIn(['current', 'notifications', 'isLoading'], true);
case NOTIFICATIONS_FOR_REQUEST_FETCH_SUCCESS:
return state.updateIn(['current', 'notifications'], map => map.set('isLoading', false).update('items', list => ImmutableList(action.notifications.map(notificationToMap)).concat(list)).update('next', next => next ?? action.next));
case NOTIFICATIONS_FOR_REQUEST_EXPAND_SUCCESS:
return state.updateIn(['current', 'notifications'], map => map.set('isLoading', false).update('items', list => list.concat(ImmutableList(action.notifications.map(notificationToMap)))).set('next', action.next));
case NOTIFICATIONS_FOR_REQUEST_FETCH_FAIL:
case NOTIFICATIONS_FOR_REQUEST_EXPAND_FAIL:
return state.setIn(['current', 'notifications', 'isLoading'], false);
default:
return state;
}
};

View File

@@ -0,0 +1,182 @@
import { createReducer, isAnyOf } from '@reduxjs/toolkit';
import {
blockAccountSuccess,
muteAccountSuccess,
} from 'mastodon/actions/accounts';
import {
fetchNotificationRequests,
expandNotificationRequests,
fetchNotificationRequest,
fetchNotificationsForRequest,
expandNotificationsForRequest,
acceptNotificationRequest,
dismissNotificationRequest,
acceptNotificationRequests,
dismissNotificationRequests,
} from 'mastodon/actions/notification_requests';
import type { NotificationRequest } from 'mastodon/models/notification_request';
import { createNotificationRequestFromJSON } from 'mastodon/models/notification_request';
import { notificationToMap } from './notifications';
interface NotificationsListState {
items: unknown[]; // TODO
isLoading: boolean;
next: string | null;
}
interface CurrentNotificationRequestState {
item: NotificationRequest | null;
isLoading: boolean;
removed: boolean;
notifications: NotificationsListState;
}
interface NotificationRequestsState {
items: NotificationRequest[];
isLoading: boolean;
next: string | null;
current: CurrentNotificationRequestState;
}
const initialState: NotificationRequestsState = {
items: [],
isLoading: false,
next: null,
current: {
item: null,
isLoading: false,
removed: false,
notifications: {
isLoading: false,
items: [],
next: null,
},
},
};
const removeRequest = (state: NotificationRequestsState, id: string) => {
if (state.current.item?.id === id) {
state.current.removed = true;
}
state.items = state.items.filter((item) => item.id !== id);
};
const removeRequestByAccount = (
state: NotificationRequestsState,
account_id: string,
) => {
if (state.current.item?.account_id === account_id) {
state.current.removed = true;
}
state.items = state.items.filter((item) => item.account_id !== account_id);
};
export const notificationRequestsReducer =
createReducer<NotificationRequestsState>(initialState, (builder) => {
builder
.addCase(fetchNotificationRequests.fulfilled, (state, action) => {
state.items = action.payload.requests
.map(createNotificationRequestFromJSON)
.concat(state.items);
state.isLoading = false;
state.next ??= action.payload.next ?? null;
})
.addCase(expandNotificationRequests.fulfilled, (state, action) => {
state.items = state.items.concat(
action.payload.requests.map(createNotificationRequestFromJSON),
);
state.isLoading = false;
state.next = action.payload.next ?? null;
})
.addCase(blockAccountSuccess, (state, action) => {
removeRequestByAccount(state, action.payload.relationship.id);
})
.addCase(muteAccountSuccess, (state, action) => {
if (action.payload.relationship.muting_notifications)
removeRequestByAccount(state, action.payload.relationship.id);
})
.addCase(fetchNotificationRequest.pending, (state) => {
state.current = { ...initialState.current, isLoading: true };
})
.addCase(fetchNotificationRequest.rejected, (state) => {
state.current.isLoading = false;
})
.addCase(fetchNotificationRequest.fulfilled, (state, action) => {
state.current.isLoading = false;
state.current.item = createNotificationRequestFromJSON(action.payload);
})
.addCase(fetchNotificationsForRequest.fulfilled, (state, action) => {
state.current.notifications.isLoading = false;
state.current.notifications.items.unshift(
...action.payload.notifications.map(notificationToMap),
);
state.current.notifications.next ??= action.payload.next ?? null;
})
.addCase(expandNotificationsForRequest.fulfilled, (state, action) => {
state.current.notifications.isLoading = false;
state.current.notifications.items.push(
...action.payload.notifications.map(notificationToMap),
);
state.current.notifications.next = action.payload.next ?? null;
})
.addMatcher(
isAnyOf(
fetchNotificationRequests.pending,
expandNotificationRequests.pending,
),
(state) => {
state.isLoading = true;
},
)
.addMatcher(
isAnyOf(
fetchNotificationRequests.rejected,
expandNotificationRequests.rejected,
),
(state) => {
state.isLoading = false;
},
)
.addMatcher(
isAnyOf(
acceptNotificationRequest.pending,
dismissNotificationRequest.pending,
),
(state, action) => {
removeRequest(state, action.meta.arg.id);
},
)
.addMatcher(
isAnyOf(
acceptNotificationRequests.pending,
dismissNotificationRequests.pending,
),
(state, action) => {
action.meta.arg.ids.forEach((id) => {
removeRequest(state, id);
});
},
)
.addMatcher(
isAnyOf(
fetchNotificationsForRequest.pending,
expandNotificationsForRequest.pending,
),
(state) => {
state.current.notifications.isLoading = true;
},
)
.addMatcher(
isAnyOf(
fetchNotificationsForRequest.rejected,
expandNotificationsForRequest.rejected,
),
(state) => {
state.current.notifications.isLoading = false;
},
);
});

View File

@@ -33,8 +33,12 @@ interface AppThunkConfig {
}
type AppThunkApi = Pick<GetThunkAPI<AppThunkConfig>, 'getState' | 'dispatch'>;
interface AppThunkOptions {
interface AppThunkOptions<Arg> {
useLoadingBar?: boolean;
condition?: (
arg: Arg,
{ getState }: { getState: AppThunkApi['getState'] },
) => boolean;
}
const createBaseAsyncThunk = createAsyncThunk.withTypes<AppThunkConfig>();
@@ -42,7 +46,7 @@ const createBaseAsyncThunk = createAsyncThunk.withTypes<AppThunkConfig>();
export function createThunk<Arg = void, Returned = void>(
name: string,
creator: (arg: Arg, api: AppThunkApi) => Returned | Promise<Returned>,
options: AppThunkOptions = {},
options: AppThunkOptions<Arg> = {},
) {
return createBaseAsyncThunk(
name,
@@ -70,6 +74,7 @@ export function createThunk<Arg = void, Returned = void>(
if (options.useLoadingBar) return { useLoadingBar: true };
return {};
},
condition: options.condition,
},
);
}
@@ -96,7 +101,7 @@ type ArgsType = Record<string, unknown> | undefined;
export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
name: string,
loadData: (args: Args) => Promise<LoadDataResult>,
thunkOptions?: AppThunkOptions,
thunkOptions?: AppThunkOptions<Args>,
): ReturnType<typeof createThunk<Args, LoadDataResult>>;
// Overload when the `onData` method returns discardLoadDataInPayload, then the payload is empty
@@ -104,17 +109,19 @@ export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
name: string,
loadData: LoadData<Args, LoadDataResult>,
onDataOrThunkOptions?:
| AppThunkOptions
| AppThunkOptions<Args>
| OnData<Args, LoadDataResult, DiscardLoadData>,
thunkOptions?: AppThunkOptions,
thunkOptions?: AppThunkOptions<Args>,
): ReturnType<typeof createThunk<Args, void>>;
// Overload when the `onData` method returns nothing, then the mayload is the `onData` result
export function createDataLoadingThunk<LoadDataResult, Args extends ArgsType>(
name: string,
loadData: LoadData<Args, LoadDataResult>,
onDataOrThunkOptions?: AppThunkOptions | OnData<Args, LoadDataResult, void>,
thunkOptions?: AppThunkOptions,
onDataOrThunkOptions?:
| AppThunkOptions<Args>
| OnData<Args, LoadDataResult, void>,
thunkOptions?: AppThunkOptions<Args>,
): ReturnType<typeof createThunk<Args, LoadDataResult>>;
// Overload when there is an `onData` method returning something
@@ -126,9 +133,9 @@ export function createDataLoadingThunk<
name: string,
loadData: LoadData<Args, LoadDataResult>,
onDataOrThunkOptions?:
| AppThunkOptions
| AppThunkOptions<Args>
| OnData<Args, LoadDataResult, Returned>,
thunkOptions?: AppThunkOptions,
thunkOptions?: AppThunkOptions<Args>,
): ReturnType<typeof createThunk<Args, Returned>>;
/**
@@ -154,6 +161,7 @@ export function createDataLoadingThunk<
* @param maybeThunkOptions
* Additional Mastodon specific options for the thunk. Currently supports:
* - `useLoadingBar` to display a loading bar while this action is pending. Defaults to true.
* - `condition` is passed to `createAsyncThunk` (https://redux-toolkit.js.org/api/createAsyncThunk#canceling-before-execution)
* @returns The created thunk
*/
export function createDataLoadingThunk<
@@ -164,12 +172,12 @@ export function createDataLoadingThunk<
name: string,
loadData: LoadData<Args, LoadDataResult>,
onDataOrThunkOptions?:
| AppThunkOptions
| AppThunkOptions<Args>
| OnData<Args, LoadDataResult, Returned>,
maybeThunkOptions?: AppThunkOptions,
maybeThunkOptions?: AppThunkOptions<Args>,
) {
let onData: OnData<Args, LoadDataResult, Returned> | undefined;
let thunkOptions: AppThunkOptions | undefined;
let thunkOptions: AppThunkOptions<Args> | undefined;
if (typeof onDataOrThunkOptions === 'function') onData = onDataOrThunkOptions;
else if (typeof onDataOrThunkOptions === 'object')
@@ -203,6 +211,9 @@ export function createDataLoadingThunk<
return undefined as Returned;
else return result;
},
{ useLoadingBar: thunkOptions?.useLoadingBar ?? true },
{
useLoadingBar: thunkOptions?.useLoadingBar ?? true,
condition: thunkOptions?.condition,
},
);
}

View File

@@ -10234,6 +10234,7 @@ noscript {
scroll-padding: 16px;
scroll-behavior: smooth;
overflow-x: scroll;
scrollbar-width: none;
&__card {
background: var(--background-color);