Merge remote-tracking branch 'mkljczk/hooks-migration' into HEAD

This commit is contained in:
marcin mikołajczak 2024-09-29 12:53:36 +02:00
commit 2a67433f6e
64 changed files with 334 additions and 936 deletions

View file

@ -1,7 +1,7 @@
import pick from 'lodash.pick';
import { z } from 'zod';
import { accountSchema } from './account';
import { type Account, accountSchema } from './account';
import { customEmojiSchema } from './custom-emoji';
import { emojiReactionSchema } from './emoji-reaction';
import { filterResultSchema } from './filter-result';
@ -147,9 +147,15 @@ const statusWithoutAccountSchema = z.preprocess(preprocess, baseStatusSchema.omi
quote: z.lazy(() => statusSchema).nullable().catch(null),
}));
type StatusWithoutAccount = Omit<z.infer<typeof baseStatusSchema>, 'account'> & {
account: Account | null;
reblog: Status | null;
quote: Status | null;
}
type Status = z.infer<typeof baseStatusSchema> & {
reblog: Status | null;
quote: Status | null;
}
export { statusSchema, statusWithoutAccountSchema, type Status };
export { statusSchema, statusWithoutAccountSchema, type Status, type StatusWithoutAccount };

View file

@ -60,7 +60,7 @@
"@reach/popover": "^0.18.0",
"@reach/rect": "^0.18.0",
"@reach/tabs": "^0.18.0",
"@reduxjs/toolkit": "^2.2.7",
"@reduxjs/toolkit": "^2.0.1",
"@sentry/browser": "^7.74.1",
"@sentry/react": "^7.74.1",
"@tabler/icons": "^3.18.0",
@ -121,7 +121,7 @@
"react-inlinesvg": "^4.0.0",
"react-intl": "^6.7.0",
"react-motion": "^0.5.2",
"react-redux": "^9.1.2",
"react-redux": "^9.0.4",
"react-router-dom": "^5.3.4",
"react-router-dom-v5-compat": "^6.24.1",
"react-router-scroll-4": "^1.0.0-beta.2",
@ -129,7 +129,7 @@
"react-sparklines": "^1.7.0",
"react-sticky-box": "^2.0.0",
"react-swipeable-views": "^0.14.0",
"redux": "^5.0.1",
"redux": "^5.0.0",
"redux-immutable": "^4.0.0",
"redux-thunk": "^3.1.0",
"reselect": "^5.0.0",

View file

@ -1,4 +1,4 @@
import { staticFetch } from '../api';
import { staticFetch } from 'pl-fe/api';
import type { RootState } from 'pl-fe/store';
import type { AnyAction } from 'redux';

View file

@ -1,42 +1,13 @@
import { getClient } from '../api';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/entity-store/actions';
import { Entities } from 'pl-fe/entity-store/entities';
import type { RootState } from 'pl-fe/store';
import type { AnyAction } from 'redux';
const ACCOUNT_NOTE_SUBMIT_REQUEST = 'ACCOUNT_NOTE_SUBMIT_REQUEST' as const;
const ACCOUNT_NOTE_SUBMIT_SUCCESS = 'ACCOUNT_NOTE_SUBMIT_SUCCESS' as const;
const ACCOUNT_NOTE_SUBMIT_FAIL = 'ACCOUNT_NOTE_SUBMIT_FAIL' as const;
const submitAccountNote = (accountId: string, value: string) =>
(dispatch: React.Dispatch<AnyAction>, getState: () => RootState) => {
dispatch(submitAccountNoteRequest(accountId));
(dispatch: React.Dispatch<AnyAction>, getState: () => RootState) =>
getClient(getState).accounts.updateAccountNote(accountId, value)
.then(response => dispatch(importEntities([response], Entities.RELATIONSHIPS)));
return getClient(getState).accounts.updateAccountNote(accountId, value)
.then(response => {
dispatch(submitAccountNoteSuccess(response));
}).catch(error => dispatch(submitAccountNoteFail(accountId, error)));
};
const submitAccountNoteRequest = (accountId: string) => ({
type: ACCOUNT_NOTE_SUBMIT_REQUEST,
accountId,
});
const submitAccountNoteSuccess = (relationship: any) => ({
type: ACCOUNT_NOTE_SUBMIT_SUCCESS,
accountId: relationship.id,
relationship,
});
const submitAccountNoteFail = (accountId: string, error: unknown) => ({
type: ACCOUNT_NOTE_SUBMIT_FAIL,
accountId,
error,
});
export {
submitAccountNote,
ACCOUNT_NOTE_SUBMIT_REQUEST,
ACCOUNT_NOTE_SUBMIT_SUCCESS,
ACCOUNT_NOTE_SUBMIT_FAIL,
};
export { submitAccountNote };

View file

