Merge branch 'develop' into frontend-rw
Some checks are pending
pl-api CI / Test for a successful build (push) Waiting to run
pl-fe CI / Test and upload artifacts (push) Waiting to run
pl-fe CI / deploy (push) Blocked by required conditions
pl-hooks CI / Test for a successful build (push) Waiting to run

Signed-off-by: marcin mikołajczak <git@mkljczk.pl>
This commit is contained in:
marcin mikołajczak 2024-10-26 22:52:32 +02:00
commit 734e1db380
42 changed files with 279 additions and 369 deletions

View file

@ -1,13 +1,12 @@
import { staticFetch } from '../api';
import type { RootState } from 'pl-fe/store';
import type { AnyAction } from 'redux';
import type { AppDispatch, RootState } from 'pl-fe/store';
const FETCH_ABOUT_PAGE_REQUEST = 'FETCH_ABOUT_PAGE_REQUEST' as const;
const FETCH_ABOUT_PAGE_SUCCESS = 'FETCH_ABOUT_PAGE_SUCCESS' as const;
const FETCH_ABOUT_PAGE_FAIL = 'FETCH_ABOUT_PAGE_FAIL' as const;
const fetchAboutPage = (slug = 'index', locale?: string) => (dispatch: React.Dispatch<AnyAction>, getState: () => RootState) => {
const fetchAboutPage = (slug = 'index', locale?: string) => (dispatch: AppDispatch, getState: () => RootState) => {
dispatch({ type: FETCH_ABOUT_PAGE_REQUEST, slug, locale });
const filename = `${slug}${locale ? `.${locale}` : ''}.html`;

View file

@ -1,14 +1,12 @@
import { importEntities } from 'pl-fe/entity-store/actions';
import { Entities } from 'pl-fe/entity-store/entities';
import { importEntities } from 'pl-fe/actions/importer';
import { getClient } from '../api';
import type { RootState } from 'pl-fe/store';
import type { AnyAction } from 'redux';
import type { AppDispatch, RootState } from 'pl-fe/store';
const submitAccountNote = (accountId: string, value: string) =>
(dispatch: React.Dispatch<AnyAction>, getState: () => RootState) =>
(dispatch: AppDispatch, getState: () => RootState) =>
getClient(getState).accounts.updateAccountNote(accountId, value)
.then(response => dispatch(importEntities([response], Entities.RELATIONSHIPS)));
.then(response => dispatch(importEntities({ relationships: [response] })));
export { submitAccountNote };

View file

@ -1,13 +1,12 @@
import { PLEROMA, type UpdateNotificationSettingsParams, type Account, type CreateAccountParams, type PaginatedResponse, type Relationship } from 'pl-api';
import { importEntities } from 'pl-fe/entity-store/actions';
import { Entities } from 'pl-fe/entity-store/entities';
import { selectAccount } from 'pl-fe/selectors';
import { isLoggedIn } from 'pl-fe/utils/auth';
import { getClient, type PlfeResponse } from '../api';
import { importFetchedAccount, importFetchedAccounts } from './importer';
import { importEntities } from './importer';
import type { Map as ImmutableMap } from 'immutable';
import type { MinifiedStatus } from 'pl-fe/reducers/statuses';
@ -100,7 +99,7 @@ const fetchAccount = (accountId: string) =>
return getClient(getState()).accounts.getAccount(accountId)
.then(response => {
dispatch(importFetchedAccount(response));
dispatch(importEntities({ accounts: [response] }));
dispatch(fetchAccountSuccess(response));
})
.catch(error => {
@ -116,7 +115,7 @@ const fetchAccountByUsername = (username: string, history?: History) =>
if (features.accountByUsername && (me || !features.accountLookup)) {
return getClient(getState()).accounts.getAccount(username).then(response => {
dispatch(fetchRelationships([response.id]));
dispatch(importFetchedAccount(response));
dispatch(importEntities({ accounts: [response] }));
dispatch(fetchAccountSuccess(response));
}).catch(error => {
dispatch(fetchAccountFail(null, error));
@ -170,7 +169,7 @@ const blockAccount = (accountId: string) =>
return getClient(getState).filtering.blockAccount(accountId)
.then(response => {
dispatch(importEntities([response], Entities.RELATIONSHIPS));
dispatch(importEntities({ relationships: [response] }));
// Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
return dispatch(blockAccountSuccess(response, getState().statuses));
}).catch(error => dispatch(blockAccountFail(error)));
@ -182,7 +181,7 @@ const unblockAccount = (accountId: string) =>
return getClient(getState).filtering.unblockAccount(accountId)
.then(response => {
dispatch(importEntities([response], Entities.RELATIONSHIPS));
dispatch(importEntities({ relationships: [response] }));
});
};
@ -226,7 +225,7 @@ const muteAccount = (accountId: string, notifications?: boolean, duration = 0) =
return client.filtering.muteAccount(accountId, params)
.then(response => {
dispatch(importEntities([response], Entities.RELATIONSHIPS));
dispatch(importEntities({ relationships: [response] }));
// Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
return dispatch(muteAccountSuccess(response, getState().statuses));
})
@ -238,7 +237,7 @@ const unmuteAccount = (accountId: string) =>
if (!isLoggedIn(getState)) return null;
return getClient(getState()).filtering.unmuteAccount(accountId)
.then(response => dispatch(importEntities([response], Entities.RELATIONSHIPS)));
.then(response => dispatch(importEntities({ relationships: [response] })));
};
const muteAccountRequest = (accountId: string) => ({
@ -263,7 +262,7 @@ const removeFromFollowers = (accountId: string) =>
if (!isLoggedIn(getState)) return null;
return getClient(getState()).accounts.removeAccountFromFollowers(accountId)
.then(response => dispatch(importEntities([response], Entities.RELATIONSHIPS)));
.then(response => dispatch(importEntities({ relationships: [response] })));
};
const fetchRelationships = (accountIds: string[]) =>
@ -278,7 +277,7 @@ const fetchRelationships = (accountIds: string[]) =>
}
return getClient(getState()).accounts.getRelationships(newAccountIds)
.then(response => dispatch(importEntities(response, Entities.RELATIONSHIPS)));
.then(response => dispatch(importEntities({ relationships: response })));
};
const fetchFollowRequests = () =>
@ -289,7 +288,7 @@ const fetchFollowRequests = () =>
return getClient(getState()).myAccount.getFollowRequests()
.then(response => {
dispatch(importFetchedAccounts(response.items));
dispatch(importEntities({ accounts: response.items }));
dispatch(fetchFollowRequestsSuccess(response.items, response.next));
})
.catch(error => dispatch(fetchFollowRequestsFail(error)));
@ -321,7 +320,7 @@ const expandFollowRequests = () =>
dispatch(expandFollowRequestsRequest());
return next().then(response => {
dispatch(importFetchedAccounts(response.items));
dispatch(importEntities({ accounts: response.items }));
dispatch(expandFollowRequestsSuccess(response.items, response.next));
}).catch(error => dispatch(expandFollowRequestsFail(error)));
};
@ -400,7 +399,7 @@ const pinAccount = (accountId: string) =>
if (!isLoggedIn(getState)) return dispatch(noOp);
return getClient(getState).accounts.pinAccount(accountId).then(response =>
dispatch(importEntities([response], Entities.RELATIONSHIPS)),
dispatch(importEntities({ relationships: [response] })),
);
};
@ -409,7 +408,7 @@ const unpinAccount = (accountId: string) =>
if (!isLoggedIn(getState)) return dispatch(noOp);
return getClient(getState).accounts.unpinAccount(accountId).then(response =>
dispatch(importEntities([response], Entities.RELATIONSHIPS)),
dispatch(importEntities({ relationships: [response] })),
);
};
@ -429,7 +428,7 @@ const fetchPinnedAccounts = (accountId: string) =>
dispatch(fetchPinnedAccountsRequest(accountId));
return getClient(getState).accounts.getAccountEndorsements(accountId).then(response => {
dispatch(importFetchedAccounts(response));
dispatch(importEntities({ accounts: response }));
dispatch(fetchPinnedAccountsSuccess(accountId, response, null));
}).catch(error => {
dispatch(fetchPinnedAccountsFail(accountId, error));
@ -458,7 +457,7 @@ const accountSearch = (q: string, signal?: AbortSignal) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch({ type: ACCOUNT_SEARCH_REQUEST, params: { q } });
return getClient(getState()).accounts.searchAccounts(q, { resolve: false, limit: 4, following: true }, { signal }).then((accounts) => {
dispatch(importFetchedAccounts(accounts));
dispatch(importEntities({ accounts }));
dispatch({ type: ACCOUNT_SEARCH_SUCCESS, accounts });
return accounts;
}).catch(error => {
@ -471,7 +470,7 @@ const accountLookup = (acct: string, signal?: AbortSignal) =>
(dispatch: AppDispatch, getState: () => RootState) => {
dispatch({ type: ACCOUNT_LOOKUP_REQUEST, acct });
return getClient(getState()).accounts.lookupAccount(acct, { signal }).then((account) => {
if (account && account.id) dispatch(importFetchedAccount(account));
if (account && account.id) dispatch(importEntities({ accounts: [account] }));
dispatch({ type: ACCOUNT_LOOKUP_SUCCESS, account });
return account;
}).catch(error => {
@ -489,7 +488,7 @@ const fetchBirthdayReminders = (month: number, day: number) =>
dispatch({ type: BIRTHDAY_REMINDERS_FETCH_REQUEST, day, month, accountId: me });
return getClient(getState).accounts.getBirthdays(day, month).then(response => {
dispatch(importFetchedAccounts(response));
dispatch(importEntities({ accounts: response }));
dispatch({
type: BIRTHDAY_REMINDERS_FETCH_SUCCESS,
accounts: response,

View file

@ -1,12 +1,12 @@
import { fetchRelationships } from 'pl-fe/actions/accounts';
import { importFetchedAccount, importFetchedAccounts, importFetchedStatus, importFetchedStatuses } from 'pl-fe/actions/importer';
import { importEntities } from 'pl-fe/actions/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';
import type { Account, AdminGetAccountsParams, AdminGetReportsParams, PleromaConfig, Status } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
const ADMIN_CONFIG_FETCH_REQUEST = 'ADMIN_CONFIG_FETCH_REQUEST' as const;
@ -111,9 +111,7 @@ 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));
dispatch(importEntities({ statuses: report.statuses as Array<Status>, accounts: [report.account?.account, report.target_account?.account] }));
dispatch({ type: ADMIN_REPORTS_FETCH_SUCCESS, reports: items, params });
});
}).catch(error => {
@ -141,7 +139,7 @@ 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)));
dispatch(importEntities({ accounts: res.items.map(({ account }) => account).filter((account): account is Account => account !== null) }));
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 +201,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));
dispatch(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

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

View file

@ -30,7 +30,7 @@ import { isStandalone } from 'pl-fe/utils/state';
import { type PlfeResponse, getClient } from '../api';
import { importFetchedAccount } from './importer';
import { importEntities } from './importer';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -150,7 +150,7 @@ const verifyCredentials = (token: string, accountUrl?: string) =>
const client = new PlApiClient(baseURL, token);
return client.settings.verifyCredentials().then((account) => {
dispatch(importFetchedAccount(account));
dispatch(importEntities({ accounts: [account] }));
dispatch({ type: VERIFY_CREDENTIALS_SUCCESS, token, account });
if (account.id === getState().me) dispatch(fetchMeSuccess(account));
return account;
@ -159,7 +159,7 @@ const verifyCredentials = (token: string, accountUrl?: string) =>
// The user is waitlisted
const account = error.response.json;
const parsedAccount = v.parse(credentialAccountSchema, error.response.json);
dispatch(importFetchedAccount(parsedAccount));
dispatch(importEntities({ accounts: [parsedAccount] }));
dispatch({ type: VERIFY_CREDENTIALS_SUCCESS, token, account: parsedAccount });
if (account.id === getState().me) dispatch(fetchMeSuccess(parsedAccount));
return parsedAccount;
@ -175,7 +175,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));
dispatch(importEntities({ accounts: [account] }));
dispatch({ type: AUTH_ACCOUNT_REMEMBER_SUCCESS, account, accountUrl });
if (account.id === getState().me) dispatch(fetchMeSuccess(account));
return account;

View file

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

View file

@ -14,7 +14,7 @@ import toast from 'pl-fe/toast';
import { isLoggedIn } from 'pl-fe/utils/auth';
import { chooseEmoji } from './emojis';
import { importFetchedAccounts } from './importer';
import { importEntities } from './importer';
import { rememberLanguageUse } from './languages';
import { uploadFile, updateMedia } from './media';
import { createStatus } from './statuses';
@ -357,12 +357,13 @@ interface SubmitComposeOpts {
history?: History;
force?: boolean;
onSubmit?: () => void;
onSuccess?: () => void;
propagate?: boolean;
}
const submitCompose = (composeId: string, opts: SubmitComposeOpts = {}) =>
async (dispatch: AppDispatch, getState: () => RootState) => {
const { history, force = false, onSubmit, propagate } = opts;
const { history, force = false, onSubmit, onSuccess, propagate } = opts;
if (!isLoggedIn(getState)) return;
const state = getState();
@ -388,7 +389,7 @@ const submitCompose = (composeId: string, opts: SubmitComposeOpts = {}) =>
useModalsStore.getState().openModal('MISSING_DESCRIPTION', {
onContinue: () => {
useModalsStore.getState().closeModal('MISSING_DESCRIPTION');
dispatch(submitCompose(composeId, { history, force: true }));
dispatch(submitCompose(composeId, { history, force: true, onSuccess }));
},
});
return;
@ -460,7 +461,8 @@ const submitCompose = (composeId: string, opts: SubmitComposeOpts = {}) =>
history.push('/conversations');
}
handleComposeSubmit(dispatch, getState, composeId, data, status, !!statusId, propagate);
if (onSubmit) onSubmit();
onSubmit?.();
onSuccess?.();
}).catch((error) => {
dispatch(submitComposeFail(composeId, error));
});
@ -610,7 +612,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));
dispatch(importEntities({ accounts: response }));
dispatch(readyComposeSuggestionsAccounts(composeId, token, response));
}).catch(error => {
if (!signal.aborted) {

View file

@ -2,13 +2,9 @@ import { isLoggedIn } from 'pl-fe/utils/auth';
import { getClient } from '../api';
import {
importFetchedAccounts,
importFetchedStatuses,
importFetchedStatus,
} from './importer';
import { importEntities } from './importer';
import type { Account, Conversation, PaginatedResponse, Status } from 'pl-api';
import type { Account, Conversation, PaginatedResponse } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
const CONVERSATIONS_MOUNT = 'CONVERSATIONS_MOUNT' as const;
@ -48,8 +44,10 @@ 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)));
dispatch(importEntities({
accounts: response.items.reduce((aggr: Array<Account>, item) => aggr.concat(item.accounts), []),
statuses: response.items.map((item) => item.last_status),
}));
dispatch(expandConversationsSuccess(response.items, response.next, isLoadingRecent));
})
.catch(err => dispatch(expandConversationsFail(err)));
@ -74,11 +72,10 @@ const expandConversationsFail = (error: unknown) => ({
});
const updateConversations = (conversation: Conversation) => (dispatch: AppDispatch) => {
dispatch(importFetchedAccounts(conversation.accounts));
if (conversation.last_status) {
dispatch(importFetchedStatus(conversation.last_status));
}
dispatch(importEntities({
accounts: conversation.accounts,
statuses: [conversation.last_status],
}));
return dispatch({
type: CONVERSATIONS_UPDATE,

View file

@ -1,7 +1,7 @@
import { getClient } from '../api';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts } from './importer';
import { importEntities } from './importer';
import type { Account, ProfileDirectoryParams } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -19,7 +19,7 @@ const fetchDirectory = (params: ProfileDirectoryParams) =>
dispatch(fetchDirectoryRequest());
return getClient(getState()).instance.profileDirectory({ ...params, limit: 20 }).then((data) => {
dispatch(importFetchedAccounts(data));
dispatch(importEntities({ accounts: data }));
dispatch(fetchDirectorySuccess(data));
dispatch(fetchRelationships(data.map((x) => x.id)));
}).catch(error => dispatch(fetchDirectoryFail(error)));
@ -46,7 +46,7 @@ const expandDirectory = (params: Record<string, any>) =>
const loadedItems = getState().user_lists.directory.items.size;
return getClient(getState()).instance.profileDirectory({ ...params, offset: loadedItems, limit: 20 }).then((data) => {
dispatch(importFetchedAccounts(data));
dispatch(importEntities({ accounts: data }));
dispatch(expandDirectorySuccess(data));
dispatch(fetchRelationships(data.map((x) => x.id)));
}).catch(error => dispatch(expandDirectoryFail(error)));

View file

@ -2,7 +2,7 @@ import { isLoggedIn } from 'pl-fe/utils/auth';
import { getClient } from '../api';
import { importFetchedStatus } from './importer';
import { importEntities } from './importer';
import type { Status } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -24,7 +24,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));
dispatch(importEntities({ statuses: [response] }));
dispatch(emojiReactSuccess(response, emoji));
}).catch((error) => {
dispatch(emojiReactFail(status.id, emoji, error));
@ -38,7 +38,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));
dispatch(importEntities({ statuses: [response] }));
dispatch(unEmojiReactSuccess(response, emoji));
}).catch(error => {
dispatch(unEmojiReactFail(status.id, emoji, error));

View file

@ -4,7 +4,7 @@ import { getClient } from 'pl-fe/api';
import { useModalsStore } from 'pl-fe/stores/modals';
import toast from 'pl-fe/toast';
import { importFetchedAccounts, importFetchedStatus, importFetchedStatuses } from './importer';
import { importEntities } 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';
@ -132,7 +132,7 @@ const submitEvent = ({
: getClient(state).events.editEvent(statusId, params)
).then((data) => {
useModalsStore.getState().closeModal('COMPOSE_EVENT');
dispatch(importFetchedStatus(data));
dispatch(importEntities({ statuses: [data] }));
dispatch(submitEventSuccess(data));
toast.success(
statusId ? messages.editSuccess : messages.success,
@ -171,7 +171,7 @@ const joinEvent = (statusId: string, participationMessage?: string) =>
dispatch(joinEventRequest(status.id));
return getClient(getState).events.joinEvent(statusId, participationMessage).then((data) => {
dispatch(importFetchedStatus(data));
dispatch(importEntities({ statuses: [data] }));
dispatch(joinEventSuccess(status.id));
toast.success(
data.event?.join_state === 'pending' ? messages.joinRequestSuccess : messages.joinSuccess,
@ -213,7 +213,7 @@ const leaveEvent = (statusId: string) =>
dispatch(leaveEventRequest(status.id));
return getClient(getState).events.leaveEvent(statusId).then((data) => {
dispatch(importFetchedStatus(data));
dispatch(importEntities({ statuses: [data] }));
dispatch(leaveEventSuccess(status.id));
}).catch((error) => {
dispatch(leaveEventFail(error, status.id));
@ -241,7 +241,7 @@ const fetchEventParticipations = (statusId: string) =>
dispatch(fetchEventParticipationsRequest(statusId));
return getClient(getState).events.getEventParticipations(statusId).then(response => {
dispatch(importFetchedAccounts(response.items));
dispatch(importEntities({ accounts: response.items }));
return dispatch(fetchEventParticipationsSuccess(statusId, response.items, response.next));
}).catch(error => {
dispatch(fetchEventParticipationsFail(statusId, error));
@ -277,7 +277,7 @@ const expandEventParticipations = (statusId: string) =>
dispatch(expandEventParticipationsRequest(statusId));
return next().then(response => {
dispatch(importFetchedAccounts(response.items));
dispatch(importEntities({ accounts: response.items }));
return dispatch(expandEventParticipationsSuccess(statusId, response.items, response.next));
}).catch(error => {
dispatch(expandEventParticipationsFail(statusId, error));
@ -307,7 +307,7 @@ const fetchEventParticipationRequests = (statusId: string) =>
dispatch(fetchEventParticipationRequestsRequest(statusId));
return getClient(getState).events.getEventParticipationRequests(statusId).then(response => {
dispatch(importFetchedAccounts(response.items.map(({ account }) => account)));
dispatch(importEntities({ accounts: response.items.map(({ account }) => account) }));
return dispatch(fetchEventParticipationRequestsSuccess(statusId, response.items, response.next));
}).catch(error => {
dispatch(fetchEventParticipationRequestsFail(statusId, error));
@ -346,7 +346,7 @@ const expandEventParticipationRequests = (statusId: string) =>
dispatch(expandEventParticipationRequestsRequest(statusId));
return next().then(response => {
dispatch(importFetchedAccounts(response.items.map(({ account }) => account)));
dispatch(importEntities({ accounts: response.items.map(({ account }) => account) }));
return dispatch(expandEventParticipationRequestsSuccess(statusId, response.items, response.next));
}).catch(error => {
dispatch(expandEventParticipationRequestsFail(statusId, error));
@ -479,7 +479,7 @@ const fetchRecentEvents = () =>
return getClient(getState()).timelines.publicTimeline({
only_events: true,
}).then(response => {
dispatch(importFetchedStatuses(response.items));
dispatch(importEntities({ statuses: response.items }));
dispatch({
type: RECENT_EVENTS_FETCH_SUCCESS,
statuses: response.items,
@ -499,7 +499,7 @@ const fetchJoinedEvents = () =>
dispatch({ type: JOINED_EVENTS_FETCH_REQUEST });
getClient(getState).events.getJoinedEvents().then(response => {
dispatch(importFetchedStatuses(response.items));
dispatch(importEntities({ statuses: response.items }));
dispatch({
type: JOINED_EVENTS_FETCH_SUCCESS,
statuses: response.items,

View file

@ -3,7 +3,7 @@ import { AppDispatch, RootState } from 'pl-fe/store';
import { getClient } from '../api';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts } from './importer';
import { importEntities } from './importer';
const FAMILIAR_FOLLOWERS_FETCH_REQUEST = 'FAMILIAR_FOLLOWERS_FETCH_REQUEST' as const;
const FAMILIAR_FOLLOWERS_FETCH_SUCCESS = 'FAMILIAR_FOLLOWERS_FETCH_SUCCESS' as const;
@ -19,7 +19,7 @@ const fetchAccountFamiliarFollowers = (accountId: string) => (dispatch: AppDispa
.then((data) => {
const accounts = data.find(({ id }: { id: string }) => id === accountId)!.accounts;
dispatch(importFetchedAccounts(accounts));
dispatch(importEntities({ accounts }));
dispatch(fetchRelationships(accounts.map((item) => item.id)));
dispatch({
type: FAMILIAR_FOLLOWERS_FETCH_SUCCESS,

View file

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

View file

@ -1,6 +1,6 @@
import { getClient } from '../api';
import { importFetchedAccounts } from './importer';
import { importEntities } from './importer';
import type { Account, PaginatedResponse } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -23,7 +23,7 @@ const fetchGroupBlocks = (groupId: string) =>
dispatch(fetchGroupBlocksRequest(groupId));
return getClient(getState).experimental.groups.getGroupBlocks(groupId).then(response => {
dispatch(importFetchedAccounts(response.items));
dispatch(importEntities({ accounts: response.items }));
dispatch(fetchGroupBlocksSuccess(groupId, response.items, response.next));
}).catch(error => {
dispatch(fetchGroupBlocksFail(groupId, error));

View file

@ -1,6 +1,6 @@
import { getClient } from 'pl-fe/api';
import { importFetchedAccounts } from './importer';
import { importEntities } from './importer';
import type { StatusEdit } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -18,7 +18,7 @@ const fetchHistory = (statusId: string) =>
dispatch(fetchHistoryRequest(statusId));
return getClient(getState()).statuses.getStatusHistory(statusId).then(data => {
dispatch(importFetchedAccounts(data.map((x) => x.account)));
dispatch(importEntities({ accounts: data.map((x) => x.account) }));
dispatch(fetchHistorySuccess(statusId, data));
}).catch(error => dispatch(fetchHistoryFail(statusId, error)));
};

View file

@ -0,0 +1,121 @@
import { importEntities as importEntityStoreEntities } from 'pl-fe/entity-store/actions';
import { Entities } from 'pl-fe/entity-store/entities';
import { normalizeGroup } from 'pl-fe/normalizers/group';
import type { Account as BaseAccount, Group as BaseGroup, Poll as BasePoll, Relationship as BaseRelationship, Status as BaseStatus } from 'pl-api';
import type { AppDispatch } from 'pl-fe/store';
const STATUS_IMPORT = 'STATUS_IMPORT' as const;
const STATUSES_IMPORT = 'STATUSES_IMPORT' as const;
const POLLS_IMPORT = 'POLLS_IMPORT' as const;
// Sometimes Pleroma can return an empty account,
// or a repost can appear of a deleted account. Skip these statuses.
const isBroken = (status: BaseStatus) => {
try {
if (status.scheduled_at !== null) return true;
// Skip empty accounts
// https://gitlab.com/soapbox-pub/soapbox/-/issues/424
if (!status.account.id) return true;
// Skip broken reposts
// https://gitlab.com/soapbox-pub/rebased/-/issues/28
if (status.reblog && !status.reblog.account.id) return true;
return false;
} catch (e) {
return true;
}
};
const isEmpty = (object: Record<string, any>) => !Object.values(object).some(value => value);
interface ImportStatusAction {
type: typeof STATUS_IMPORT;
status: BaseStatus;
idempotencyKey?: string;
}
interface ImportStatusesAction {
type: typeof STATUSES_IMPORT;
statuses: Array<BaseStatus>;
}
interface ImportPollAction {
type: typeof POLLS_IMPORT;
polls: Array<BasePoll>;
}
const importEntities = (entities: {
accounts?: Array<BaseAccount | undefined | null>;
groups?: Array<BaseGroup | undefined | null>;
polls?: Array<BasePoll | undefined | null>;
statuses?: Array<BaseStatus & { expectsCard?: boolean } | undefined | null>;
relationships?: Array<BaseRelationship | undefined | null>;
}, options: {
withParents?: boolean;
idempotencyKey?: string;
} = {
withParents: true,
}) => (dispatch: AppDispatch) => {
const accounts: Record<string, BaseAccount> = {};
const groups: Record<string, BaseGroup> = {};
const polls: Record<string, BasePoll> = {};
const relationships: Record<string, BaseRelationship> = {};
const statuses: Record<string, BaseStatus> = {};
const processAccount = (account: BaseAccount, withSelf = true) => {
if (withSelf) accounts[account.id] = account;
if (account.moved) processAccount(account.moved);
if (account.relationship) relationships[account.relationship.id] = account.relationship;
};
const processStatus = (status: BaseStatus, withSelf = true) => {
// Skip broken statuses
if (isBroken(status)) return;
if (withSelf) 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;
if (status.group) groups[status.group.id] = status.group;
};
if (options.withParents) {
entities.groups?.forEach(group => group && (groups[group.id] = group));
entities.polls?.forEach(poll => poll && (polls[poll.id] = poll));
entities.relationships?.forEach(relationship => relationship && (relationships[relationship.id] = relationship));
}
entities.accounts?.forEach((account) => account && processAccount(account, options.withParents));
if (entities.statuses?.length === 1 && entities.statuses[0] && options.idempotencyKey) {
dispatch<ImportStatusAction>({
type: STATUS_IMPORT,
status: entities.statuses[0], idempotencyKey: options.idempotencyKey,
});
processStatus(entities.statuses[0], false);
} else {
entities.statuses?.forEach((status) => status && processStatus(status, options.withParents));
}
if (!isEmpty(accounts)) dispatch(importEntityStoreEntities(Object.values(accounts), Entities.ACCOUNTS));
if (!isEmpty(groups)) dispatch(importEntityStoreEntities(Object.values(groups).map(normalizeGroup), Entities.GROUPS));
if (!isEmpty(polls)) dispatch<ImportPollAction>(({ type: POLLS_IMPORT, polls: Object.values(polls) }));
if (!isEmpty(relationships)) dispatch(importEntityStoreEntities(Object.values(relationships), Entities.RELATIONSHIPS));
if (!isEmpty(statuses)) dispatch<ImportStatusesAction>({ type: STATUSES_IMPORT, statuses: Object.values(statuses) });
};
type ImporterAction = ImportStatusAction | ImportStatusesAction | ImportPollAction;
export {
STATUS_IMPORT,
STATUSES_IMPORT,
POLLS_IMPORT,
importEntities,
type ImporterAction,
};

View file

@ -1,169 +0,0 @@
import { importEntities } from 'pl-fe/entity-store/actions';
import { Entities } from 'pl-fe/entity-store/entities';
import { normalizeAccount } from 'pl-fe/normalizers/account';
import { normalizeGroup } from 'pl-fe/normalizers/group';
import type { Account as BaseAccount, Group, Poll, Status as BaseStatus } from 'pl-api';
import type { AppDispatch } from 'pl-fe/store';
const STATUS_IMPORT = 'STATUS_IMPORT' as const;
const STATUSES_IMPORT = 'STATUSES_IMPORT' as const;
const POLLS_IMPORT = 'POLLS_IMPORT' as const;
const importAccounts = (data: Array<BaseAccount>) => (dispatch: AppDispatch) => {
try {
const accounts = data.map(normalizeAccount);
const relationships = accounts.map(account => account.relationship).filter(relationship => !!relationship);
dispatch(importEntities(accounts, Entities.ACCOUNTS));
dispatch(importEntities(relationships, Entities.RELATIONSHIPS));
} catch (e) {
//
}
};
const importGroup = (data: Group) => importGroups([data]);
const importGroups = (data: Array<Group>) => (dispatch: AppDispatch) => {
try {
const groups = data.map(normalizeGroup);
dispatch(importEntities(groups, Entities.GROUPS));
} catch (e) {
//
}
};
const importStatus = (status: BaseStatus & { expectsCard?: boolean }, idempotencyKey?: string) => ({ type: STATUS_IMPORT, status, idempotencyKey });
const importStatuses = (statuses: Array<BaseStatus>) => ({ type: STATUSES_IMPORT, statuses });
const importPolls = (polls: Array<Poll>) => ({ type: POLLS_IMPORT, polls });
const importFetchedAccount = (account: BaseAccount) =>
importFetchedAccounts([account]);
const importFetchedAccounts = (accounts: Array<BaseAccount>) => {
const normalAccounts: Array<BaseAccount> = [];
const processAccount = (account: BaseAccount) => {
if (!account.id) return;
normalAccounts.push(account);
if (account.moved) {
processAccount(account.moved);
}
};
accounts.forEach(processAccount);
return importAccounts(normalAccounts);
};
const importFetchedStatus = (status: BaseStatus & { expectsCard?: boolean }, idempotencyKey?: string) =>
(dispatch: AppDispatch) => {
// Skip broken statuses
if (isBroken(status)) return;
if (status.reblog?.id) {
dispatch(importFetchedStatus(status.reblog as BaseStatus));
}
// Fedibird quotes
if (status.quote?.id) {
dispatch(importFetchedStatus(status.quote as BaseStatus));
}
// Fedibird quote from reblog
if (status.reblog?.quote?.id) {
dispatch(importFetchedStatus(status.reblog.quote));
}
if (status.poll?.id) {
dispatch(importFetchedPoll(status.poll));
}
if (status.group?.id) {
dispatch(importGroup(status.group));
}
dispatch(importFetchedAccount(status.account));
dispatch(importStatus(status, idempotencyKey));
};
// Sometimes Pleroma can return an empty account,
// or a repost can appear of a deleted account. Skip these statuses.
const isBroken = (status: BaseStatus) => {
try {
if (status.scheduled_at !== null) return true;
// Skip empty accounts
// https://gitlab.com/soapbox-pub/soapbox/-/issues/424
if (!status.account.id) return true;
// Skip broken reposts
// https://gitlab.com/soapbox-pub/rebased/-/issues/28
if (status.reblog && !status.reblog.account.id) return true;
return false;
} catch (e) {
return true;
}
};
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]));
};
type ImporterAction =
| ReturnType<typeof importStatus>
| ReturnType<typeof importStatuses>
| ReturnType<typeof importPolls>;
export {
STATUS_IMPORT,
STATUSES_IMPORT,
POLLS_IMPORT,
importFetchedAccount,
importFetchedAccounts,
importFetchedStatus,
importFetchedStatuses,
importFetchedPoll,
type ImporterAction,
};

View file

@ -7,7 +7,7 @@ import { isLoggedIn } from 'pl-fe/utils/auth';
import { getClient } from '../api';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts, importFetchedStatus } from './importer';
import { importEntities } from './importer';
import type { Account, EmojiReaction, PaginatedResponse, Status } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -97,7 +97,7 @@ const reblog = (status: Pick<Status, 'id'>) =>
return getClient(getState()).statuses.reblogStatus(status.id).then((response) => {
// The reblog API method returns a new status wrapped around the original. In this case we are only
// interested in how the original is modified, hence passing it skipping the wrapper
if (response.reblog) dispatch(importFetchedStatus(response.reblog as Status));
if (response.reblog) dispatch(importEntities({ statuses: [response.reblog] }));
dispatch(reblogSuccess(response));
}).catch(error => {
dispatch(reblogFail(status.id, error));
@ -307,7 +307,7 @@ const bookmark = (status: Pick<Status, 'id'>, folderId?: string) =>
dispatch(bookmarkRequest(status.id));
return getClient(getState()).statuses.bookmarkStatus(status.id, folderId).then((response) => {
dispatch(importFetchedStatus(response));
dispatch(importEntities({ statuses: [response] }));
dispatch(bookmarkSuccess(response));
let opts: IToastOptions = {
@ -335,7 +335,7 @@ const unbookmark = (status: Pick<Status, 'id'>) =>
dispatch(unbookmarkRequest(status.id));
return getClient(getState()).statuses.unbookmarkStatus(status.id).then(response => {
dispatch(importFetchedStatus(response));
dispatch(importEntities({ statuses: [response] }));
dispatch(unbookmarkSuccess(response));
toast.success(messages.bookmarkRemoved);
}).catch(error => {
@ -391,7 +391,7 @@ const fetchReblogs = (statusId: string) =>
dispatch(fetchReblogsRequest(statusId));
return getClient(getState()).statuses.getRebloggedBy(statusId).then(response => {
dispatch(importFetchedAccounts(response.items));
dispatch(importEntities({ accounts: response.items }));
dispatch(fetchRelationships(response.items.map((item) => item.id)));
dispatch(fetchReblogsSuccess(statusId, response.items, response.next));
}).catch(error => {
@ -420,7 +420,7 @@ const fetchReblogsFail = (statusId: string, error: unknown) => ({
const expandReblogs = (statusId: string, next: AccountListLink) =>
(dispatch: AppDispatch, getState: () => RootState) => {
next().then(response => {
dispatch(importFetchedAccounts(response.items));
dispatch(importEntities({ accounts: response.items }));
dispatch(fetchRelationships(response.items.map((item) => item.id)));
dispatch(expandReblogsSuccess(statusId, response.items, response.next));
}).catch(error => {
@ -446,7 +446,7 @@ const fetchFavourites = (statusId: string) =>
dispatch(fetchFavouritesRequest(statusId));
return getClient(getState()).statuses.getFavouritedBy(statusId).then(response => {
dispatch(importFetchedAccounts(response.items));
dispatch(importEntities({ accounts: response.items }));
dispatch(fetchRelationships(response.items.map((item) => item.id)));
dispatch(fetchFavouritesSuccess(statusId, response.items, response.next));
}).catch(error => {
@ -475,7 +475,7 @@ const fetchFavouritesFail = (statusId: string, error: unknown) => ({
const expandFavourites = (statusId: string, next: AccountListLink) =>
(dispatch: AppDispatch) => {
next().then(response => {
dispatch(importFetchedAccounts(response.items));
dispatch(importEntities({ accounts: response.items }));
dispatch(fetchRelationships(response.items.map((item) => item.id)));
dispatch(expandFavouritesSuccess(statusId, response.items, response.next));
}).catch(error => {
@ -501,7 +501,7 @@ const fetchDislikes = (statusId: string) =>
dispatch(fetchDislikesRequest(statusId));
return getClient(getState).statuses.getDislikedBy(statusId).then(response => {
dispatch(importFetchedAccounts(response));
dispatch(importEntities({ accounts: response }));
dispatch(fetchRelationships(response.map((item) => item.id)));
dispatch(fetchDislikesSuccess(statusId, response));
}).catch(error => {
@ -531,7 +531,7 @@ const fetchReactions = (statusId: string) =>
dispatch(fetchReactionsRequest(statusId));
return getClient(getState).statuses.getStatusReactions(statusId).then(response => {
dispatch(importFetchedAccounts((response).map(({ accounts }) => accounts).flat()));
dispatch(importEntities({ accounts: (response).map(({ accounts }) => accounts).flat() }));
dispatch(fetchReactionsSuccess(statusId, response));
}).catch(error => {
dispatch(fetchReactionsFail(statusId, error));
@ -562,7 +562,7 @@ const pin = (status: Pick<Status, 'id'>, accountId: string) =>
dispatch(pinRequest(status.id, accountId));
return getClient(getState()).statuses.pinStatus(status.id).then(response => {
dispatch(importFetchedStatus(response));
dispatch(importEntities({ statuses: [response] }));
dispatch(pinSuccess(response, accountId));
}).catch(error => {
dispatch(pinFail(status.id, error, accountId));
@ -596,7 +596,7 @@ const unpin = (status: Pick<Status, 'id'>, accountId: string) =>
dispatch(unpinRequest(status.id, accountId));
return getClient(getState()).statuses.unpinStatus(status.id).then(response => {
dispatch(importFetchedStatus(response));
dispatch(importEntities({ statuses: [response] }));
dispatch(unpinSuccess(response, accountId));
}).catch(error => {
dispatch(unpinFail(status.id, error, accountId));

View file

@ -4,7 +4,7 @@ import { isLoggedIn } from 'pl-fe/utils/auth';
import { getClient } from '../api';
import { importFetchedAccounts } from './importer';
import { importEntities } from './importer';
import type { Account, List, PaginatedResponse } from 'pl-api';
import type { AppDispatch, RootState } from 'pl-fe/store';
@ -229,7 +229,7 @@ const fetchListAccounts = (listId: string) => (dispatch: AppDispatch, getState:
dispatch(fetchListAccountsRequest(listId));
return getClient(getState()).lists.getListAccounts(listId).then(({ items, next }) => {
dispatch(importFetchedAccounts(items));
dispatch(importEntities({ accounts: items }));
dispatch(fetchListAccountsSuccess(listId, items, next));
}).catch(err => dispatch(fetchListAccountsFail(listId, err)));
};
@ -256,7 +256,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));
dispatch(importEntities({ accounts: data }));
dispatch(fetchListSuggestionsReady(q, data));
}).catch(error => toast.showAlertForError(error));
};

View file

@ -7,7 +7,7 @@ import { getAuthUserId, getAuthUserUrl } from 'pl-fe/utils/auth';
import { getClient } from '../api';
import { loadCredentials } from './auth';
import { importFetchedAccount } from './importer';
import { importEntities } from './importer';
import { FE_NAME } from './settings';
import type { CredentialAccount, UpdateCredentialsParams } from 'pl-api';
@ -127,7 +127,7 @@ const patchMeSuccess = (me: CredentialAccount) =>
me,
};
dispatch(importFetchedAccount(me));
dispatch(importEntities({ accounts: [me] }));
dispatch(action);
};

View file

@ -14,12 +14,7 @@ import { EXCLUDE_TYPES, NOTIFICATION_TYPES } from 'pl-fe/utils/notification';
import { joinPublicPath } from 'pl-fe/utils/static';
import { fetchRelationships } from './accounts';
import {
importFetchedAccount,
importFetchedAccounts,
importFetchedStatus,
importFetchedStatuses,
} from './importer';
import { importEntities } from './importer';
import { saveMarker } from './markers';
import { saveSettings } from './settings';
@ -76,20 +71,10 @@ const updateNotifications = (notification: BaseNotification) =>
const selectedFilter = useSettingsStore.getState().settings.notifications.quickFilter.active;
const showInColumn = selectedFilter === 'all' ? true : (FILTER_TYPES[selectedFilter as FilterType] || [notification.type]).includes(notification.type);
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));
}
dispatch(importEntities({
accounts: [notification.account, notification.type === 'move' ? notification.target : undefined],
statuses: [getNotificationStatus(notification)],
}));
if (showInColumn) {
dispatch({
@ -256,8 +241,10 @@ const expandNotifications = ({ maxId }: Record<string, any> = {}, done: () => an
return acc;
}, { accounts: {}, statuses: {} } as { accounts: Record<string, Account>; statuses: Record<string, Status> });
dispatch(importFetchedAccounts(Object.values(entries.accounts)));
dispatch(importFetchedStatuses(Object.values(entries.statuses)));
dispatch(importEntities({
accounts: Object.values(entries.accounts),
statuses: Object.values(entries.statuses),
}));
const deduplicatedNotifications = normalizeNotifications(response.items, state.notifications.items);

View file

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@ import { useSettingsStore } from 'pl-fe/stores/settings';
import { getClient } from '../api';
import { fetchRelationships } from './accounts';
import { importFetchedAccounts, importFetchedStatuses } from './importer';
import { importEntities } from './importer';
import type { Search } from 'pl-api';
import type { SearchFilter } from 'pl-fe/reducers/search';
@ -54,13 +54,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));
}
dispatch(importEntities({ accounts: response.accounts, statuses: response.statuses }));
dispatch(fetchSearchSuccess(response, value, type));
dispatch(fetchRelationships(response.accounts.map((item) => item.id)));
@ -113,13 +107,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));
}
dispatch(importEntities({ accounts: response.accounts, statuses: response.statuses }));
dispatch(expandSearchSuccess(response, value, type));
dispatch(fetchRelationships(response.accounts.map((item) => item.id)));

View file

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

View file

@ -6,7 +6,7 @@ import { shouldHaveCard } from 'pl-fe/utils/status';
import { getClient } from '../api';
import { setComposeToStatus } from './compose';
import { importFetchedStatus, importFetchedStatuses } from './importer';
import { importEntities } from './importer';
import { deleteFromTimelines } from './timelines';
import type { CreateStatusParams, Status as BaseStatus } from 'pl-api';
@ -66,7 +66,7 @@ const createStatus = (params: CreateStatusParams, idempotencyKey: string, status
// The backend might still be processing the rich media attachment
const expectsCard = status.scheduled_at === null && !status.card && shouldHaveCard(status);
if (status.scheduled_at === null) dispatch(importFetchedStatus({ ...status, expectsCard }, idempotencyKey));
if (status.scheduled_at === null) dispatch(importEntities({ statuses: [{ ...status, expectsCard }] }, { idempotencyKey, withParents: true }));
dispatch({ type: STATUS_CREATE_SUCCESS, status, params, idempotencyKey, editing: !!statusId });
// Poll the backend for the updated card
@ -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));
dispatch(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));
dispatch(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));
dispatch(importEntities({ statuses: [status] }));
const fetchContext = (statusId: string, intl?: IntlShape) =>
(dispatch: AppDispatch, getState: () => RootState) => {
@ -164,14 +164,10 @@ const fetchContext = (statusId: string, intl?: IntlShape) =>
} : undefined;
return getClient(getState()).statuses.getContext(statusId, params).then(context => {
if (typeof context === 'object') {
const { ancestors, descendants } = context;
const statuses = ancestors.concat(descendants);
dispatch(importFetchedStatuses(statuses));
dispatch({ type: CONTEXT_FETCH_SUCCESS, statusId, ancestors, descendants });
} else {
throw context;
}
const { ancestors, descendants } = context;
const statuses = ancestors.concat(descendants);
dispatch(importEntities({ statuses }));
dispatch({ type: CONTEXT_FETCH_SUCCESS, statusId, ancestors, descendants });
return context;
}).catch(error => {
if (error.response?.status === 404) {

View file

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

View file

@ -6,7 +6,7 @@ import { shouldFilter } from 'pl-fe/utils/timelines';
import { getClient } from '../api';
import { importFetchedStatus, importFetchedStatuses } from './importer';
import { importEntities } 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';
@ -46,7 +46,7 @@ const processTimelineUpdate = (timeline: string, status: BaseStatus) =>
return;
}
dispatch(importFetchedStatus(status));
dispatch(importEntities({ statuses: [status] }));
if (shouldSkipQueue) {
dispatch(updateTimeline(timeline, status.id));
@ -159,10 +159,10 @@ const handleTimelineExpand = (timelineId: string, fn: Promise<PaginatedResponse<
dispatch(expandTimelineRequest(timelineId));
return fn.then(response => {
dispatch(importFetchedStatuses(response.items));
dispatch(importEntities({ statuses: response.items }));
const statuses = deduplicateStatuses(response.items);
dispatch(importFetchedStatuses(statuses.filter(status => status.accounts)));
dispatch(importEntities({ statuses: statuses.filter(status => status.accounts) }));
dispatch(expandTimelineSuccess(
timelineId,

View file

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

View file

@ -230,7 +230,7 @@ const Status: React.FC<IStatus> = (props) => {
<Link key={account.acct} to={`/@${account.acct}`} className='hover:underline'>
<bdi className='truncate'>
<strong className='text-gray-800 dark:text-gray-200'>
<Emojify text={status.account.display_name} emojis={status.account.emojis} />
<Emojify text={account.display_name} emojis={account.emojis} />
</strong>
</bdi>
</Link>

View file

@ -121,7 +121,7 @@ const Modal = React.forwardRef<HTMLDivElement, IModal>(({
/>
)}
<h3 className='grow truncate text-lg font-bold leading-6 text-gray-900 dark:text-white'>
<h3 className='grow text-lg font-bold leading-6 text-gray-900 dark:text-white'>
{title}
</h3>

View file

@ -154,10 +154,9 @@ const ComposeForm = <ID extends string>({ id, shouldCondense, autoFocus, clickab
if (!canSubmit) return;
e?.preventDefault();
dispatch(submitCompose(id, { history, onSubmit, propagate: fullScreen })).then(() => {
dispatch(submitCompose(id, { history, onSubmit, propagate: fullScreen, onSuccess: () => {
editorRef.current?.dispatchCommand(CLEAR_EDITOR_COMMAND, undefined);
}).catch(() => {});
} }));
};
const onSuggestionsClearRequested = () => {

View file

@ -8,9 +8,9 @@ import { fetchDraftStatuses } from 'pl-fe/actions/draft-statuses';
import { fetchStatus } from 'pl-fe/actions/statuses';
import Stack from 'pl-fe/components/ui/stack';
import ComposeForm from 'pl-fe/features/compose/components/compose-form';
import { useAppDispatch } from 'pl-fe/hooks/useAppDispatch';
import { useCompose } from 'pl-fe/hooks/useCompose';
import { useFeatures } from 'pl-fe/hooks/useFeatures';
import { useAppDispatch } from 'pl-fe/hooks/use-app-dispatch';
import { useCompose } from 'pl-fe/hooks/use-compose';
import { useFeatures } from 'pl-fe/hooks/use-features';
import { makeGetStatus, selectOwnAccount } from 'pl-fe/selectors';
import { useSettingsStore } from 'pl-fe/stores/settings';

View file

@ -9,6 +9,7 @@ import {
} from 'pl-fe/actions/import-data';
import Column from 'pl-fe/components/ui/column';
import { useFeatures } from 'pl-fe/hooks/use-features';
import { useInstance } from 'pl-fe/hooks/use-instance';
import DataImporter from './components/data-importer';

View file

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

View file

@ -25,7 +25,7 @@ const MissingDescriptionModal: React.FC<BaseModalProps & MissingDescriptionModal
confirmationText={intl.formatMessage(messages.post)}
confirmationTheme='danger'
cancelText={intl.formatMessage(messages.cancel)}
cancelAction={onClose}
cancelAction={() => onClose('MISSING_DESCRIPTION')}
>
<p className='text-gray-600 dark:text-gray-300'>
<FormattedMessage id='missing_description_modal.description' defaultMessage='Continue anyway?' />

View file

@ -1,7 +1,6 @@
import { QueryClientProvider } from '@tanstack/react-query';
import React from 'react';
import { HelmetProvider } from 'react-helmet-async';
import { Toaster } from 'react-hot-toast';
import { Provider } from 'react-redux';
import { StatProvider } from 'pl-fe/contexts/stat-context';
@ -40,12 +39,6 @@ const PlFe: React.FC = () => (
</StatProvider>
</QueryClientProvider>
</Provider>
<div id='toaster'>
<Toaster
position='top-right'
containerClassName='top-4'
/>
</div>
</>
);

View file

@ -3,7 +3,7 @@ import sumBy from 'lodash/sumBy';
import { type Chat, type ChatMessage as BaseChatMessage, type PaginatedResponse, chatMessageSchema, type Relationship } from 'pl-api';
import * as v from 'valibot';
import { importFetchedAccount, importFetchedAccounts } from 'pl-fe/actions/importer';
import { importEntities } from 'pl-fe/actions/importer';
import { ChatWidgetScreens, useChatContext } from 'pl-fe/contexts/chat-context';
import { useStatContext } from 'pl-fe/contexts/stat-context';
import { Entities } from 'pl-fe/entity-store/entities';
@ -72,7 +72,7 @@ const useChats = () => {
// Set the relationships to these users in the redux store.
fetchRelationships.mutate({ accountIds: items.map((item) => item.account.id) });
dispatch(importFetchedAccounts(items.map((item) => item.account)));
dispatch(importEntities({ accounts: items.map((item) => item.account) }));
return response;
};
@ -109,7 +109,7 @@ const useChat = (chatId?: string) => {
const data = await client.chats.getChat(chatId);
fetchRelationships.mutate({ accountIds: [data.account.id] });
dispatch(importFetchedAccount(data.account));
dispatch(importEntities({ accounts: [data.account] }));
return data;
}

View file

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

View file

@ -5,17 +5,18 @@ import * as v from 'valibot';
import { usePlHooksApiClient } from 'pl-hooks/contexts/api-client';
import { usePlHooksQueryClient } from 'pl-hooks/contexts/query-client';
const placeholderData = v.parse(instanceSchema, {});
const initialData = v.parse(instanceSchema, {});
const useInstance = () => {
const { client } = usePlHooksApiClient();
const queryClient = usePlHooksQueryClient();
return useQuery({
const query = useQuery({
queryKey: ['instance'],
queryFn: client.instance.getInstance,
placeholderData,
}, queryClient);
return { ...query, data: query.data || initialData };
};
export { useInstance };