@ -1,11 +1,11 @@
import { PLEROMA, type UpdateNotificationSettingsParams, type Account, type CreateAccountParams, type PaginatedResponse, type Relationship } from 'pl-api';
import { getClient, type PlfeResponse } from 'pl-fe/api';
import { Entities } from 'pl-fe/entity-store/entities';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { selectAccount } from 'pl-fe/selectors';
import { isLoggedIn } from 'pl-fe/utils/auth';
import { getClient, type PlfeResponse } from '../api';
import type { Map as ImmutableMap } from 'immutable';
import type { MinifiedStatus } from 'pl-fe/reducers/statuses';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -23,30 +23,10 @@ const ACCOUNT_BLOCK_REQUEST = 'ACCOUNT_BLOCK_REQUEST' as const;
const ACCOUNT_BLOCK_SUCCESS = 'ACCOUNT_BLOCK_SUCCESS' as const;
const ACCOUNT_BLOCK_FAIL = 'ACCOUNT_BLOCK_FAIL' as const;
const ACCOUNT_UNBLOCK_REQUEST = 'ACCOUNT_UNBLOCK_REQUEST' as const;
const ACCOUNT_UNBLOCK_SUCCESS = 'ACCOUNT_UNBLOCK_SUCCESS' as const;
const ACCOUNT_UNBLOCK_FAIL = 'ACCOUNT_UNBLOCK_FAIL' as const;
const ACCOUNT_MUTE_REQUEST = 'ACCOUNT_MUTE_REQUEST' as const;
const ACCOUNT_MUTE_SUCCESS = 'ACCOUNT_MUTE_SUCCESS' as const;
const ACCOUNT_MUTE_FAIL = 'ACCOUNT_MUTE_FAIL' as const;
const ACCOUNT_UNMUTE_REQUEST = 'ACCOUNT_UNMUTE_REQUEST' as const;
const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS' as const;
const ACCOUNT_UNMUTE_FAIL = 'ACCOUNT_UNMUTE_FAIL' as const;
const ACCOUNT_PIN_REQUEST = 'ACCOUNT_PIN_REQUEST' as const;
const ACCOUNT_PIN_SUCCESS = 'ACCOUNT_PIN_SUCCESS' as const;
const ACCOUNT_PIN_FAIL = 'ACCOUNT_PIN_FAIL' as const;
const ACCOUNT_UNPIN_REQUEST = 'ACCOUNT_UNPIN_REQUEST' as const;
const ACCOUNT_UNPIN_SUCCESS = 'ACCOUNT_UNPIN_SUCCESS' as const;
const ACCOUNT_UNPIN_FAIL = 'ACCOUNT_UNPIN_FAIL' as const;
const ACCOUNT_REMOVE_FROM_FOLLOWERS_REQUEST = 'ACCOUNT_REMOVE_FROM_FOLLOWERS_REQUEST' as const;
const ACCOUNT_REMOVE_FROM_FOLLOWERS_SUCCESS = 'ACCOUNT_REMOVE_FROM_FOLLOWERS_SUCCESS' as const;
const ACCOUNT_REMOVE_FROM_FOLLOWERS_FAIL = 'ACCOUNT_REMOVE_FROM_FOLLOWERS_FAIL' as const;
const PINNED_ACCOUNTS_FETCH_REQUEST = 'PINNED_ACCOUNTS_FETCH_REQUEST' as const;
const PINNED_ACCOUNTS_FETCH_SUCCESS = 'PINNED_ACCOUNTS_FETCH_SUCCESS' as const;
const PINNED_ACCOUNTS_FETCH_FAIL = 'PINNED_ACCOUNTS_FETCH_FAIL' as const;
@ -59,10 +39,6 @@ const ACCOUNT_LOOKUP_REQUEST = 'ACCOUNT_LOOKUP_REQUEST' as const;
const ACCOUNT_LOOKUP_SUCCESS = 'ACCOUNT_LOOKUP_SUCCESS' as const;
const ACCOUNT_LOOKUP_FAIL = 'ACCOUNT_LOOKUP_FAIL' as const;
const RELATIONSHIPS_FETCH_REQUEST = 'RELATIONSHIPS_FETCH_REQUEST' as const;
const RELATIONSHIPS_FETCH_SUCCESS = 'RELATIONSHIPS_FETCH_SUCCESS' as const;
const RELATIONSHIPS_FETCH_FAIL = 'RELATIONSHIPS_FETCH_FAIL' as const;
const FOLLOW_REQUESTS_FETCH_REQUEST = 'FOLLOW_REQUESTS_FETCH_REQUEST' as const;
const FOLLOW_REQUESTS_FETCH_SUCCESS = 'FOLLOW_REQUESTS_FETCH_SUCCESS' as const;
const FOLLOW_REQUESTS_FETCH_FAIL = 'FOLLOW_REQUESTS_FETCH_FAIL' as const;
@ -87,10 +63,6 @@ const BIRTHDAY_REMINDERS_FETCH_REQUEST = 'BIRTHDAY_REMINDERS_FETCH_REQUEST' as c
const BIRTHDAY_REMINDERS_FETCH_SUCCESS = 'BIRTHDAY_REMINDERS_FETCH_SUCCESS' as const;
const BIRTHDAY_REMINDERS_FETCH_FAIL = 'BIRTHDAY_REMINDERS_FETCH_FAIL' as const;
const ACCOUNT_BITE_REQUEST = 'ACCOUNT_BITE_REQUEST' as const;
const ACCOUNT_BITE_SUCCESS = 'ACCOUNT_BITE_SUCCESS' as const;
const ACCOUNT_BITE_FAIL = 'ACCOUNT_BITE_FAIL' as const;
const maybeRedirectLogin = (error: { response: PlfeResponse }, history?: History) => {
// The client is unauthorized - redirect to login.
if (history && error?.response?.status === 401) {
@ -205,14 +177,8 @@ const unblockAccount = (accountId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return null;
dispatch(unblockAccountRequest(accountId));
return getClient(getState).filtering.unblockAccount(accountId)
.then(response => {
importEntities({ relationships: [response] });
return dispatch(unblockAccountSuccess(response));
})
.catch(error => dispatch(unblockAccountFail(error)));
.then(response => importEntities({ relationships: [response] }));
};
const blockAccountRequest = (accountId: string) => ({
@ -231,21 +197,6 @@ const blockAccountFail = (error: unknown) => ({
error,
});
const unblockAccountRequest = (accountId: string) => ({
type: ACCOUNT_UNBLOCK_REQUEST,
accountId,
});
const unblockAccountSuccess = (relationship: Relationship) => ({
type: ACCOUNT_UNBLOCK_SUCCESS,
relationship,
});
const unblockAccountFail = (error: unknown) => ({
type: ACCOUNT_UNBLOCK_FAIL,
error,
});
const muteAccount = (accountId: string, notifications?: boolean, duration = 0) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return null;
@ -281,14 +232,8 @@ const unmuteAccount = (accountId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return null;
dispatch(unmuteAccountRequest(accountId));
return getClient(getState()).filtering.unmuteAccount(accountId)
.then(response => {
importEntities({ relationships: [response] });
return dispatch(unmuteAccountSuccess(response));
})
.catch(error => dispatch(unmuteAccountFail(accountId, error)));
.then(response => importEntities({ relationships: [response] }));
};
const muteAccountRequest = (accountId: string) => ({
@ -308,85 +253,29 @@ const muteAccountFail = (accountId: string, error: unknown) => ({
error,
});
const unmuteAccountRequest = (accountId: string) => ({
type: ACCOUNT_UNMUTE_REQUEST,
accountId,
});
const unmuteAccountSuccess = (relationship: Relationship) => ({
type: ACCOUNT_UNMUTE_SUCCESS,
relationship,
});
const unmuteAccountFail = (accountId: string, error: unknown) => ({
type: ACCOUNT_UNMUTE_FAIL,
accountId,
error,
});
const removeFromFollowers = (accountId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return null;
dispatch(removeFromFollowersRequest(accountId));
return getClient(getState()).accounts.removeAccountFromFollowers(accountId)
.then(response => dispatch(removeFromFollowersSuccess(response)))
.catch(error => dispatch(removeFromFollowersFail(accountId, error)));
.then(response => importEntities({ relationships: [response] }));
};
const removeFromFollowersRequest = (accountId: string) => ({
type: ACCOUNT_REMOVE_FROM_FOLLOWERS_REQUEST,
accountId,
});
const removeFromFollowersSuccess = (relationship: Relationship) => ({
type: ACCOUNT_REMOVE_FROM_FOLLOWERS_SUCCESS,
relationship,
});
const removeFromFollowersFail = (accountId: string, error: unknown) => ({
type: ACCOUNT_REMOVE_FROM_FOLLOWERS_FAIL,
accountId,
error,
});
const fetchRelationships = (accountIds: string[]) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return null;
const loadedRelationships = getState().relationships;
const newAccountIds = accountIds.filter(id => loadedRelationships.get(id, null) === null);
const loadedRelationships = getState().entities[Entities.RELATIONSHIPS]?.store;
const newAccountIds = accountIds.filter(id => !loadedRelationships?.[id]);
if (newAccountIds.length === 0) {
return null;
}
dispatch(fetchRelationshipsRequest(newAccountIds));
return getClient(getState()).accounts.getRelationships(newAccountIds)
.then(response => {
importEntities({ relationships: response });
dispatch(fetchRelationshipsSuccess(response));
})
.catch(error => dispatch(fetchRelationshipsFail(error)));
.then(response => importEntities({ relationships: response }));
};
const fetchRelationshipsRequest = (accountIds: string[]) => ({
type: RELATIONSHIPS_FETCH_REQUEST,
accountIds,
});
const fetchRelationshipsSuccess = (relationships: Array<Relationship>) => ({
type: RELATIONSHIPS_FETCH_SUCCESS,
relationships,
});
const fetchRelationshipsFail = (error: unknown) => ({
type: RELATIONSHIPS_FETCH_FAIL,
error,
});
const fetchFollowRequests = () =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return null;
@ -505,26 +394,16 @@ const pinAccount = (accountId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return dispatch(noOp);
dispatch(pinAccountRequest(accountId));
return getClient(getState).accounts.pinAccount(accountId).then(response => {
dispatch(pinAccountSuccess(response));
}).catch(error => {
dispatch(pinAccountFail(error));
});
return getClient(getState).accounts.pinAccount(accountId)
.then(response => importEntities({ relationships: [response] }));
};
const unpinAccount = (accountId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!isLoggedIn(getState)) return dispatch(noOp);
dispatch(unpinAccountRequest(accountId));
return getClient(getState).accounts.unpinAccount(accountId).then(response => {
dispatch(unpinAccountSuccess(response));
}).catch(error => {
dispatch(unpinAccountFail(error));
});
return getClient(getState).accounts.unpinAccount(accountId)
.then(response => importEntities({ relationships: [response] }));
};
const updateNotificationSettings = (params: UpdateNotificationSettingsParams) =>
@ -538,36 +417,6 @@ const updateNotificationSettings = (params: UpdateNotificationSettingsParams) =>
});
};
const pinAccountRequest = (accountId: string) => ({
type: ACCOUNT_PIN_REQUEST,
accountId,
});
const pinAccountSuccess = (relationship: Relationship) => ({
type: ACCOUNT_PIN_SUCCESS,
relationship,
});
const pinAccountFail = (error: unknown) => ({
type: ACCOUNT_PIN_FAIL,
error,
});
const unpinAccountRequest = (accountId: string) => ({
type: ACCOUNT_UNPIN_REQUEST,
accountId,
});
const unpinAccountSuccess = (relationship: Relationship) => ({
type: ACCOUNT_UNPIN_SUCCESS,
relationship,
});
const unpinAccountFail = (error: unknown) => ({
type: ACCOUNT_UNPIN_FAIL,
error,
});
const fetchPinnedAccounts = (accountId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch(fetchPinnedAccountsRequest(accountId));
@ -650,33 +499,9 @@ const biteAccount = (accountId: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
const client = getClient(getState);
dispatch(biteAccountRequest(accountId));
return client.accounts.biteAccount(accountId)
.then(() => {
return dispatch(biteAccountSuccess(accountId));
})
.catch(error => {
dispatch(biteAccountFail(accountId, error));
throw error;
});
return client.accounts.biteAccount(accountId);
};
const biteAccountRequest = (accountId: string) => ({
type: ACCOUNT_BITE_REQUEST,
accountId,
});
const biteAccountSuccess = (accountId: string) => ({
type: ACCOUNT_BITE_SUCCESS,
});
const biteAccountFail = (accountId: string, error: unknown) => ({
type: ACCOUNT_BITE_FAIL,
accountId,
error,
});
export {
ACCOUNT_CREATE_REQUEST,
ACCOUNT_CREATE_SUCCESS,
@ -687,24 +512,9 @@ export {
ACCOUNT_BLOCK_REQUEST,
ACCOUNT_BLOCK_SUCCESS,
ACCOUNT_BLOCK_FAIL,
ACCOUNT_UNBLOCK_REQUEST,
ACCOUNT_UNBLOCK_SUCCESS,
ACCOUNT_UNBLOCK_FAIL,
ACCOUNT_MUTE_REQUEST,
ACCOUNT_MUTE_SUCCESS,
ACCOUNT_MUTE_FAIL,
ACCOUNT_UNMUTE_REQUEST,
ACCOUNT_UNMUTE_SUCCESS,
ACCOUNT_UNMUTE_FAIL,
ACCOUNT_PIN_REQUEST,
ACCOUNT_PIN_SUCCESS,
ACCOUNT_PIN_FAIL,
ACCOUNT_UNPIN_REQUEST,
ACCOUNT_UNPIN_SUCCESS,
ACCOUNT_UNPIN_FAIL,
ACCOUNT_REMOVE_FROM_FOLLOWERS_REQUEST,
ACCOUNT_REMOVE_FROM_FOLLOWERS_SUCCESS,
ACCOUNT_REMOVE_FROM_FOLLOWERS_FAIL,
PINNED_ACCOUNTS_FETCH_REQUEST,
PINNED_ACCOUNTS_FETCH_SUCCESS,
PINNED_ACCOUNTS_FETCH_FAIL,
@ -714,9 +524,6 @@ export {
ACCOUNT_LOOKUP_REQUEST,
ACCOUNT_LOOKUP_SUCCESS,
ACCOUNT_LOOKUP_FAIL,
RELATIONSHIPS_FETCH_REQUEST,
RELATIONSHIPS_FETCH_SUCCESS,
RELATIONSHIPS_FETCH_FAIL,
FOLLOW_REQUESTS_FETCH_REQUEST,
FOLLOW_REQUESTS_FETCH_SUCCESS,
FOLLOW_REQUESTS_FETCH_FAIL,
@ -735,9 +542,6 @@ export {
BIRTHDAY_REMINDERS_FETCH_REQUEST,
BIRTHDAY_REMINDERS_FETCH_SUCCESS,
BIRTHDAY_REMINDERS_FETCH_FAIL,
ACCOUNT_BITE_REQUEST,
ACCOUNT_BITE_SUCCESS,
ACCOUNT_BITE_FAIL,
createAccount,
fetchAccount,
fetchAccountByUsername,
@ -749,25 +553,13 @@ export {
blockAccountRequest,
blockAccountSuccess,
blockAccountFail,
unblockAccountRequest,
unblockAccountSuccess,
unblockAccountFail,
muteAccount,
unmuteAccount,
muteAccountRequest,
muteAccountSuccess,
muteAccountFail,
unmuteAccountRequest,
unmuteAccountSuccess,
unmuteAccountFail,
removeFromFollowers,
removeFromFollowersRequest,
removeFromFollowersSuccess,
removeFromFollowersFail,
fetchRelationships,
fetchRelationshipsRequest,
fetchRelationshipsSuccess,
fetchRelationshipsFail,
fetchFollowRequests,
fetchFollowRequestsRequest,
fetchFollowRequestsSuccess,
@ -787,12 +579,6 @@ export {
pinAccount,
unpinAccount,
updateNotificationSettings,
pinAccountRequest,
pinAccountSuccess,
pinAccountFail,
unpinAccountRequest,
unpinAccountSuccess,
unpinAccountFail,
fetchPinnedAccounts,
fetchPinnedAccountsRequest,
fetchPinnedAccountsSuccess,
@ -801,7 +587,4 @@ export {
accountLookup,
fetchBirthdayReminders,
biteAccount,
biteAccountRequest,
biteAccountSuccess,
biteAccountFail,
};

View file

@ -1,9 +1,8 @@
import { fetchRelationships } from 'pl-fe/actions/accounts';
import { importFetchedAccount, importFetchedAccounts, importFetchedStatus, importFetchedStatuses } from 'pl-fe/actions/importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { filterBadges, getTagDiff } from 'pl-fe/utils/badges';
import { getClient } from '../api';
import { deleteFromTimelines } from './timelines';
import type { Account, AdminGetAccountsParams, AdminGetReportsParams, PleromaConfig } from 'pl-api';
@ -111,9 +110,10 @@ const fetchReports = (params?: AdminGetReportsParams) =>
return getClient(state).admin.reports.getReports(params)
.then(({ items }) => {
items.forEach((report) => {
if (report.account?.account) dispatch(importFetchedAccount(report.account.account));
if (report.target_account?.account) dispatch(importFetchedAccount(report.target_account.account));
dispatch(importFetchedStatuses(report.statuses));
const accounts = [];
if (report.account?.account) accounts.push(report.account.account);
if (report.target_account?.account) accounts.push(report.target_account.account);
importEntities({ accounts, statuses: report.statuses });
dispatch({ type: ADMIN_REPORTS_FETCH_SUCCESS, reports: items, params });
});
}).catch(error => {
@ -141,7 +141,8 @@ const fetchUsers = (params?: AdminGetAccountsParams) =>
dispatch({ type: ADMIN_USERS_FETCH_REQUEST, params });
return getClient(state).admin.accounts.getAccounts(params).then((res) => {
dispatch(importFetchedAccounts(res.items.map(({ account }) => account).filter((account): account is Account => account !== null)));
const accounts = res.items.map(({ account }) => account).filter((account): account is Account => account !== null);
importEntities({ accounts });
dispatch(fetchRelationships(res.items.map((account) => account.id)));
dispatch({ type: ADMIN_USERS_FETCH_SUCCESS, users: res.items, params, next: res.next });
return res;
@ -203,7 +204,7 @@ const toggleStatusSensitivity = (statusId: string, sensitive: boolean) =>
dispatch({ type: ADMIN_STATUS_TOGGLE_SENSITIVITY_REQUEST, statusId });
return getClient(getState).admin.statuses.updateStatus(statusId, { sensitive: !sensitive })
.then((status) => {
dispatch(importFetchedStatus(status));
importEntities({ statuses: [status] });
dispatch({ type: ADMIN_STATUS_TOGGLE_SENSITIVITY_SUCCESS, statusId, status });
}).catch(error => {
dispatch({ type: ADMIN_STATUS_TOGGLE_SENSITIVITY_FAIL, error, statusId });

View file

@ -1,12 +1,10 @@
import { defineMessages } from 'react-intl';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import toast from 'pl-fe/toast';
import { isLoggedIn } from 'pl-fe/utils/auth';
import { getClient } from '../api';
import { importFetchedAccounts } from './importer';
import type { Account as BaseAccount } from 'pl-api';
import type { Account } from 'pl-fe/normalizers';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -63,7 +61,7 @@ const fetchAliasesSuggestions = (q: string) =>
return getClient(getState()).accounts.searchAccounts(q, { resolve: true, limit: 4 })
.then((data) => {
dispatch(importFetchedAccounts(data));
importEntities({ accounts: data });
dispatch(fetchAliasesSuggestionsReady(q, data));
}).catch(error => toast.showAlertForError(error));
};

View file

@ -14,8 +14,10 @@ import { createApp } from 'pl-fe/actions/apps';
import { fetchMeSuccess, fetchMeFail } from 'pl-fe/actions/me';
import { obtainOAuthToken, revokeOAuthToken } from 'pl-fe/actions/oauth';
import { startOnboarding } from 'pl-fe/actions/onboarding';
import { type PlfeResponse, getClient } from 'pl-fe/api';
import * as BuildConfig from 'pl-fe/build-config';
import { custom } from 'pl-fe/custom';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { queryClient } from 'pl-fe/queries/client';
import { selectAccount } from 'pl-fe/selectors';
import { unsetSentryAccount } from 'pl-fe/sentry';
@ -27,10 +29,6 @@ import { normalizeUsername } from 'pl-fe/utils/input';
import { getScopes } from 'pl-fe/utils/scopes';
import { isStandalone } from 'pl-fe/utils/state';
import { type PlfeResponse, getClient } from '../api';
import { importFetchedAccount } from './importer';
import type { AppDispatch, RootState } from 'pl-fe/store';
const SWITCH_ACCOUNT = 'SWITCH_ACCOUNT' as const;
@ -149,7 +147,7 @@ const verifyCredentials = (token: string, accountUrl?: string) =>
const client = new PlApiClient(baseURL, token);
return client.settings.verifyCredentials().then((account) => {
dispatch(importFetchedAccount(account));
importEntities({ accounts: [account] });
dispatch({ type: VERIFY_CREDENTIALS_SUCCESS, token, account });
if (account.id === getState().me) dispatch(fetchMeSuccess(account));
return account;
@ -158,7 +156,7 @@ const verifyCredentials = (token: string, accountUrl?: string) =>
// The user is waitlisted
const account = error.response.json;
const parsedAccount = credentialAccountSchema.parse(error.response.json);
dispatch(importFetchedAccount(parsedAccount));
importEntities({ accounts: [parsedAccount] });
dispatch({ type: VERIFY_CREDENTIALS_SUCCESS, token, account: parsedAccount });
if (account.id === getState().me) dispatch(fetchMeSuccess(parsedAccount));
return parsedAccount;
@ -174,7 +172,7 @@ const rememberAuthAccount = (accountUrl: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch({ type: AUTH_ACCOUNT_REMEMBER_REQUEST, accountUrl });
return KVStore.getItemOrError(`authAccount:${accountUrl}`).then(account => {
dispatch(importFetchedAccount(account));
importEntities({ accounts: [account] });
dispatch({ type: AUTH_ACCOUNT_REMEMBER_SUCCESS, account, accountUrl });
if (account.id === getState().me) dispatch(fetchMeSuccess(account));
return account;

View file

@ -1,4 +1,4 @@
import { getClient } from '../api';
import { getClient } from 'pl-fe/api';
import type { AppDispatch, RootState } from 'pl-fe/store';

View file

@ -1,6 +1,5 @@
import { getClient } from '../api';
import { importFetchedStatuses } from './importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import type { PaginatedResponse, Status } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -24,7 +23,7 @@ const fetchBookmarkedStatuses = (folderId?: string) =>
dispatch(fetchBookmarkedStatusesRequest(folderId));
return getClient(getState()).myAccount.getBookmarks({ folder_id: folderId }).then(response => {
dispatch(importFetchedStatuses(response.items));
importEntities({ statuses: response.items });
return dispatch(fetchBookmarkedStatusesSuccess(response.items, response.next, folderId));
}).catch(error => {
dispatch(fetchBookmarkedStatusesFail(error, folderId));
@ -61,7 +60,7 @@ const expandBookmarkedStatuses = (folderId?: string) =>
dispatch(expandBookmarkedStatusesRequest(folderId));
return next().then(response => {
dispatch(importFetchedStatuses(response.items));
importEntities({ statuses: response.items });
return dispatch(expandBookmarkedStatusesSuccess(response.items, response.next, folderId));
}).catch(error => {
dispatch(expandBookmarkedStatusesFail(error, folderId));

View file

@ -5,6 +5,7 @@ import { getClient } from 'pl-fe/api';
import { isNativeEmoji } from 'pl-fe/features/emoji';
import emojiSearch from 'pl-fe/features/emoji/search';
import { Language } from 'pl-fe/features/preferences';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { selectAccount, selectOwnAccount, makeGetAccount } from 'pl-fe/selectors';
import { tagHistory } from 'pl-fe/settings';
import { useModalsStore } from 'pl-fe/stores';
@ -12,7 +13,6 @@ import toast from 'pl-fe/toast';
import { isLoggedIn } from 'pl-fe/utils/auth';
import { chooseEmoji } from './emojis';
import { importFetchedAccounts } from './importer';
import { rememberLanguageUse } from './languages';
import { uploadFile, updateMedia } from './media';
import { getSettings } from './settings';
@ -590,7 +590,7 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, composeId,
return getClient(getState).accounts.searchAccounts(token.slice(1), { resolve: false, limit: 10 }, { signal })
.then(response => {
dispatch(importFetchedAccounts(response));
importEntities({ accounts: response });
dispatch(readyComposeSuggestionsAccounts(composeId, token, response));
}).catch(error => {
if (!signal.aborted) {

View file

@ -1,13 +1,7 @@
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { isLoggedIn } from 'pl-fe/utils/auth';
import { getClient } from '../api';
import {
importFetchedAccounts,
importFetchedStatuses,
importFetchedStatus,
} from './importer';
import type { Account, Conversation, PaginatedResponse, Status } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -48,8 +42,11 @@ const expandConversations = (expand = true) => (dispatch: AppDispatch, getState:
return (state.conversations.next?.() || getClient(state).timelines.getConversations())
.then(response => {
dispatch(importFetchedAccounts(response.items.reduce((aggr: Array<Account>, item) => aggr.concat(item.accounts), [])));
dispatch(importFetchedStatuses(response.items.map((item) => item.last_status).filter((x): x is Status => x !== null)));
const accounts = response.items.reduce((aggr: Array<Account>, item) => aggr.concat(item.accounts), []);
const statuses = response.items.map((item) => item.last_status).filter((x): x is Status => x !== null);
importEntities({ accounts, statuses });
dispatch(expandConversationsSuccess(response.items, response.next, isLoadingRecent));
})
.catch(err => dispatch(expandConversationsFail(err)));
@ -74,12 +71,14 @@ const expandConversationsFail = (error: unknown) => ({
});
const updateConversations = (conversation: Conversation) => (dispatch: AppDispatch) => {
dispatch(importFetchedAccounts(conversation.accounts));
const statuses: Array<Status> = [];
if (conversation.last_status) {
dispatch(importFetchedStatus(conversation.last_status));
statuses.push(conversation.last_status);
}
importEntities({ accounts: conversation.accounts, statuses });
return dispatch({
type: CONVERSATIONS_UPDATE,
conversation,

View file

@ -1,4 +1,4 @@
import { getClient } from '../api';
import { getClient } from 'pl-fe/api';
import type { CustomEmoji } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';

View file

@ -1,7 +1,6 @@
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { getClient } from '../api';
import { fetchRelationships } from './accounts';
import type { Account, ProfileDirectoryParams } from 'pl-api';

View file

@ -1,8 +1,7 @@
import { getClient } from 'pl-fe/api';
import { Entities } from 'pl-fe/entity-store/entities';
import { isLoggedIn } from 'pl-fe/utils/auth';
import { getClient } from '../api';
import type { PaginatedResponse } from 'pl-api';
import type { EntityStore } from 'pl-fe/entity-store/types';
import type { Account } from 'pl-fe/normalizers';
@ -31,6 +30,7 @@ const blockDomain = (domain: string) =>
dispatch(blockDomainRequest(domain));
return getClient(getState).filtering.blockDomain(domain).then(() => {
// TODO: Update relationships on block
const accounts = selectAccountsByDomain(getState(), domain);
if (!accounts) return;
dispatch(blockDomainSuccess(domain, accounts));
@ -63,6 +63,7 @@ const unblockDomain = (domain: string) =>
dispatch(unblockDomainRequest(domain));
return getClient(getState).filtering.unblockDomain(domain).then(() => {
// TODO: Update relationships on unblock
const accounts = selectAccountsByDomain(getState(), domain);
if (!accounts) return;
dispatch(unblockDomainSuccess(domain, accounts));

View file

@ -5,10 +5,10 @@ import KVStore from 'pl-fe/storage/kv-store';
import type { AppDispatch, RootState } from 'pl-fe/store';
const DRAFT_STATUSES_FETCH_SUCCESS = 'DRAFT_STATUSES_FETCH_SUCCESS';
const DRAFT_STATUSES_FETCH_SUCCESS = 'DRAFT_STATUSES_FETCH_SUCCESS' as const;
const PERSIST_DRAFT_STATUS = 'PERSIST_DRAFT_STATUS';
const CANCEL_DRAFT_STATUS = 'DELETE_DRAFT_STATUS';
const PERSIST_DRAFT_STATUS = 'PERSIST_DRAFT_STATUS' as const;
const CANCEL_DRAFT_STATUS = 'DELETE_DRAFT_STATUS' as const;
const getAccount = makeGetAccount();

View file

@ -1,9 +1,7 @@
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { isLoggedIn } from 'pl-fe/utils/auth';
import { getClient } from '../api';
import { importFetchedStatus } from './importer';
import type { Status } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -24,7 +22,7 @@ const emojiReact = (status: Pick<Status, 'id'>, emoji: string, custom?: string)
dispatch(emojiReactRequest(status.id, emoji, custom));
return getClient(getState).statuses.createStatusReaction(status.id, emoji).then((response) => {
dispatch(importFetchedStatus(response));
importEntities({ statuses: [response] });
dispatch(emojiReactSuccess(response, emoji));
}).catch((error) => {
dispatch(emojiReactFail(status.id, emoji, error));
@ -38,7 +36,7 @@ const unEmojiReact = (status: Pick<Status, 'id'>, emoji: string) =>
dispatch(unEmojiReactRequest(status.id, emoji));
return getClient(getState).statuses.deleteStatusReaction(status.id, emoji).then(response => {
dispatch(importFetchedStatus(response));
importEntities({ statuses: [response] });
dispatch(unEmojiReactSuccess(response, emoji));
}).catch(error => {
dispatch(unEmojiReactFail(status.id, emoji, error));

View file

@ -1,13 +1,11 @@
import { defineMessages } from 'react-intl';
import { STATUS_FETCH_SOURCE_FAIL, STATUS_FETCH_SOURCE_REQUEST, STATUS_FETCH_SOURCE_SUCCESS } from 'pl-fe/actions/statuses';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { useModalsStore } from 'pl-fe/stores';
import toast from 'pl-fe/toast';
import { importFetchedStatus, importFetchedStatuses } from './importer';
import { STATUS_FETCH_SOURCE_FAIL, STATUS_FETCH_SOURCE_REQUEST, STATUS_FETCH_SOURCE_SUCCESS } from './statuses';
import type { Account, CreateEventParams, Location, MediaAttachment, PaginatedResponse, Status } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -133,7 +131,7 @@ const submitEvent = ({
: getClient(state).events.editEvent(statusId, params)
).then((data) => {
useModalsStore.getState().closeModal('COMPOSE_EVENT');
dispatch(importFetchedStatus(data));
importEntities({ statuses: [data] });
dispatch(submitEventSuccess(data));
toast.success(
statusId ? messages.editSuccess : messages.success,
@ -172,7 +170,7 @@ const joinEvent = (statusId: string, participationMessage?: string) =>
dispatch(joinEventRequest(status.id));
return getClient(getState).events.joinEvent(statusId, participationMessage).then((data) => {
dispatch(importFetchedStatus(data));
importEntities({ statuses: [data] });
dispatch(joinEventSuccess(status.id));
toast.success(
data.event?.join_state === 'pending' ? messages.joinRequestSuccess : messages.joinSuccess,
@ -214,7 +212,7 @@ const leaveEvent = (statusId: string) =>
dispatch(leaveEventRequest(status.id));
return getClient(getState).events.leaveEvent(statusId).then((data) => {
dispatch(importFetchedStatus(data));
importEntities({ statuses: [data] });
dispatch(leaveEventSuccess(status.id));
}).catch((error) => {
dispatch(leaveEventFail(error, status.id));
@ -480,7 +478,7 @@ const fetchRecentEvents = () =>
return getClient(getState()).timelines.publicTimeline({
only_events: true,
}).then(response => {
dispatch(importFetchedStatuses(response.items));
importEntities({ statuses: response.items });
dispatch({
type: RECENT_EVENTS_FETCH_SUCCESS,
statuses: response.items,
@ -500,7 +498,7 @@ const fetchJoinedEvents = () =>
dispatch({ type: JOINED_EVENTS_FETCH_REQUEST });
getClient(getState).events.getJoinedEvents().then(response => {
dispatch(importFetchedStatuses(response.items));
importEntities({ statuses: response.items });
dispatch({
type: JOINED_EVENTS_FETCH_SUCCESS,
statuses: response.items,

View file

@ -1,8 +1,7 @@
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { AppDispatch, RootState } from 'pl-fe/store';
import { getClient } from '../api';
import { fetchRelationships } from './accounts';
const FAMILIAR_FOLLOWERS_FETCH_REQUEST = 'FAMILIAR_FOLLOWERS_FETCH_REQUEST' as const;

View file

@ -1,9 +1,7 @@
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { isLoggedIn } from 'pl-fe/utils/auth';
import { getClient } from '../api';
import { importFetchedStatuses } from './importer';
import type { PaginatedResponse, Status } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -34,7 +32,7 @@ const fetchFavouritedStatuses = () =>
dispatch(fetchFavouritedStatusesRequest());
return getClient(getState()).myAccount.getFavourites().then(response => {
dispatch(importFetchedStatuses(response.items));
importEntities({ statuses: response.items });
dispatch(fetchFavouritedStatusesSuccess(response.items, response.next));
}).catch(error => {
dispatch(fetchFavouritedStatusesFail(error));
@ -69,7 +67,7 @@ const expandFavouritedStatuses = () =>
dispatch(expandFavouritedStatusesRequest());
return next().then(response => {
dispatch(importFetchedStatuses(response.items));
importEntities({ statuses: response.items });
dispatch(expandFavouritedStatusesSuccess(response.items, response.next));
}).catch(error => {
dispatch(expandFavouritedStatusesFail(error));
@ -102,7 +100,7 @@ const fetchAccountFavouritedStatuses = (accountId: string) =>
dispatch(fetchAccountFavouritedStatusesRequest(accountId));
return getClient(getState).accounts.getAccountFavourites(accountId).then(response => {
dispatch(importFetchedStatuses(response.items));
importEntities({ statuses: response.items });
dispatch(fetchAccountFavouritedStatusesSuccess(accountId, response.items, response.next));
}).catch(error => {
dispatch(fetchAccountFavouritedStatusesFail(accountId, error));
@ -140,7 +138,7 @@ const expandAccountFavouritedStatuses = (accountId: string) =>
dispatch(expandAccountFavouritedStatusesRequest(accountId));
return next().then(response => {
dispatch(importFetchedStatuses(response.items));
importEntities({ statuses: response.items });
dispatch(expandAccountFavouritedStatusesSuccess(accountId, response.items, response.next));
}).catch(error => {
dispatch(expandAccountFavouritedStatusesFail(accountId, error));

View file

@ -1,10 +1,9 @@
import { defineMessages } from 'react-intl';
import { getClient } from 'pl-fe/api';
import toast from 'pl-fe/toast';
import { isLoggedIn } from 'pl-fe/utils/auth';
import { getClient } from '../api';
import type { FilterContext } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';

View file

@ -1,7 +1,6 @@
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { getClient } from '../api';
import type { Account, PaginatedResponse } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';

View file

@ -1,9 +1,8 @@
import { defineMessages } from 'react-intl';
import { getClient } from 'pl-fe/api';
import toast from 'pl-fe/toast';
import { getClient } from '../api';
import type { RootState } from 'pl-fe/store';
const IMPORT_FOLLOWS_REQUEST = 'IMPORT_FOLLOWS_REQUEST' as const;

View file

@ -5,14 +5,10 @@ import { normalizeAccount, normalizeGroup, type Account, type Group } from 'pl-f
import type { Account as BaseAccount, Group as BaseGroup, Poll, Status as BaseStatus } from 'pl-api';
import type { AppDispatch } from 'pl-fe/store';
const ACCOUNT_IMPORT = 'ACCOUNT_IMPORT';
const ACCOUNTS_IMPORT = 'ACCOUNTS_IMPORT';
const STATUS_IMPORT = 'STATUS_IMPORT';
const STATUSES_IMPORT = 'STATUSES_IMPORT';
const POLLS_IMPORT = 'POLLS_IMPORT';
const importAccount = (data: BaseAccount) => importAccounts([data]);
const importAccounts = (data: Array<BaseAccount>) => {
let accounts: Array<Account> = [];
@ -113,65 +109,19 @@ const isBroken = (status: BaseStatus) => {
}
};
const importFetchedStatuses = (statuses: Array<Omit<BaseStatus, 'account'> & { account: BaseAccount | null }>) => (dispatch: AppDispatch) => {
const accounts: Record<string, BaseAccount> = {};
const normalStatuses: Array<BaseStatus> = [];
const polls: Array<Poll> = [];
const processStatus = (status: BaseStatus) => {
if (status.account === null) return;
// Skip broken statuses
if (isBroken(status)) return;
normalStatuses.push(status);
accounts[status.account.id] = status.account;
// if (status.accounts) {
// accounts.push(...status.accounts);
// }
if (status.reblog?.id) {
processStatus(status.reblog as BaseStatus);
}
// Fedibird quotes
if (status.quote?.id) {
processStatus(status.quote as BaseStatus);
}
if (status.poll?.id) {
polls.push(status.poll);
}
};
(statuses as Array<BaseStatus>).forEach(processStatus);
dispatch(importPolls(polls));
dispatch(importFetchedAccounts(Object.values(accounts)));
dispatch(importStatuses(normalStatuses));
};
const importFetchedPoll = (poll: Poll) =>
(dispatch: AppDispatch) => {
dispatch(importPolls([poll]));
};
export {
ACCOUNT_IMPORT,
ACCOUNTS_IMPORT,
STATUS_IMPORT,
STATUSES_IMPORT,
POLLS_IMPORT,
importAccount,
importAccounts,
importGroup,
importGroups,
importStatus,
importStatuses,
importPolls,
importFetchedAccount,
importFetchedAccounts,
importFetchedStatus,
importFetchedStatuses,
importFetchedPoll,
};

View file

@ -1,7 +1,6 @@
import { getClient } from 'pl-fe/api';
import { getAuthUserUrl, getMeUrl } from 'pl-fe/utils/auth';
import { getClient } from '../api';
import type { Instance } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';

View file

@ -1,12 +1,11 @@
import { defineMessages } from 'react-intl';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { useModalsStore } from 'pl-fe/stores';
import toast, { type IToastOptions } from 'pl-fe/toast';
import { isLoggedIn } from 'pl-fe/utils/auth';
import { getClient } from '../api';
import { fetchRelationships } from './accounts';
import type { Account, EmojiReaction, PaginatedResponse, Status } from 'pl-api';

View file

@ -1,11 +1,9 @@
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { selectAccount } from 'pl-fe/selectors';
import toast from 'pl-fe/toast';
import { isLoggedIn } from 'pl-fe/utils/auth';
import { getClient } from '../api';
import { importFetchedAccounts } from './importer';
import type { Account, List, PaginatedResponse } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -229,7 +227,7 @@ const fetchListAccounts = (listId: string) => (dispatch: AppDispatch, getState:
dispatch(fetchListAccountsRequest(listId));
return getClient(getState()).lists.getListAccounts(listId).then(({ items, next }) => {
dispatch(importFetchedAccounts(items));
importEntities({ accounts: items });
dispatch(fetchListAccountsSuccess(listId, items, next));
}).catch(err => dispatch(fetchListAccountsFail(listId, err)));
};
@ -256,7 +254,7 @@ const fetchListSuggestions = (q: string) => (dispatch: AppDispatch, getState: ()
if (!isLoggedIn(getState)) return;
return getClient(getState()).accounts.searchAccounts(q, { resolve: false, limit: 4, following: true }).then((data) => {
dispatch(importFetchedAccounts(data));
importEntities({ accounts: data });
dispatch(fetchListSuggestionsReady(q, data));
}).catch(error => toast.showAlertForError(error));
};

View file

@ -1,4 +1,4 @@
import { getClient } from '../api';
import { getClient } from 'pl-fe/api';
import type { SaveMarkersParams } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';

View file

@ -1,12 +1,11 @@
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { selectAccount } from 'pl-fe/selectors';
import { setSentryAccount } from 'pl-fe/sentry';
import KVStore from 'pl-fe/storage/kv-store';
import { getAuthUserId, getAuthUserUrl } from 'pl-fe/utils/auth';
import { getClient } from '../api';
import { loadCredentials } from './auth';
import { importFetchedAccount } from './importer';
import type { CredentialAccount, UpdateCredentialsParams } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -123,7 +122,7 @@ const patchMeSuccess = (me: CredentialAccount) =>
me,
};
dispatch(importFetchedAccount(me));
importEntities({ accounts: [me] });
dispatch(action);
};

View file

@ -1,12 +1,11 @@
import { defineMessages, type IntlShape } from 'react-intl';
import { getClient } from 'pl-fe/api';
import toast from 'pl-fe/toast';
import { isLoggedIn } from 'pl-fe/utils/auth';
import { formatBytes, getVideoDuration } from 'pl-fe/utils/media';
import resizeImage from 'pl-fe/utils/resize-image';
import { getClient } from '../api';
import type { MediaAttachment, UploadMediaParams } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';

View file

@ -1,4 +1,4 @@
import { getClient } from '../api';
import { getClient } from 'pl-fe/api';
import type { AppDispatch, RootState } from 'pl-fe/store';

View file

@ -4,6 +4,7 @@ import { defineMessages } from 'react-intl';
import { getNotificationStatus } from 'pl-fe/features/notifications/components/notification';
import { normalizeNotification } from 'pl-fe/normalizers';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { getFilters, regexFromFilters } from 'pl-fe/selectors';
import { isLoggedIn } from 'pl-fe/utils/auth';
import { compareId } from 'pl-fe/utils/comparators';
@ -11,14 +12,10 @@ import { unescapeHTML } from 'pl-fe/utils/html';
import { joinPublicPath } from 'pl-fe/utils/static';
import { fetchRelationships } from './accounts';
import {
importFetchedAccount,
importFetchedStatus,
} from './importer';
import { saveMarker } from './markers';
import { getSettings, saveSettings } from './settings';
import type { Notification as BaseNotification } from 'pl-api';
import type { Notification } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE' as const;
@ -54,7 +51,7 @@ defineMessages({
mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' },
});
const fetchRelatedRelationships = (dispatch: AppDispatch, notifications: Array<BaseNotification>) => {
const fetchRelatedRelationships = (dispatch: AppDispatch, notifications: Array<Notification>) => {
const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id);
if (accountIds.length > 0) {
@ -62,24 +59,11 @@ const fetchRelatedRelationships = (dispatch: AppDispatch, notifications: Array<B
}
};
const updateNotifications = (notification: BaseNotification) =>
const updateNotifications = (notification: Notification) =>
(dispatch: AppDispatch, getState: () => RootState) => {
const showInColumn = getSettings(getState()).getIn(['notifications', 'shows', notification.type], true);
if (notification.account) {
dispatch(importFetchedAccount(notification.account));
}
// Used by Move notification
if (notification.type === 'move' && notification.target) {
dispatch(importFetchedAccount(notification.target));
}
const status = getNotificationStatus(notification);
if (status) {
dispatch(importFetchedStatus(status));
}
importEntities({ notifications: [{ ...notification, accounts: [notification.account], duplicate: false }] });
if (showInColumn) {
dispatch({
@ -91,7 +75,7 @@ const updateNotifications = (notification: BaseNotification) =>
}
};
const updateNotificationsQueue = (notification: BaseNotification, intlMessages: Record<string, string>, intlLocale: string, curPath: string) =>
const updateNotificationsQueue = (notification: Notification, intlMessages: Record<string, string>, intlLocale: string, curPath: string) =>
(dispatch: AppDispatch, getState: () => RootState) => {
if (!notification.type) return; // drop invalid notifications
if (notification.type === 'chat_mention') return; // Drop chat notifications, handle them per-chat

View file

@ -1,9 +1,7 @@
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { isLoggedIn } from 'pl-fe/utils/auth';
import { getClient } from '../api';
import { importFetchedStatuses } from './importer';
import type { Status } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -19,7 +17,7 @@ const fetchPinnedStatuses = () =>
dispatch(fetchPinnedStatusesRequest());
return getClient(getState()).accounts.getAccountStatuses(me as string, { pinned: true }).then(response => {
dispatch(importFetchedStatuses(response.items));
importEntities({ statuses: response.items });
dispatch(fetchPinnedStatusesSuccess(response.items, null));
}).catch(error => {
dispatch(fetchPinnedStatusesFail(error));

View file

@ -1,11 +1,10 @@
import { createSelector } from 'reselect';
import { getHost } from 'pl-fe/actions/instance';
import { getClient, staticFetch } from 'pl-fe/api';
import { normalizePlFeConfig } from 'pl-fe/normalizers';
import KVStore from 'pl-fe/storage/kv-store';
import { getClient, staticFetch } from '../api';
import type { AppDispatch, RootState } from 'pl-fe/store';
import type { APIEntity } from 'pl-fe/types/entities';

View file

@ -1,6 +1,5 @@
import { getClient } from '../api';
import { importFetchedPoll } from './importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import type { Poll } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -18,7 +17,7 @@ const vote = (pollId: string, choices: number[]) =>
dispatch(voteRequest());
return getClient(getState()).polls.vote(pollId, choices).then((data) => {
dispatch(importFetchedPoll(data));
importEntities({ polls: [data] });
dispatch(voteSuccess(data));
}).catch(err => dispatch(voteFail(err)));
};
@ -28,7 +27,7 @@ const fetchPoll = (pollId: string) =>
dispatch(fetchPollRequest());
return getClient(getState()).polls.getPoll(pollId).then((data) => {
dispatch(importFetchedPoll(data));
importEntities({ polls: [data] });
dispatch(fetchPollSuccess(data));
}).catch(err => dispatch(fetchPollFail(err)));
};

View file

@ -1,7 +1,8 @@
import mapValues from 'lodash/mapValues';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { verifyCredentials } from './auth';
import { importFetchedAccounts } from './importer';
import type { AppDispatch } from 'pl-fe/store';
@ -54,7 +55,7 @@ const preloadMastodon = (data: Record<string, any>) =>
const { me, access_token } = data.meta;
const { url } = data.accounts[me];
dispatch(importFetchedAccounts(Object.values(data.accounts)));
importEntities({ accounts: Object.values(data.accounts) });
dispatch(verifyCredentials(access_token, url));
dispatch({ type: MASTODON_PRELOAD_IMPORT, data });
};

View file

@ -1,4 +1,4 @@
import { getClient } from '../api';
import { getClient } from 'pl-fe/api';
import type { CreatePushNotificationsSubscriptionParams, UpdatePushNotificationsSubscriptionParams } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';

View file

@ -1,7 +1,6 @@
import { getClient } from 'pl-fe/api';
import { useModalsStore } from 'pl-fe/stores';
import { getClient } from '../api';
import type { Account, Status } from 'pl-fe/normalizers';
import type { AppDispatch, RootState } from 'pl-fe/store';

View file

@ -1,4 +1,4 @@
import { getClient } from '../api';
import { getClient } from 'pl-fe/api';
import type { PaginatedResponse, ScheduledStatus } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';

View file

@ -1,7 +1,7 @@
import { getClient } from '../api';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts, importFetchedStatuses } from './importer';
import type { Search } from 'pl-api';
import type { SearchFilter } from 'pl-fe/reducers/search';
@ -52,13 +52,7 @@ const submitSearch = (value: string, filter?: SearchFilter) =>
if (accountId) params.account_id = accountId;
return getClient(getState()).search.search(value, params).then(response => {
if (response.accounts) {
dispatch(importFetchedAccounts(response.accounts));
}
if (response.statuses) {
dispatch(importFetchedStatuses(response.statuses));
}
importEntities({ accounts: response.accounts, statuses: response.statuses, groups: response.groups });
dispatch(fetchSearchSuccess(response, value, type));
dispatch(fetchRelationships(response.accounts.map((item) => item.id)));
@ -110,13 +104,7 @@ const expandSearch = (type: SearchFilter) => (dispatch: AppDispatch, getState: (
if (accountId) params.account_id = accountId;
return getClient(getState()).search.search(value, params).then(response => {
if (response.accounts) {
dispatch(importFetchedAccounts(response.accounts));
}
if (response.statuses) {
dispatch(importFetchedStatuses(response.statuses));
}
importEntities({ accounts: response.accounts, statuses: response.statuses, groups: response.groups });
dispatch(expandSearchSuccess(response, value, type));
dispatch(fetchRelationships(response.accounts.map((item) => item.id)));

View file

@ -1,6 +1,5 @@
import { getClient } from '../api';
import { importFetchedStatuses } from './importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import type { Status as BaseStatus, PaginatedResponse } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -43,7 +42,7 @@ const fetchStatusQuotes = (statusId: string) =>
dispatch(action);
return getClient(getState).statuses.getStatusQuotes(statusId).then(response => {
dispatch(importFetchedStatuses(response.items));
importEntities({ statuses: response.items });
const action: FetchStatusQuotesSuccessAction = {
type: STATUS_QUOTES_FETCH_SUCCESS,
statusId,
@ -94,7 +93,7 @@ const expandStatusQuotes = (statusId: string) =>
dispatch(action);
return next().then(response => {
dispatch(importFetchedStatuses(response.items));
importEntities({ statuses: response.items });
const action: ExpandStatusQuotesSuccessAction = {
type: STATUS_QUOTES_EXPAND_SUCCESS,
statusId,

View file

@ -1,11 +1,11 @@
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { useModalsStore } from 'pl-fe/stores';
import { isLoggedIn } from 'pl-fe/utils/auth';
import { shouldHaveCard } from 'pl-fe/utils/status';
import { getClient } from '../api';
import { setComposeToStatus } from './compose';
import { importFetchedStatus, importFetchedStatuses } from './importer';
import { importFetchedStatus } from './importer';
import { getSettings } from './settings';
import { deleteFromTimelines } from './timelines';
@ -76,7 +76,7 @@ const createStatus = (params: CreateStatusParams, idempotencyKey: string, status
const poll = (retries = 5) => {
return getClient(getState()).statuses.getStatus(status.id).then(response => {
if (response.card) {
dispatch(importFetchedStatus(response));
importEntities({ statuses: [response] });
} else if (retries > 0 && response) {
setTimeout(() => poll(retries - 1), delay);
}
@ -119,7 +119,7 @@ const fetchStatus = (statusId: string, intl?: IntlShape) =>
} : undefined;
return getClient(getState()).statuses.getStatus(statusId, params).then(status => {
dispatch(importFetchedStatus(status));
importEntities({ statuses: [status] });
dispatch({ type: STATUS_FETCH_SUCCESS, status });
return status;
}).catch(error => {
@ -153,7 +153,7 @@ const deleteStatus = (statusId: string, withRedraft = false) =>
};
const updateStatus = (status: BaseStatus) => (dispatch: AppDispatch) =>
dispatch(importFetchedStatus(status));
importEntities({ statuses: [status] });
const fetchContext = (statusId: string, intl?: IntlShape) =>
(dispatch: AppDispatch, getState: () => RootState) => {
@ -167,7 +167,7 @@ const fetchContext = (statusId: string, intl?: IntlShape) =>
if (typeof context === 'object') {
const { ancestors, descendants } = context;
const statuses = ancestors.concat(descendants);
dispatch(importFetchedStatuses(statuses));
importEntities({ statuses });
dispatch({ type: CONTEXT_FETCH_SUCCESS, statusId, ancestors, descendants });
} else {
throw context;

View file

@ -1,7 +1,7 @@
import { getClient } from '../api';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts } from './importer';
import { insertSuggestionsIntoTimeline } from './timelines';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -24,7 +24,8 @@ const fetchSuggestions = (limit = 50) =>
return getClient(getState).myAccount.getSuggestions(limit).then((suggestions) => {
const accounts = suggestions.map(({ account }) => account);
dispatch(importFetchedAccounts(accounts));
importEntities({ accounts });
dispatch({ type: SUGGESTIONS_FETCH_SUCCESS, suggestions });
dispatch(fetchRelationships(accounts.map(({ id }) => id)));

View file

@ -1,4 +1,4 @@
import { getClient } from '../api';
import { getClient } from 'pl-fe/api';
import type { PaginatedResponse, Tag } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';

View file

@ -1,12 +1,10 @@
import { Map as ImmutableMap } from 'immutable';
import { getLocale, getSettings } from 'pl-fe/actions/settings';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { shouldFilter } from 'pl-fe/utils/timelines';
import { getClient } from '../api';
import { importFetchedStatus, importFetchedStatuses } from './importer';
import type { PaginatedResponse, Status as BaseStatus, PublicTimelineParams, HomeTimelineParams, ListTimelineParams, HashtagTimelineParams, GetAccountStatusesParams, GroupTimelineParams } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -45,7 +43,7 @@ const processTimelineUpdate = (timeline: string, status: BaseStatus) =>
return;
}
dispatch(importFetchedStatus(status));
importEntities({ statuses: [status] });
if (shouldSkipQueue) {
dispatch(updateTimeline(timeline, status.id));
@ -158,10 +156,9 @@ const handleTimelineExpand = (timelineId: string, fn: Promise<PaginatedResponse<
dispatch(expandTimelineRequest(timelineId));
return fn.then(response => {
dispatch(importFetchedStatuses(response.items));
const statuses = deduplicateStatuses(response.items);
dispatch(importFetchedStatuses(statuses.filter(status => status.accounts)));
importEntities({ statuses: [...response.items, ...statuses.filter(status => status.accounts)] });
dispatch(expandTimelineSuccess(
timelineId,

View file

@ -1,6 +1,5 @@
import { getClient } from '../api';
import { importFetchedStatuses } from './importer';
import { getClient } from 'pl-fe/api';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -18,7 +17,7 @@ const fetchTrendingStatuses = () =>
dispatch({ type: TRENDING_STATUSES_FETCH_REQUEST });
return client.trends.getTrendingStatuses().then((statuses) => {
dispatch(importFetchedStatuses(statuses));
importEntities({ statuses });
dispatch({ type: TRENDING_STATUSES_FETCH_SUCCESS, statuses });
return statuses;
}).catch(error => {

View file

@ -1,6 +1,6 @@
import type { Tag } from 'pl-api';
const TRENDS_FETCH_SUCCESS = 'TRENDS_FETCH_SUCCESS';
const TRENDS_FETCH_SUCCESS = 'TRENDS_FETCH_SUCCESS' as const;
const fetchTrendsSuccess = (tags: Array<Tag>) => ({
type: TRENDS_FETCH_SUCCESS,

View file

@ -1,7 +1,7 @@
import clsx from 'clsx';
import parse, { Element, type HTMLReactParserOptions, domToReact, type DOMNode } from 'html-react-parser';
import React, { useState, useRef, useLayoutEffect, useMemo, useEffect } from 'react';
import { FormattedMessage } from 'react-intl';
import { FormattedMessage } from 'react-intl';
import { Link } from 'react-router-dom';
import { collapseStatusSpoiler, expandStatusSpoiler } from 'pl-fe/actions/statuses';

View file

@ -1,10 +1,10 @@
import React from 'react';
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
import { importFetchedStatuses } from 'pl-fe/actions/importer';
import { expandTimelineSuccess } from 'pl-fe/actions/timelines';
import { useAppDispatch, useTheme } from 'pl-fe/hooks';
import { useIsMobile } from 'pl-fe/hooks/useIsMobile';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { Column } from '../../components/ui';
import Timeline from '../ui/components/timeline';
@ -35,7 +35,7 @@ const TestTimeline: React.FC = () => {
const isMobile = useIsMobile();
React.useEffect(() => {
dispatch(importFetchedStatuses(MOCK_STATUSES));
importEntities({ statuses: MOCK_STATUSES });
dispatch(expandTimelineSuccess(timelineId, MOCK_STATUSES, null, null, false, false));
}, []);

View file

@ -29,7 +29,6 @@ const UploadButton: React.FC<IUploadButton> = ({ disabled, onSelectFile }) => {
fileElement.current?.click();
};
return (
<HStack className='size-full cursor-pointer text-primary-500 dark:text-accent-blue' space={3} alignItems='center' justifyContent='center' element='label'>
<Icon

View file

@ -4,7 +4,7 @@ export { normalizeAnnouncement, type AdminAnnouncement, type Announcement } from
export { normalizeChatMessage, type ChatMessage } from './chat-message';
export { normalizeGroup, type Group } from './group';
export { normalizeGroupMember, type GroupMember } from './group-member';
export { normalizeNotification, normalizeNotifications, type Notification } from './notification';
export { normalizeNotification, type Notification } from './notification';
export { normalizePoll, normalizePollEdit, type Poll, type PollEdit } from './poll';
export { normalizeStatus, type Status } from './status';
export { normalizeStatusEdit, type StatusEdit } from './status-edit';

View file

@ -1,61 +1,11 @@
import { getNotificationStatus } from 'pl-fe/features/notifications/components/notification';
import { normalizeAccount } from './account';
import type { OrderedMap as ImmutableOrderedMap } from 'immutable';
import type { Notification as BaseNotification } from 'pl-api';
import type { MinifiedNotification } from 'pl-fe/reducers/notifications';
const STATUS_NOTIFICATION_TYPES = [
'favourite',
'reblog',
'emoji_reaction',
'event_reminder',
'participation_accepted',
'participation_request',
];
const normalizeNotification = (notification: BaseNotification) => ({
...notification,
accounts: [notification.account],
duplicate: false,
account: normalizeAccount(notification.account),
account_id: notification.account.id,
accounts: [normalizeAccount(notification.account)],
account_ids: [notification.account.id],
...notification,
});
const normalizeNotifications = (notifications: Array<BaseNotification>, stateNotifications?: ImmutableOrderedMap<string, MinifiedNotification>) => {
const deduplicatedNotifications: Notification[] = [];
for (const notification of notifications) {
const existingNotification = stateNotifications?.get(notification.id);
// Do not update grouped notifications
if (existingNotification && (existingNotification.duplicate || existingNotification.account_ids.length)) continue;
if (STATUS_NOTIFICATION_TYPES.includes(notification.type)) {
const existingNotification = deduplicatedNotifications
.find(deduplicated =>
deduplicated.type === notification.type
&& ((notification.type === 'emoji_reaction' && deduplicated.type === 'emoji_reaction') ? notification.emoji === deduplicated.emoji : true)
&& getNotificationStatus(deduplicated)?.id === getNotificationStatus(notification)?.id,
);
if (existingNotification) {
existingNotification.accounts.push(normalizeAccount(notification.account));
existingNotification.account_ids.push(notification.account.id);
deduplicatedNotifications.push({ ...normalizeNotification(notification), duplicate: true });
} else {
deduplicatedNotifications.push(normalizeNotification(notification));
}
} else {
deduplicatedNotifications.push(normalizeNotification(notification));
}
}
return deduplicatedNotifications;
};
type Notification = ReturnType<typeof normalizeNotification>;
export { normalizeNotification, normalizeNotifications, type Notification };
export { normalizeNotification, type Notification };

View file

@ -1,85 +1,13 @@
import { useQuery } from '@tanstack/react-query';
import omit from 'lodash/omit';
import { useAppSelector, useClient } from 'pl-fe/hooks';
import { normalizeNotification, type Notification } from 'pl-fe/normalizers';
import { type MinifiedNotification, minifyNotification } from 'pl-fe/pl-hooks/minifiers/minifyNotification';
import { queryClient } from 'pl-fe/queries/client';
import { selectAccount, selectAccounts } from 'pl-fe/selectors';
import type { AccountWarning, RelationshipSeveranceEvent } from 'pl-api';
type Account = ReturnType<typeof selectAccount>;
const minifyNotification = (notification: Notification) => {
// @ts-ignore
const minifiedNotification: {
duplicate: boolean;
account_id: string;
account_ids: string[];
created_at: string;
id: string;
group_key: string;
} & (
| { type: 'follow' | 'follow_request' | 'admin.sign_up' | 'bite' }
| {
type: 'mention';
subtype?: 'reply';
status_id: string;
}
| {
type: 'status' | 'reblog' | 'favourite' | 'poll' | 'update' | 'event_reminder';
status_id: string;
}
| {
type: 'admin.report';
report: Report;
}
| {
type: 'severed_relationships';
relationship_severance_event: RelationshipSeveranceEvent;
}
| {
type: 'moderation_warning';
moderation_warning: AccountWarning;
}
| {
type: 'move';
target_id: string;
}
| {
type: 'emoji_reaction';
emoji: string;
emoji_url: string | null;
status_id: string;
}
| {
type: 'chat_mention';
chat_message_id: string;
}
| {
type: 'participation_accepted' | 'participation_request';
status_id: string;
participation_message: string | null;
}
) = {
...omit(notification, ['account', 'accounts']),
created_at: notification.created_at,
id: notification.id,
type: notification.type,
};
// @ts-ignore
if (notification.status) minifiedNotification.status_id = notification.status.id;
// @ts-ignore
if (notification.target) minifiedNotification.target_id = notification.target.id;
// @ts-ignore
if (notification.chat_message) minifiedNotification.chat_message_id = notification.chat_message.id;
return minifiedNotification;
};
type MinifiedNotification = ReturnType<typeof minifyNotification>;
const importNotification = (notification: MinifiedNotification) => {
queryClient.setQueryData<MinifiedNotification>(
['notifications', 'entities', notification.id],

View file

@ -1,20 +1,14 @@
import { useInfiniteQuery } from '@tanstack/react-query';
import { getNotificationStatus } from 'pl-fe/features/notifications/components/notification';
import { useClient } from 'pl-fe/hooks';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { deduplicateNotifications } from 'pl-fe/pl-hooks/normalizers/deduplicateNotifications';
import { queryClient } from 'pl-fe/queries/client';
import { flattenPages } from 'pl-fe/utils/queries';
import type {
Account as BaseAccount,
Notification as BaseNotification,
PaginatedResponse,
PlApiClient,
} from 'pl-api';
import type { Notification as BaseNotification, PaginatedResponse, PlApiClient } from 'pl-api';
import type { NotificationType } from 'pl-fe/utils/notification';
type UseNotificationParams = {
types?: Array<NotificationType>;
excludeTypes?: Array<NotificationType>;
@ -25,46 +19,6 @@ const getQueryKey = (params: UseNotificationParams) => [
params.types ? params.types.join('|') : params.excludeTypes ? ('exclude:' + params.excludeTypes.join('|')) : 'all',
];
type DeduplicatedNotification = BaseNotification & {
accounts: Array<BaseAccount>;
duplicate?: boolean;
}
const STATUS_NOTIFICATION_TYPES = [
'favourite',
'reblog',
'emoji_reaction',
'event_reminder',
'participation_accepted',
'participation_request',
];
const deduplicateNotifications = (notifications: Array<BaseNotification>) => {
const deduplicatedNotifications: DeduplicatedNotification[] = [];
for (const notification of notifications) {
if (STATUS_NOTIFICATION_TYPES.includes(notification.type)) {
const existingNotification = deduplicatedNotifications
.find(deduplicated =>
deduplicated.type === notification.type
&& ((notification.type === 'emoji_reaction' && deduplicated.type === 'emoji_reaction') ? notification.emoji === deduplicated.emoji : true)
&& getNotificationStatus(deduplicated)?.id === getNotificationStatus(notification)?.id,
);
if (existingNotification) {
existingNotification.accounts.push(notification.account);
deduplicatedNotifications.push({ ...notification, accounts: [notification.account], duplicate: true });
} else {
deduplicatedNotifications.push({ ...notification, accounts: [notification.account], duplicate: false });
}
} else {
deduplicatedNotifications.push({ ...notification, accounts: [notification.account], duplicate: false });
}
}
return deduplicatedNotifications;
};
const importNotifications = (response: PaginatedResponse<BaseNotification>) => {
const deduplicatedNotifications = deduplicateNotifications(response.items);
@ -72,10 +26,6 @@ const importNotifications = (response: PaginatedResponse<BaseNotification>) => {
notifications: deduplicatedNotifications,
});
// const normalizedNotifications = normalizeNotifications(response.items);
// normalizedNotifications.map(minifyNotification).forEach(importNotification);
return {
items: deduplicatedNotifications.filter(({ duplicate }) => !duplicate).map(({ id }) => id),
previous: response.previous,
@ -115,4 +65,4 @@ const prefetchNotifications = (client: PlApiClient, params: UseNotificationParam
getNextPageParam: (response) => response,
});
export { useNotifications, prefetchNotifications, type DeduplicatedNotification };
export { useNotifications, prefetchNotifications };

View file

@ -1,5 +1,3 @@
import omit from 'lodash/omit';
import { importAccounts, importGroups, importPolls, importStatuses } from 'pl-fe/actions/importer';
import { importEntities as importEntityStoreEntities } from 'pl-fe/entity-store/actions';
import { Entities } from 'pl-fe/entity-store/entities';
@ -9,92 +7,18 @@ let dispatch: AppDispatch;
import('pl-fe/store').then(value => dispatch = value.store.dispatch).catch(() => {});
import { DeduplicatedNotification } from './hooks/notifications/useNotifications';
import { MinifiedNotification, minifyNotification } from './minifiers/minifyNotification';
import { DeduplicatedNotification } from './normalizers/deduplicateNotifications';
import type {
AccountWarning,
Account as BaseAccount,
Group as BaseGroup,
Poll as BasePoll,
Relationship as BaseRelationship,
Status as BaseStatus,
RelationshipSeveranceEvent,
StatusWithoutAccount as BaseStatus,
} from 'pl-api';
import type { AppDispatch } from 'pl-fe/store';
const minifyNotification = (notification: DeduplicatedNotification) => {
// @ts-ignore
const minifiedNotification: {
duplicate: boolean;
account_id: string;
account_ids: string[];
created_at: string;
id: string;
group_key: string;
} & (
| { type: 'follow' | 'follow_request' | 'admin.sign_up' | 'bite' }
| {
type: 'mention';
subtype?: 'reply';
status_id: string;
}
| {
type: 'status' | 'reblog' | 'favourite' | 'poll' | 'update' | 'event_reminder';
status_id: string;
}
| {
type: 'admin.report';
report: Report;
}
| {
type: 'severed_relationships';
relationship_severance_event: RelationshipSeveranceEvent;
}
| {
type: 'moderation_warning';
moderation_warning: AccountWarning;
}
| {
type: 'move';
target_id: string;
}
| {
type: 'emoji_reaction';
emoji: string;
emoji_url: string | null;
status_id: string;
}
| {
type: 'chat_mention';
chat_message_id: string;
}
| {
type: 'participation_accepted' | 'participation_request';
status_id: string;
participation_message: string | null;
}
) = {
...omit(notification, ['account', 'accounts', 'status', 'target', 'chat_message']),
account_id: notification.account.id,
account_ids: notification.accounts.map(({ id }) => id),
created_at: notification.created_at,
id: notification.id,
type: notification.type,
};
// @ts-ignore
if (notification.status) minifiedNotification.status_id = notification.status.id;
// @ts-ignore
if (notification.target) minifiedNotification.target_id = notification.target.id;
// @ts-ignore
if (notification.chat_message) minifiedNotification.chat_message_id = notification.chat_message.id;
return minifiedNotification;
};
type MinifiedNotification = ReturnType<typeof minifyNotification>;
const importNotification = (notification: DeduplicatedNotification) => {
queryClient.setQueryData<MinifiedNotification>(
['notifications', 'entities', notification.id],
@ -102,9 +26,16 @@ const importNotification = (notification: DeduplicatedNotification) => {
);
};
const isEmpty = (object: Record<string, any>) => {
for (const i in object) return false;
return true;
};
const importEntities = (entities: {
accounts?: Array<BaseAccount>;
groups?: Array<BaseGroup>;
notifications?: Array<DeduplicatedNotification>;
polls?: Array<BasePoll>;
statuses?: Array<BaseStatus>;
relationships?: Array<BaseRelationship>;
}) => {
@ -133,8 +64,9 @@ const importEntities = (entities: {
};
const processStatus = (status: BaseStatus) => {
statuses[status.id] = status;
if (!statuses[status.id] || status.account || !statuses[status.id].account) statuses[status.id] = status;
if (status.account) processAccount(status.account);
if (status.quote) processStatus(status.quote);
if (status.reblog) processStatus(status.reblog);
if (status.poll) polls[status.poll.id] = status.poll;
@ -142,15 +74,18 @@ const importEntities = (entities: {
};
entities.accounts?.forEach(processAccount);
entities.groups?.forEach(group => groups[group.id] = group);
entities.notifications?.forEach(processNotification);
entities.polls?.forEach(poll => polls[poll.id] = poll);
entities.relationships?.forEach(relationship => relationships[relationship.id] = relationship);
entities.statuses?.forEach(processStatus);
dispatch(importAccounts(Object.values(accounts)));
dispatch(importGroups(Object.values(groups)));
Object.values(notifications).forEach(importNotification);
dispatch(importPolls(Object.values(polls)));
dispatch(importStatuses(Object.values(statuses)));
dispatch(importEntityStoreEntities(Object.values(relationships), Entities.RELATIONSHIPS));
if (!isEmpty(accounts)) dispatch(importAccounts(Object.values(accounts)));
if (!isEmpty(groups)) dispatch(importGroups(Object.values(groups)));
if (!isEmpty(notifications)) Object.values(notifications).forEach(importNotification);
if (!isEmpty(polls)) dispatch(importPolls(Object.values(polls)));
if (!isEmpty(relationships)) dispatch(importEntityStoreEntities(Object.values(relationships), Entities.RELATIONSHIPS));
if (!isEmpty(statuses)) dispatch(importStatuses(Object.values(statuses)));
};
export { importEntities };

View file

@ -0,0 +1,79 @@
import omit from 'lodash/omit';
import { DeduplicatedNotification } from '../normalizers/deduplicateNotifications';
import type { AccountWarning, RelationshipSeveranceEvent } from 'pl-api';
const minifyNotification = (notification: DeduplicatedNotification) => {
// @ts-ignore
const minifiedNotification: {
duplicate: boolean;
account_id: string;
account_ids: string[];
created_at: string;
id: string;
group_key: string;
} & (
| { type: 'follow' | 'follow_request' | 'admin.sign_up' | 'bite' }
| {
type: 'mention';
subtype?: 'reply';
status_id: string;
}
| {
type: 'status' | 'reblog' | 'favourite' | 'poll' | 'update' | 'event_reminder';
status_id: string;
}
| {
type: 'admin.report';
report: Report;
}
| {
type: 'severed_relationships';
relationship_severance_event: RelationshipSeveranceEvent;
}
| {
type: 'moderation_warning';
moderation_warning: AccountWarning;
}
| {
type: 'move';
target_id: string;
}
| {
type: 'emoji_reaction';
emoji: string;
emoji_url: string | null;
status_id: string;
}
| {
type: 'chat_mention';
chat_message_id: string;
}
| {
type: 'participation_accepted' | 'participation_request';
status_id: string;
participation_message: string | null;
}
) = {
...omit(notification, ['account', 'accounts', 'status', 'target', 'chat_message']),
account_id: notification.account.id,
account_ids: notification.accounts.map(({ id }) => id),
created_at: notification.created_at,
id: notification.id,
type: notification.type,
};
// @ts-ignore
if (notification.status) minifiedNotification.status_id = notification.status.id;
// @ts-ignore
if (notification.target) minifiedNotification.target_id = notification.target.id;
// @ts-ignore
if (notification.chat_message) minifiedNotification.chat_message_id = notification.chat_message.id;
return minifiedNotification;
};
type MinifiedNotification = ReturnType<typeof minifyNotification>;
export { minifyNotification, type MinifiedNotification };

View file

@ -0,0 +1,45 @@
import { getNotificationStatus } from 'pl-fe/features/notifications/components/notification';
import type { Account as BaseAccount, Notification as BaseNotification } from 'pl-api';
type DeduplicatedNotification = BaseNotification & {
accounts: Array<BaseAccount>;
duplicate?: boolean;
}
const STATUS_NOTIFICATION_TYPES = [
'favourite',
'reblog',
'emoji_reaction',
'event_reminder',
'participation_accepted',
'participation_request',
];
const deduplicateNotifications = (notifications: Array<BaseNotification>) => {
const deduplicatedNotifications: DeduplicatedNotification[] = [];
for (const notification of notifications) {
if (STATUS_NOTIFICATION_TYPES.includes(notification.type)) {
const existingNotification = deduplicatedNotifications
.find(deduplicated =>
deduplicated.type === notification.type
&& ((notification.type === 'emoji_reaction' && deduplicated.type === 'emoji_reaction') ? notification.emoji === deduplicated.emoji : true)
&& getNotificationStatus(deduplicated)?.id === getNotificationStatus(notification)?.id,
);
if (existingNotification) {
existingNotification.accounts.push(notification.account);
deduplicatedNotifications.push({ ...notification, accounts: [notification.account], duplicate: true });
} else {
deduplicatedNotifications.push({ ...notification, accounts: [notification.account], duplicate: false });
}
} else {
deduplicatedNotifications.push({ ...notification, accounts: [notification.account], duplicate: false });
}
}
return deduplicatedNotifications;
};
export { deduplicateNotifications, type DeduplicatedNotification };

View file

@ -1,22 +1,14 @@
import { useMutation } from '@tanstack/react-query';
import { fetchRelationshipsFail, fetchRelationshipsSuccess } from 'pl-fe/actions/accounts';
import { useAppDispatch, useClient } from 'pl-fe/hooks';
import { useClient } from 'pl-fe/hooks';
const useFetchRelationships = () => {
const client = useClient();
const dispatch = useAppDispatch();
return useMutation({
mutationFn: ({ accountIds }: { accountIds: string[]}) => {
return client.accounts.getRelationships(accountIds);
},
onSuccess(response) {
dispatch(fetchRelationshipsSuccess(response));
},
onError(error) {
dispatch(fetchRelationshipsFail(error));
},
});
};

View file

@ -1,8 +1,8 @@
import { useMutation, keepPreviousData, useQuery } from '@tanstack/react-query';
import { fetchRelationships } from 'pl-fe/actions/accounts';
import { importFetchedAccounts } from 'pl-fe/actions/importer';
import { useAppDispatch, useClient } from 'pl-fe/hooks';
import { importEntities } from 'pl-fe/pl-hooks/importer';
import { removePageItem } from '../utils/queries';
@ -19,7 +19,7 @@ const useSuggestions = () => {
const accounts = response.map(({ account }) => account);
const accountIds = accounts.map((account) => account.id);
dispatch(importFetchedAccounts(accounts));
importEntities({ accounts });
dispatch(fetchRelationships(accountIds));
return response.map(({ account, ...x }) => ({ ...x, account_id: account.id }));
@ -59,7 +59,7 @@ const useOnboardingSuggestions = () => {
const accounts = response.map(({ account }) => account);
const accountIds = accounts.map((account) => account.id);
dispatch(importFetchedAccounts(accounts));
importEntities({ accounts });
dispatch(fetchRelationships(accountIds));
return response;

View file

@ -1,7 +1,6 @@
import { Record as ImmutableRecord } from 'immutable';
import { combineReducers } from 'redux-immutable';
import { AUTH_LOGGED_OUT } from 'pl-fe/actions/auth';
import * as BuildConfig from 'pl-fe/build-config';
import entities from 'pl-fe/entity-store/reducer';
@ -35,7 +34,6 @@ import plfe from './pl-fe';
import polls from './polls';
import profile_hover_card from './profile-hover-card';
import push_notifications from './push-notifications';
import relationships from './relationships';
import scheduled_statuses from './scheduled-statuses';
import search from './search';
import security from './security';
@ -83,7 +81,6 @@ const reducers = {
polls,
profile_hover_card,
push_notifications,
relationships,
scheduled_statuses,
search,
security,
@ -128,7 +125,7 @@ const logOut = (state: any = StateRecord()): ReturnType<typeof appReducer> => {
const rootReducer: typeof appReducer = (state, action) => {
switch (action.type) {
case AUTH_LOGGED_OUT:
case 'AUTH_LOGGED_OUT':
return appReducer(logOut(state), action);
default:
return appReducer(state, action);

View file

@ -1,40 +0,0 @@
import { Map as ImmutableMap } from 'immutable';
import lain from 'pl-fe/__fixtures__/lain.json';
import { ACCOUNT_IMPORT } from 'pl-fe/actions/importer';
import reducer from './relationships';
describe('relationships reducer', () => {
it('should return the initial state', () => {
expect(reducer(undefined, {} as any)).toEqual(ImmutableMap());
});
describe('ACCOUNT_IMPORT', () => {
it('should import the relationship', () => {
const action = {
type: ACCOUNT_IMPORT,
account: lain,
};
const state = ImmutableMap<string, any>();
expect(reducer(state, action).toJS()).toEqual({
'9v5bqYwY2jfmvPNhTM': {
blocked_by: false,
blocking: false,
domain_blocking: false,
endorsed: false,
followed_by: true,
following: true,
id: '9v5bqYwY2jfmvPNhTM',
muting: false,
muting_notifications: false,
note: '',
notifying: false,
requested: false,
showing_reblogs: true,
subscribing: false,
},
});
});
});
});

View file

@ -1,85 +0,0 @@
import { Map as ImmutableMap } from 'immutable';
import get from 'lodash/get';
import { type Relationship, relationshipSchema } from 'pl-api';
import { ACCOUNT_NOTE_SUBMIT_SUCCESS } from '../actions/account-notes';
import {
ACCOUNT_BLOCK_SUCCESS,
ACCOUNT_UNBLOCK_SUCCESS,
ACCOUNT_MUTE_SUCCESS,
ACCOUNT_UNMUTE_SUCCESS,
ACCOUNT_PIN_SUCCESS,
ACCOUNT_UNPIN_SUCCESS,
ACCOUNT_REMOVE_FROM_FOLLOWERS_SUCCESS,
RELATIONSHIPS_FETCH_SUCCESS,
} from '../actions/accounts';
import { DOMAIN_BLOCK_SUCCESS, DOMAIN_UNBLOCK_SUCCESS } from '../actions/domain-blocks';
import { ACCOUNT_IMPORT, ACCOUNTS_IMPORT } from '../actions/importer';
import type { APIEntity } from 'pl-fe/types/entities';
import type { AnyAction } from 'redux';
type State = ImmutableMap<string, Relationship>;
type APIEntities = Array<APIEntity>;
const normalizeRelationships = (state: State, relationships: APIEntities) => {
relationships.forEach(relationship => {
try {
state = state.set(relationship.id, relationshipSchema.parse(relationship));
} catch (_e) {
// do nothing
}
});
return state;
};
const setDomainBlocking = (state: State, accounts: string[], blocking: boolean) =>
state.withMutations(map => {
accounts.forEach(id => {
map.setIn([id, 'domain_blocking'], blocking);
});
});
const importPleromaAccount = (state: State, account: APIEntity) => {
const relationship = get(account, ['pleroma', 'relationship'], {});
if (relationship.id)
return normalizeRelationships(state, [relationship]);
return state;
};
const importPleromaAccounts = (state: State, accounts: APIEntities) => {
accounts.forEach(account => {
state = importPleromaAccount(state, account);
});
return state;
};
const relationships = (state: State = ImmutableMap<string, Relationship>(), action: AnyAction) => {
switch (action.type) {
case ACCOUNT_IMPORT:
return importPleromaAccount(state, action.account);
case ACCOUNTS_IMPORT:
return importPleromaAccounts(state, action.accounts);
case ACCOUNT_BLOCK_SUCCESS:
case ACCOUNT_UNBLOCK_SUCCESS:
case ACCOUNT_MUTE_SUCCESS:
case ACCOUNT_UNMUTE_SUCCESS:
case ACCOUNT_PIN_SUCCESS:
case ACCOUNT_UNPIN_SUCCESS:
case ACCOUNT_NOTE_SUBMIT_SUCCESS:
case ACCOUNT_REMOVE_FROM_FOLLOWERS_SUCCESS:
return normalizeRelationships(state, [action.relationship]);
case RELATIONSHIPS_FETCH_SUCCESS:
return normalizeRelationships(state, action.relationships);
case DOMAIN_BLOCK_SUCCESS:
return setDomainBlocking(state, action.accounts, true);
case DOMAIN_UNBLOCK_SUCCESS:
return setDomainBlocking(state, action.accounts, false);
default:
return state;
}
};
export { relationships as default };

View file

@ -13,7 +13,7 @@ import { validId } from 'pl-fe/utils/auth';
import ConfigDB from 'pl-fe/utils/config-db';
import { shouldFilter } from 'pl-fe/utils/timelines';
import type { Account as BaseAccount, Filter, MediaAttachment } from 'pl-api';
import type { Account as BaseAccount, Filter, MediaAttachment, Relationship } from 'pl-api';
import type { EntityStore } from 'pl-fe/entity-store/types';
import type { Account, Group } from 'pl-fe/normalizers';
import type { MinifiedStatus } from 'pl-fe/reducers/statuses';
@ -33,7 +33,7 @@ const selectOwnAccount = (state: RootState) => {
};
const getAccountBase = (state: RootState, accountId: string) => state.entities[Entities.ACCOUNTS]?.store[accountId] as Account | undefined;
const getAccountRelationship = (state: RootState, accountId: string) => state.relationships.get(accountId);
const getAccountRelationship = (state: RootState, accountId: string) => state.entities[Entities.RELATIONSHIPS]?.store[accountId] as Relationship | undefined;
const getAccountMeta = (state: RootState, accountId: string) => state.accounts_meta[accountId];
const makeGetAccount = () => createSelector([

View file

@ -2171,15 +2171,15 @@
resolved "https://registry.yarnpkg.com/@reach/utils/-/utils-0.18.0.tgz#4f3cebe093dd436eeaff633809bf0f68f4f9d2ee"
integrity sha512-KdVMdpTgDyK8FzdKO9SCpiibuy/kbv3pwgfXshTI6tEcQT1OOwj7BAksnzGC0rPz0UholwC+AgkqEl3EJX3M1A==
"@reduxjs/toolkit@^2.2.7":
version "2.2.7"
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-2.2.7.tgz#199e3d10ccb39267cb5aee92c0262fd9da7fdfb2"
integrity sha512-faI3cZbSdFb8yv9dhDTmGwclW0vk0z5o1cia+kf7gCbaCwHI5e+7tP57mJUv22pNcNbeA62GSrPpfrUfdXcQ6g==
"@reduxjs/toolkit@^2.0.1":
version "2.0.1"
resolved "https://registry.yarnpkg.com/@reduxjs/toolkit/-/toolkit-2.0.1.tgz#0a5233c1e35c1941b03aece39cceade3467a1062"
integrity sha512-fxIjrR9934cmS8YXIGd9e7s1XRsEU++aFc9DVNMFMRTM5Vtsg2DCRMj21eslGtDt43IUf9bJL3h5bwUlZleibA==
dependencies:
immer "^10.0.3"
redux "^5.0.1"
redux "^5.0.0"
redux-thunk "^3.1.0"
reselect "^5.1.0"
reselect "^5.0.1"
"@remix-run/router@1.18.0":
version "1.18.0"
@ -9713,10 +9713,10 @@ react-property@2.0.2:
resolved "https://registry.yarnpkg.com/react-property/-/react-property-2.0.2.tgz#d5ac9e244cef564880a610bc8d868bd6f60fdda6"
integrity sha512-+PbtI3VuDV0l6CleQMsx2gtK0JZbZKbpdu5ynr+lbsuvtmgbNcS3VM0tuY2QjFNOcWxvXeHjDpy42RO+4U2rug==
react-redux@^9.1.2:
version "9.1.2"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-9.1.2.tgz#deba38c64c3403e9abd0c3fbeab69ffd9d8a7e4b"
integrity sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==
react-redux@^9.0.4:
version "9.0.4"
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-9.0.4.tgz#6892d465f086507a517d4b53eb589876e6bc8344"
integrity sha512-9J1xh8sWO0vYq2sCxK2My/QO7MzUMRi3rpiILP/+tDr8krBHixC6JMM17fMK88+Oh3e4Ae6/sHIhNBgkUivwFA==
dependencies:
"@types/use-sync-external-store" "^0.0.3"
use-sync-external-store "^1.0.0"
@ -9908,10 +9908,10 @@ redux@^4.0.5:
dependencies:
"@babel/runtime" "^7.9.2"
redux@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.1.tgz#97fa26881ce5746500125585d5642c77b6e9447b"
integrity sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==
redux@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/redux/-/redux-5.0.0.tgz#29572e29a439e094ff8fec46883fc45053f6736d"
integrity sha512-blLIYmYetpZMET6Q6uCY7Jtl/Im5OBldy+vNPauA8vvsdqyt66oep4EUpAMWNHauTC6xa9JuRPhRB72rY82QGA==
reflect.getprototypeof@^1.0.4:
version "1.0.4"
@ -10051,16 +10051,11 @@ requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
reselect@^5.0.0:
reselect@^5.0.0, reselect@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/reselect/-/reselect-5.0.1.tgz#587cdaaeb4e0e8927cff80ebe2bbef05f74b1648"
integrity sha512-D72j2ubjgHpvuCiORWkOUxndHJrxDaSolheiz5CO+roz8ka97/4msh2E8F5qay4GawR5vzBt5MkbDHT+Rdy/Wg==
reselect@^5.1.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/reselect/-/reselect-5.1.1.tgz#c766b1eb5d558291e5e550298adb0becc24bb72e"
integrity sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==
resize-observer-polyfill@^1.5.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"