diff --git a/package.json b/package.json index a1e6dea2f..675a3b99d 100644 --- a/package.json +++ b/package.json @@ -182,7 +182,7 @@ "vite-plugin-require": "^1.1.10", "vite-plugin-static-copy": "^1.0.0", "wicg-inert": "^3.1.1", - "zod": "^3.22.4" + "zod": "^3.23.8" }, "devDependencies": { "@formatjs/cli": "^6.2.0", diff --git a/src/actions/about.ts b/src/actions/about.ts index 8a129594e..376211e43 100644 --- a/src/actions/about.ts +++ b/src/actions/about.ts @@ -3,9 +3,9 @@ import { staticFetch } from '../api'; import type { AnyAction } from 'redux'; import type { RootState } from 'soapbox/store'; -const FETCH_ABOUT_PAGE_REQUEST = 'FETCH_ABOUT_PAGE_REQUEST'; -const FETCH_ABOUT_PAGE_SUCCESS = 'FETCH_ABOUT_PAGE_SUCCESS'; -const FETCH_ABOUT_PAGE_FAIL = 'FETCH_ABOUT_PAGE_FAIL'; +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, getState: () => RootState) => { dispatch({ type: FETCH_ABOUT_PAGE_REQUEST, slug, locale }); diff --git a/src/actions/account-notes.ts b/src/actions/account-notes.ts index b8846b20b..750090929 100644 --- a/src/actions/account-notes.ts +++ b/src/actions/account-notes.ts @@ -3,31 +3,34 @@ import { getClient } from '../api'; import type { AnyAction } from 'redux'; import type { RootState } from 'soapbox/store'; -const ACCOUNT_NOTE_SUBMIT_REQUEST = 'ACCOUNT_NOTE_SUBMIT_REQUEST'; -const ACCOUNT_NOTE_SUBMIT_SUCCESS = 'ACCOUNT_NOTE_SUBMIT_SUCCESS'; -const ACCOUNT_NOTE_SUBMIT_FAIL = 'ACCOUNT_NOTE_SUBMIT_FAIL'; +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 = (id: string, value: string) => +const submitAccountNote = (accountId: string, value: string) => (dispatch: React.Dispatch, getState: () => RootState) => { - dispatch(submitAccountNoteRequest()); + dispatch(submitAccountNoteRequest(accountId)); - return getClient(getState).accounts.updateAccountNote(id, value) + return getClient(getState).accounts.updateAccountNote(accountId, value) .then(response => { dispatch(submitAccountNoteSuccess(response)); - }).catch(error => dispatch(submitAccountNoteFail(error))); + }).catch(error => dispatch(submitAccountNoteFail(accountId, error))); }; -const submitAccountNoteRequest = () => ({ +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 = (error: unknown) => ({ +const submitAccountNoteFail = (accountId: string, error: unknown) => ({ type: ACCOUNT_NOTE_SUBMIT_FAIL, + accountId, error, }); diff --git a/src/actions/accounts.test.ts b/src/actions/accounts.test.ts index 0f6af8cb0..fed8bb8b0 100644 --- a/src/actions/accounts.test.ts +++ b/src/actions/accounts.test.ts @@ -217,7 +217,6 @@ describe('fetchAccountByUsername()', () => { expect(actions[0]).toEqual({ type: 'RELATIONSHIPS_FETCH_REQUEST', ids: ['123'], - skipLoading: true, }); expect(actions[1].type).toEqual('ACCOUNTS_IMPORT'); expect(actions[2].type).toEqual('ACCOUNT_FETCH_SUCCESS'); @@ -341,7 +340,6 @@ describe('fetchAccountByUsername()', () => { expect(actions[3]).toEqual({ type: 'RELATIONSHIPS_FETCH_REQUEST', ids: [ '123' ], - skipLoading: true, }); expect(actions[4].type).toEqual('ACCOUNT_FETCH_SUCCESS'); }); @@ -1215,12 +1213,8 @@ describe('fetchRelationships()', () => { it('should dispatch the correct actions', async() => { const expectedActions = [ - { type: 'RELATIONSHIPS_FETCH_REQUEST', ids: [id], skipLoading: true }, - { - type: 'RELATIONSHIPS_FETCH_SUCCESS', - relationships: [], - skipLoading: true, - }, + { type: 'RELATIONSHIPS_FETCH_REQUEST', ids: [id] }, + { type: 'RELATIONSHIPS_FETCH_SUCCESS', relationships: [] }, ]; await store.dispatch(fetchRelationships([id])); const actions = store.getActions(); @@ -1240,8 +1234,8 @@ describe('fetchRelationships()', () => { it('should dispatch the correct actions', async() => { const expectedActions = [ - { type: 'RELATIONSHIPS_FETCH_REQUEST', ids: [id], skipLoading: true }, - { type: 'RELATIONSHIPS_FETCH_FAIL', skipLoading: true, error: new Error('Network Error') }, + { type: 'RELATIONSHIPS_FETCH_REQUEST', ids: [id] }, + { type: 'RELATIONSHIPS_FETCH_FAIL', error: new Error('Network Error') }, ]; await store.dispatch(fetchRelationships([id])); const actions = store.getActions(); diff --git a/src/actions/accounts.ts b/src/actions/accounts.ts index e66bdfbe8..99d3512dc 100644 --- a/src/actions/accounts.ts +++ b/src/actions/accounts.ts @@ -1,8 +1,9 @@ +import { PLEROMA, type UpdateNotificationSettingsParams, type Account, type CreateAccountParams, type PaginatedResponse, type Relationship } from 'pl-api'; + import { importEntities } from 'soapbox/entity-store/actions'; import { Entities } from 'soapbox/entity-store/entities'; import { selectAccount } from 'soapbox/selectors'; import { isLoggedIn } from 'soapbox/utils/auth'; -import { getFeatures, parseVersion, PLEROMA } from 'soapbox/utils/features'; import { getClient, type PlfeResponse } from '../api'; @@ -13,102 +14,101 @@ import { } from './importer'; import type { Map as ImmutableMap } from 'immutable'; -import type { Account, CreateAccountParams, PaginatedResponse } from 'pl-api'; +import type { ReducerStatus } from 'soapbox/reducers/statuses'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity, Status } from 'soapbox/types/entities'; import type { History } from 'soapbox/types/history'; -const ACCOUNT_CREATE_REQUEST = 'ACCOUNT_CREATE_REQUEST'; -const ACCOUNT_CREATE_SUCCESS = 'ACCOUNT_CREATE_SUCCESS'; -const ACCOUNT_CREATE_FAIL = 'ACCOUNT_CREATE_FAIL'; +const ACCOUNT_CREATE_REQUEST = 'ACCOUNT_CREATE_REQUEST' as const; +const ACCOUNT_CREATE_SUCCESS = 'ACCOUNT_CREATE_SUCCESS' as const; +const ACCOUNT_CREATE_FAIL = 'ACCOUNT_CREATE_FAIL' as const; -const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST'; -const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS'; -const ACCOUNT_FETCH_FAIL = 'ACCOUNT_FETCH_FAIL'; +const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST' as const; +const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS' as const; +const ACCOUNT_FETCH_FAIL = 'ACCOUNT_FETCH_FAIL' as const; -const ACCOUNT_BLOCK_REQUEST = 'ACCOUNT_BLOCK_REQUEST'; -const ACCOUNT_BLOCK_SUCCESS = 'ACCOUNT_BLOCK_SUCCESS'; -const ACCOUNT_BLOCK_FAIL = 'ACCOUNT_BLOCK_FAIL'; +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'; -const ACCOUNT_UNBLOCK_SUCCESS = 'ACCOUNT_UNBLOCK_SUCCESS'; -const ACCOUNT_UNBLOCK_FAIL = 'ACCOUNT_UNBLOCK_FAIL'; +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'; -const ACCOUNT_MUTE_SUCCESS = 'ACCOUNT_MUTE_SUCCESS'; -const ACCOUNT_MUTE_FAIL = 'ACCOUNT_MUTE_FAIL'; +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'; -const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS'; -const ACCOUNT_UNMUTE_FAIL = 'ACCOUNT_UNMUTE_FAIL'; +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'; -const ACCOUNT_PIN_SUCCESS = 'ACCOUNT_PIN_SUCCESS'; -const ACCOUNT_PIN_FAIL = 'ACCOUNT_PIN_FAIL'; +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'; -const ACCOUNT_UNPIN_SUCCESS = 'ACCOUNT_UNPIN_SUCCESS'; -const ACCOUNT_UNPIN_FAIL = 'ACCOUNT_UNPIN_FAIL'; +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'; -const ACCOUNT_REMOVE_FROM_FOLLOWERS_SUCCESS = 'ACCOUNT_REMOVE_FROM_FOLLOWERS_SUCCESS'; -const ACCOUNT_REMOVE_FROM_FOLLOWERS_FAIL = 'ACCOUNT_REMOVE_FROM_FOLLOWERS_FAIL'; +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'; -const PINNED_ACCOUNTS_FETCH_SUCCESS = 'PINNED_ACCOUNTS_FETCH_SUCCESS'; -const PINNED_ACCOUNTS_FETCH_FAIL = 'PINNED_ACCOUNTS_FETCH_FAIL'; +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; -const ACCOUNT_SEARCH_REQUEST = 'ACCOUNT_SEARCH_REQUEST'; -const ACCOUNT_SEARCH_SUCCESS = 'ACCOUNT_SEARCH_SUCCESS'; -const ACCOUNT_SEARCH_FAIL = 'ACCOUNT_SEARCH_FAIL'; +const ACCOUNT_SEARCH_REQUEST = 'ACCOUNT_SEARCH_REQUEST' as const; +const ACCOUNT_SEARCH_SUCCESS = 'ACCOUNT_SEARCH_SUCCESS' as const; +const ACCOUNT_SEARCH_FAIL = 'ACCOUNT_SEARCH_FAIL' as const; -const ACCOUNT_LOOKUP_REQUEST = 'ACCOUNT_LOOKUP_REQUEST'; -const ACCOUNT_LOOKUP_SUCCESS = 'ACCOUNT_LOOKUP_SUCCESS'; -const ACCOUNT_LOOKUP_FAIL = 'ACCOUNT_LOOKUP_FAIL'; +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 FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST'; -const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS'; -const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL'; +const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST' as const; +const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS' as const; +const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL' as const; -const FOLLOWERS_EXPAND_REQUEST = 'FOLLOWERS_EXPAND_REQUEST'; -const FOLLOWERS_EXPAND_SUCCESS = 'FOLLOWERS_EXPAND_SUCCESS'; -const FOLLOWERS_EXPAND_FAIL = 'FOLLOWERS_EXPAND_FAIL'; +const FOLLOWERS_EXPAND_REQUEST = 'FOLLOWERS_EXPAND_REQUEST' as const; +const FOLLOWERS_EXPAND_SUCCESS = 'FOLLOWERS_EXPAND_SUCCESS' as const; +const FOLLOWERS_EXPAND_FAIL = 'FOLLOWERS_EXPAND_FAIL' as const; -const FOLLOWING_FETCH_REQUEST = 'FOLLOWING_FETCH_REQUEST'; -const FOLLOWING_FETCH_SUCCESS = 'FOLLOWING_FETCH_SUCCESS'; -const FOLLOWING_FETCH_FAIL = 'FOLLOWING_FETCH_FAIL'; +const FOLLOWING_FETCH_REQUEST = 'FOLLOWING_FETCH_REQUEST' as const; +const FOLLOWING_FETCH_SUCCESS = 'FOLLOWING_FETCH_SUCCESS' as const; +const FOLLOWING_FETCH_FAIL = 'FOLLOWING_FETCH_FAIL' as const; -const FOLLOWING_EXPAND_REQUEST = 'FOLLOWING_EXPAND_REQUEST'; -const FOLLOWING_EXPAND_SUCCESS = 'FOLLOWING_EXPAND_SUCCESS'; -const FOLLOWING_EXPAND_FAIL = 'FOLLOWING_EXPAND_FAIL'; +const FOLLOWING_EXPAND_REQUEST = 'FOLLOWING_EXPAND_REQUEST' as const; +const FOLLOWING_EXPAND_SUCCESS = 'FOLLOWING_EXPAND_SUCCESS' as const; +const FOLLOWING_EXPAND_FAIL = 'FOLLOWING_EXPAND_FAIL' as const; -const RELATIONSHIPS_FETCH_REQUEST = 'RELATIONSHIPS_FETCH_REQUEST'; -const RELATIONSHIPS_FETCH_SUCCESS = 'RELATIONSHIPS_FETCH_SUCCESS'; -const RELATIONSHIPS_FETCH_FAIL = 'RELATIONSHIPS_FETCH_FAIL'; +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'; -const FOLLOW_REQUESTS_FETCH_SUCCESS = 'FOLLOW_REQUESTS_FETCH_SUCCESS'; -const FOLLOW_REQUESTS_FETCH_FAIL = 'FOLLOW_REQUESTS_FETCH_FAIL'; +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; -const FOLLOW_REQUESTS_EXPAND_REQUEST = 'FOLLOW_REQUESTS_EXPAND_REQUEST'; -const FOLLOW_REQUESTS_EXPAND_SUCCESS = 'FOLLOW_REQUESTS_EXPAND_SUCCESS'; -const FOLLOW_REQUESTS_EXPAND_FAIL = 'FOLLOW_REQUESTS_EXPAND_FAIL'; +const FOLLOW_REQUESTS_EXPAND_REQUEST = 'FOLLOW_REQUESTS_EXPAND_REQUEST' as const; +const FOLLOW_REQUESTS_EXPAND_SUCCESS = 'FOLLOW_REQUESTS_EXPAND_SUCCESS' as const; +const FOLLOW_REQUESTS_EXPAND_FAIL = 'FOLLOW_REQUESTS_EXPAND_FAIL' as const; -const FOLLOW_REQUEST_AUTHORIZE_REQUEST = 'FOLLOW_REQUEST_AUTHORIZE_REQUEST'; -const FOLLOW_REQUEST_AUTHORIZE_SUCCESS = 'FOLLOW_REQUEST_AUTHORIZE_SUCCESS'; -const FOLLOW_REQUEST_AUTHORIZE_FAIL = 'FOLLOW_REQUEST_AUTHORIZE_FAIL'; +const FOLLOW_REQUEST_AUTHORIZE_REQUEST = 'FOLLOW_REQUEST_AUTHORIZE_REQUEST' as const; +const FOLLOW_REQUEST_AUTHORIZE_SUCCESS = 'FOLLOW_REQUEST_AUTHORIZE_SUCCESS' as const; +const FOLLOW_REQUEST_AUTHORIZE_FAIL = 'FOLLOW_REQUEST_AUTHORIZE_FAIL' as const; -const FOLLOW_REQUEST_REJECT_REQUEST = 'FOLLOW_REQUEST_REJECT_REQUEST'; -const FOLLOW_REQUEST_REJECT_SUCCESS = 'FOLLOW_REQUEST_REJECT_SUCCESS'; -const FOLLOW_REQUEST_REJECT_FAIL = 'FOLLOW_REQUEST_REJECT_FAIL'; +const FOLLOW_REQUEST_REJECT_REQUEST = 'FOLLOW_REQUEST_REJECT_REQUEST' as const; +const FOLLOW_REQUEST_REJECT_SUCCESS = 'FOLLOW_REQUEST_REJECT_SUCCESS' as const; +const FOLLOW_REQUEST_REJECT_FAIL = 'FOLLOW_REQUEST_REJECT_FAIL' as const; -const NOTIFICATION_SETTINGS_REQUEST = 'NOTIFICATION_SETTINGS_REQUEST'; -const NOTIFICATION_SETTINGS_SUCCESS = 'NOTIFICATION_SETTINGS_SUCCESS'; -const NOTIFICATION_SETTINGS_FAIL = 'NOTIFICATION_SETTINGS_FAIL'; +const NOTIFICATION_SETTINGS_REQUEST = 'NOTIFICATION_SETTINGS_REQUEST' as const; +const NOTIFICATION_SETTINGS_SUCCESS = 'NOTIFICATION_SETTINGS_SUCCESS' as const; +const NOTIFICATION_SETTINGS_FAIL = 'NOTIFICATION_SETTINGS_FAIL' as const; -const BIRTHDAY_REMINDERS_FETCH_REQUEST = 'BIRTHDAY_REMINDERS_FETCH_REQUEST'; -const BIRTHDAY_REMINDERS_FETCH_SUCCESS = 'BIRTHDAY_REMINDERS_FETCH_SUCCESS'; -const BIRTHDAY_REMINDERS_FETCH_FAIL = 'BIRTHDAY_REMINDERS_FETCH_FAIL'; +const BIRTHDAY_REMINDERS_FETCH_REQUEST = 'BIRTHDAY_REMINDERS_FETCH_REQUEST' as const; +const BIRTHDAY_REMINDERS_FETCH_SUCCESS = 'BIRTHDAY_REMINDERS_FETCH_SUCCESS' as const; +const BIRTHDAY_REMINDERS_FETCH_FAIL = 'BIRTHDAY_REMINDERS_FETCH_FAIL' as const; const maybeRedirectLogin = (error: { response: PlfeResponse }, history?: History) => { // The client is unauthorized - redirect to login. @@ -130,32 +130,32 @@ const createAccount = (params: CreateAccountParams) => }); }; -const fetchAccount = (id: string) => +const fetchAccount = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - dispatch(fetchRelationships([id])); + dispatch(fetchRelationships([accountId])); - const account = selectAccount(getState(), id); + const account = selectAccount(getState(), accountId); if (account) { return Promise.resolve(null); } - dispatch(fetchAccountRequest(id)); + dispatch(fetchAccountRequest(accountId)); - return getClient(getState()).accounts.getAccount(id) + return getClient(getState()).accounts.getAccount(accountId) .then(response => { dispatch(importFetchedAccount(response)); dispatch(fetchAccountSuccess(response)); }) .catch(error => { - dispatch(fetchAccountFail(id, error)); + dispatch(fetchAccountFail(accountId, error)); }); }; const fetchAccountByUsername = (username: string, history?: History) => (dispatch: AppDispatch, getState: () => RootState) => { - const { instance, me } = getState(); - const features = getFeatures(instance); + const { auth, me } = getState(); + const features = auth.client.features; if (features.accountByUsername && (me || !features.accountLookup)) { return getClient(getState()).accounts.getAccount(username).then(response => { @@ -177,7 +177,7 @@ const fetchAccountByUsername = (username: string, history?: History) => }); } else { return getClient(getState()).accounts.searchAccounts(username, { resolve: true, limit: 1 }).then(accounts => { - const found = accounts.find((a: APIEntity) => a.acct === username); + const found = accounts.find((a) => a.acct === username); if (found) { dispatch(fetchRelationships([found.id])); @@ -192,30 +192,30 @@ const fetchAccountByUsername = (username: string, history?: History) => } }; -const fetchAccountRequest = (id: string) => ({ +const fetchAccountRequest = (accountId: string) => ({ type: ACCOUNT_FETCH_REQUEST, - id, + accountId, }); -const fetchAccountSuccess = (account: APIEntity) => ({ +const fetchAccountSuccess = (account: Account) => ({ type: ACCOUNT_FETCH_SUCCESS, account, }); -const fetchAccountFail = (id: string | null, error: unknown) => ({ +const fetchAccountFail = (accountId: string | null, error: unknown) => ({ type: ACCOUNT_FETCH_FAIL, - id, + accountId, error, skipAlert: true, }); -const blockAccount = (id: string) => +const blockAccount = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return null; - dispatch(blockAccountRequest(id)); + dispatch(blockAccountRequest(accountId)); - return getClient(getState()).filtering.blockAccount(id) + return getClient(getState).filtering.blockAccount(accountId) .then(response => { dispatch(importEntities([response], Entities.RELATIONSHIPS)); // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers @@ -223,13 +223,13 @@ const blockAccount = (id: string) => }).catch(error => dispatch(blockAccountFail(error))); }; -const unblockAccount = (id: string) => +const unblockAccount = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return null; - dispatch(unblockAccountRequest(id)); + dispatch(unblockAccountRequest(accountId)); - return getClient(getState()).filtering.unblockAccount(id) + return getClient(getState).filtering.unblockAccount(accountId) .then(response => { dispatch(importEntities([response], Entities.RELATIONSHIPS)); return dispatch(unblockAccountSuccess(response)); @@ -237,12 +237,12 @@ const unblockAccount = (id: string) => .catch(error => dispatch(unblockAccountFail(error))); }; -const blockAccountRequest = (id: string) => ({ +const blockAccountRequest = (accountId: string) => ({ type: ACCOUNT_BLOCK_REQUEST, - id, + accountId, }); -const blockAccountSuccess = (relationship: APIEntity, statuses: ImmutableMap) => ({ +const blockAccountSuccess = (relationship: Relationship, statuses: ImmutableMap) => ({ type: ACCOUNT_BLOCK_SUCCESS, relationship, statuses, @@ -253,12 +253,12 @@ const blockAccountFail = (error: unknown) => ({ error, }); -const unblockAccountRequest = (id: string) => ({ +const unblockAccountRequest = (accountId: string) => ({ type: ACCOUNT_UNBLOCK_REQUEST, - id, + accountId, }); -const unblockAccountSuccess = (relationship: APIEntity) => ({ +const unblockAccountSuccess = (relationship: Relationship) => ({ type: ACCOUNT_UNBLOCK_SUCCESS, relationship, }); @@ -268,21 +268,20 @@ const unblockAccountFail = (error: unknown) => ({ error, }); -const muteAccount = (id: string, notifications?: boolean, duration = 0) => +const muteAccount = (accountId: string, notifications?: boolean, duration = 0) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return null; const client = getClient(getState); - dispatch(muteAccountRequest(id)); + dispatch(muteAccountRequest(accountId)); const params: Record = { notifications, }; if (duration) { - const instance = client.instanceInformation; - const v = parseVersion(instance.version); + const v = client.features.version; if (v.software === PLEROMA) { params.expires_in = duration; @@ -291,225 +290,227 @@ const muteAccount = (id: string, notifications?: boolean, duration = 0) => } } - return client.filtering.muteAccount(id, params) + return client.filtering.muteAccount(accountId, params) .then(response => { dispatch(importEntities([response], Entities.RELATIONSHIPS)); // 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)); }) - .catch(error => dispatch(muteAccountFail(error))); + .catch(error => dispatch(muteAccountFail(accountId, error))); }; -const unmuteAccount = (id: string) => +const unmuteAccount = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return null; - dispatch(unmuteAccountRequest(id)); + dispatch(unmuteAccountRequest(accountId)); - return getClient(getState()).filtering.unmuteAccount(id) + return getClient(getState()).filtering.unmuteAccount(accountId) .then(response => { dispatch(importEntities([response], Entities.RELATIONSHIPS)); return dispatch(unmuteAccountSuccess(response)); }) - .catch(error => dispatch(unmuteAccountFail(error))); + .catch(error => dispatch(unmuteAccountFail(accountId, error))); }; -const muteAccountRequest = (id: string) => ({ +const muteAccountRequest = (accountId: string) => ({ type: ACCOUNT_MUTE_REQUEST, - id, + accountId, }); -const muteAccountSuccess = (relationship: APIEntity, statuses: ImmutableMap) => ({ +const muteAccountSuccess = (relationship: Relationship, statuses: ImmutableMap) => ({ type: ACCOUNT_MUTE_SUCCESS, relationship, statuses, }); -const muteAccountFail = (error: unknown) => ({ +const muteAccountFail = (accountId: string, error: unknown) => ({ type: ACCOUNT_MUTE_FAIL, + accountId, error, }); -const unmuteAccountRequest = (id: string) => ({ +const unmuteAccountRequest = (accountId: string) => ({ type: ACCOUNT_UNMUTE_REQUEST, - id, + accountId, }); -const unmuteAccountSuccess = (relationship: APIEntity) => ({ +const unmuteAccountSuccess = (relationship: Relationship) => ({ type: ACCOUNT_UNMUTE_SUCCESS, relationship, }); -const unmuteAccountFail = (error: unknown) => ({ +const unmuteAccountFail = (accountId: string, error: unknown) => ({ type: ACCOUNT_UNMUTE_FAIL, + accountId, error, }); -const removeFromFollowers = (id: string) => +const removeFromFollowers = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return null; - dispatch(removeFromFollowersRequest(id)); + dispatch(removeFromFollowersRequest(accountId)); - return getClient(getState()).accounts.removeAccountFromFollowers(id) + return getClient(getState()).accounts.removeAccountFromFollowers(accountId) .then(response => dispatch(removeFromFollowersSuccess(response))) - .catch(error => dispatch(removeFromFollowersFail(id, error))); + .catch(error => dispatch(removeFromFollowersFail(accountId, error))); }; -const removeFromFollowersRequest = (id: string) => ({ +const removeFromFollowersRequest = (accountId: string) => ({ type: ACCOUNT_REMOVE_FROM_FOLLOWERS_REQUEST, - id, + accountId, }); -const removeFromFollowersSuccess = (relationship: APIEntity) => ({ +const removeFromFollowersSuccess = (relationship: Relationship) => ({ type: ACCOUNT_REMOVE_FROM_FOLLOWERS_SUCCESS, relationship, }); -const removeFromFollowersFail = (id: string, error: unknown) => ({ +const removeFromFollowersFail = (accountId: string, error: unknown) => ({ type: ACCOUNT_REMOVE_FROM_FOLLOWERS_FAIL, - id, + accountId, error, }); -const fetchFollowers = (id: string) => +const fetchFollowers = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - dispatch(fetchFollowersRequest(id)); + dispatch(fetchFollowersRequest(accountId)); - return getClient(getState()).accounts.getAccountFollowers(id) + return getClient(getState()).accounts.getAccountFollowers(accountId) .then(response => { dispatch(importFetchedAccounts(response.items)); - dispatch(fetchFollowersSuccess(id, response.items, response.next)); + dispatch(fetchFollowersSuccess(accountId, response.items, response.next)); dispatch(fetchRelationships(response.items.map((item) => item.id))); }) .catch(error => { - dispatch(fetchFollowersFail(id, error)); + dispatch(fetchFollowersFail(accountId, error)); }); }; -const fetchFollowersRequest = (id: string) => ({ +const fetchFollowersRequest = (accountId: string) => ({ type: FOLLOWERS_FETCH_REQUEST, - id, + accountId, }); -const fetchFollowersSuccess = (id: string, accounts: APIEntity[], next: (() => Promise>) | null) => ({ +const fetchFollowersSuccess = (accountId: string, accounts: Array, next: (() => Promise>) | null) => ({ type: FOLLOWERS_FETCH_SUCCESS, - id, + accountId, accounts, next, }); -const fetchFollowersFail = (id: string, error: unknown) => ({ +const fetchFollowersFail = (accountId: string, error: unknown) => ({ type: FOLLOWERS_FETCH_FAIL, - id, + accountId, error, }); -const expandFollowers = (id: string) => +const expandFollowers = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return null; - const next = getState().user_lists.followers.get(id)!.next; + const next = getState().user_lists.followers.get(accountId)!.next; if (next === null) return; - dispatch(expandFollowersRequest(id)); + dispatch(expandFollowersRequest(accountId)); next().then(response => { dispatch(importFetchedAccounts(response.items)); - dispatch(expandFollowersSuccess(id, response.items, response.next)); - dispatch(fetchRelationships(response.items.map((item: APIEntity) => item.id))); + dispatch(expandFollowersSuccess(accountId, response.items, response.next)); + dispatch(fetchRelationships(response.items.map((item) => item.id))); }) .catch(error => { - dispatch(expandFollowersFail(id, error)); + dispatch(expandFollowersFail(accountId, error)); }); }; -const expandFollowersRequest = (id: string) => ({ +const expandFollowersRequest = (accountId: string) => ({ type: FOLLOWERS_EXPAND_REQUEST, - id, + accountId, }); -const expandFollowersSuccess = (id: string, accounts: APIEntity[], next: (() => Promise>) | null) => ({ +const expandFollowersSuccess = (accountId: string, accounts: Array, next: (() => Promise>) | null) => ({ type: FOLLOWERS_EXPAND_SUCCESS, - id, + accountId, accounts, next, }); -const expandFollowersFail = (id: string, error: unknown) => ({ +const expandFollowersFail = (accountId: string, error: unknown) => ({ type: FOLLOWERS_EXPAND_FAIL, - id, + accountId, error, }); -const fetchFollowing = (id: string) => +const fetchFollowing = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - dispatch(fetchFollowingRequest(id)); + dispatch(fetchFollowingRequest(accountId)); - return getClient(getState()).accounts.getAccountFollowing(id) + return getClient(getState()).accounts.getAccountFollowing(accountId) .then(response => { dispatch(importFetchedAccounts(response.items)); - dispatch(fetchFollowingSuccess(id, response.items, response.next)); + dispatch(fetchFollowingSuccess(accountId, response.items, response.next)); dispatch(fetchRelationships(response.items.map((item) => item.id))); }) .catch(error => { - dispatch(fetchFollowingFail(id, error)); + dispatch(fetchFollowingFail(accountId, error)); }); }; -const fetchFollowingRequest = (id: string) => ({ +const fetchFollowingRequest = (accountId: string) => ({ type: FOLLOWING_FETCH_REQUEST, - id, + accountId, }); -const fetchFollowingSuccess = (id: string, accounts: APIEntity[], next: (() => Promise>) | null) => ({ +const fetchFollowingSuccess = (accountId: string, accounts: Array, next: (() => Promise>) | null) => ({ type: FOLLOWING_FETCH_SUCCESS, - id, + accountId, accounts, next, }); -const fetchFollowingFail = (id: string, error: unknown) => ({ +const fetchFollowingFail = (accountId: string, error: unknown) => ({ type: FOLLOWING_FETCH_FAIL, - id, + accountId, error, }); -const expandFollowing = (id: string) => +const expandFollowing = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return null; - const next = getState().user_lists.following.get(id)!.next; + const next = getState().user_lists.following.get(accountId)!.next; if (next === null) return; - dispatch(expandFollowingRequest(id)); + dispatch(expandFollowingRequest(accountId)); next().then(response => { dispatch(importFetchedAccounts(response.items)); - dispatch(expandFollowingSuccess(id, response.items, response.next)); - dispatch(fetchRelationships(response.items.map((item: APIEntity) => item.id))); + dispatch(expandFollowingSuccess(accountId, response.items, response.next)); + dispatch(fetchRelationships(response.items.map((item) => item.id))); }).catch(error => { - dispatch(expandFollowingFail(id, error)); + dispatch(expandFollowingFail(accountId, error)); }); }; -const expandFollowingRequest = (id: string) => ({ +const expandFollowingRequest = (accountId: string) => ({ type: FOLLOWING_EXPAND_REQUEST, - id, + accountId, }); -const expandFollowingSuccess = (id: string, accounts: APIEntity[], next: (() => Promise>) | null) => ({ +const expandFollowingSuccess = (accountId: string, accounts: Array, next: (() => Promise>) | null) => ({ type: FOLLOWING_EXPAND_SUCCESS, - id, + accountId, accounts, next, }); -const expandFollowingFail = (id: string, error: unknown) => ({ +const expandFollowingFail = (accountId: string, error: unknown) => ({ type: FOLLOWING_EXPAND_FAIL, - id, + accountId, error, }); @@ -534,22 +535,19 @@ const fetchRelationships = (accountIds: string[]) => .catch(error => dispatch(fetchRelationshipsFail(error))); }; -const fetchRelationshipsRequest = (ids: string[]) => ({ +const fetchRelationshipsRequest = (accountIds: string[]) => ({ type: RELATIONSHIPS_FETCH_REQUEST, - ids, - skipLoading: true, + accountIds, }); -const fetchRelationshipsSuccess = (relationships: APIEntity[]) => ({ +const fetchRelationshipsSuccess = (relationships: Array) => ({ type: RELATIONSHIPS_FETCH_SUCCESS, relationships, - skipLoading: true, }); const fetchRelationshipsFail = (error: unknown) => ({ type: RELATIONSHIPS_FETCH_FAIL, error, - skipLoading: true, }); const fetchFollowRequests = () => @@ -570,7 +568,7 @@ const fetchFollowRequestsRequest = () => ({ type: FOLLOW_REQUESTS_FETCH_REQUEST, }); -const fetchFollowRequestsSuccess = (accounts: APIEntity[], next: (() => Promise>) | null) => ({ +const fetchFollowRequestsSuccess = (accounts: Array, next: (() => Promise>) | null) => ({ type: FOLLOW_REQUESTS_FETCH_SUCCESS, accounts, next, @@ -601,7 +599,7 @@ const expandFollowRequestsRequest = () => ({ type: FOLLOW_REQUESTS_EXPAND_REQUEST, }); -const expandFollowRequestsSuccess = (accounts: APIEntity[], next: (() => Promise>) | null) => ({ +const expandFollowRequestsSuccess = (accounts: Array, next: (() => Promise>) | null) => ({ type: FOLLOW_REQUESTS_EXPAND_SUCCESS, accounts, next, @@ -612,93 +610,90 @@ const expandFollowRequestsFail = (error: unknown) => ({ error, }); -const authorizeFollowRequest = (id: string) => +const authorizeFollowRequest = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return null; - dispatch(authorizeFollowRequestRequest(id)); + dispatch(authorizeFollowRequestRequest(accountId)); - return getClient(getState()).myAccount.acceptFollowRequest(id) - .then(() => dispatch(authorizeFollowRequestSuccess(id))) - .catch(error => dispatch(authorizeFollowRequestFail(id, error))); + return getClient(getState()).myAccount.acceptFollowRequest(accountId) + .then(() => dispatch(authorizeFollowRequestSuccess(accountId))) + .catch(error => dispatch(authorizeFollowRequestFail(accountId, error))); }; -const authorizeFollowRequestRequest = (id: string) => ({ +const authorizeFollowRequestRequest = (accountId: string) => ({ type: FOLLOW_REQUEST_AUTHORIZE_REQUEST, - id, + accountId, }); -const authorizeFollowRequestSuccess = (id: string) => ({ +const authorizeFollowRequestSuccess = (accountId: string) => ({ type: FOLLOW_REQUEST_AUTHORIZE_SUCCESS, - id, + accountId, }); -const authorizeFollowRequestFail = (id: string, error: unknown) => ({ +const authorizeFollowRequestFail = (accountId: string, error: unknown) => ({ type: FOLLOW_REQUEST_AUTHORIZE_FAIL, - id, + accountId, error, }); -const rejectFollowRequest = (id: string) => +const rejectFollowRequest = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - dispatch(rejectFollowRequestRequest(id)); + dispatch(rejectFollowRequestRequest(accountId)); - return getClient(getState()).myAccount.rejectFollowRequest(id) - .then(() => dispatch(rejectFollowRequestSuccess(id))) - .catch(error => dispatch(rejectFollowRequestFail(id, error))); + return getClient(getState()).myAccount.rejectFollowRequest(accountId) + .then(() => dispatch(rejectFollowRequestSuccess(accountId))) + .catch(error => dispatch(rejectFollowRequestFail(accountId, error))); }; -const rejectFollowRequestRequest = (id: string) => ({ +const rejectFollowRequestRequest = (accountId: string) => ({ type: FOLLOW_REQUEST_REJECT_REQUEST, - id, + accountId, }); -const rejectFollowRequestSuccess = (id: string) => ({ +const rejectFollowRequestSuccess = (accountId: string) => ({ type: FOLLOW_REQUEST_REJECT_SUCCESS, - id, + accountId, }); -const rejectFollowRequestFail = (id: string, error: unknown) => ({ +const rejectFollowRequestFail = (accountId: string, error: unknown) => ({ type: FOLLOW_REQUEST_REJECT_FAIL, - id, + accountId, error, }); -const pinAccount = (id: string) => +const pinAccount = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return dispatch(noOp); - dispatch(pinAccountRequest(id)); + dispatch(pinAccountRequest(accountId)); - return getClient(getState()).accounts.pinAccount(id).then(response => { + return getClient(getState).accounts.pinAccount(accountId).then(response => { dispatch(pinAccountSuccess(response)); }).catch(error => { dispatch(pinAccountFail(error)); }); }; -const unpinAccount = (id: string) => +const unpinAccount = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return dispatch(noOp); - dispatch(unpinAccountRequest(id)); + dispatch(unpinAccountRequest(accountId)); - return getClient(getState()).accounts.unpinAccount(id).then(response => { + return getClient(getState).accounts.unpinAccount(accountId).then(response => { dispatch(unpinAccountSuccess(response)); }).catch(error => { dispatch(unpinAccountFail(error)); }); }; -const updateNotificationSettings = (params: Record) => +const updateNotificationSettings = (params: UpdateNotificationSettingsParams) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: NOTIFICATION_SETTINGS_REQUEST, params }); - return getClient(getState).request('/api/pleroma/notification_settings', { - method: 'PUT', - body: params, - }).then(({ json: data }) => { + return getClient(getState).settings.updateNotificationSettings(params).then((data) => { dispatch({ type: NOTIFICATION_SETTINGS_SUCCESS, params, data }); }).catch(error => { dispatch({ type: NOTIFICATION_SETTINGS_FAIL, params, error }); @@ -706,12 +701,12 @@ const updateNotificationSettings = (params: Record) => }); }; -const pinAccountRequest = (id: string) => ({ +const pinAccountRequest = (accountId: string) => ({ type: ACCOUNT_PIN_REQUEST, - id, + accountId, }); -const pinAccountSuccess = (relationship: APIEntity) => ({ +const pinAccountSuccess = (relationship: Relationship) => ({ type: ACCOUNT_PIN_SUCCESS, relationship, }); @@ -721,12 +716,12 @@ const pinAccountFail = (error: unknown) => ({ error, }); -const unpinAccountRequest = (id: string) => ({ +const unpinAccountRequest = (accountId: string) => ({ type: ACCOUNT_UNPIN_REQUEST, - id, + accountId, }); -const unpinAccountSuccess = (relationship: APIEntity) => ({ +const unpinAccountSuccess = (relationship: Relationship) => ({ type: ACCOUNT_UNPIN_SUCCESS, relationship, }); @@ -736,33 +731,33 @@ const unpinAccountFail = (error: unknown) => ({ error, }); -const fetchPinnedAccounts = (id: string) => +const fetchPinnedAccounts = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - dispatch(fetchPinnedAccountsRequest(id)); + dispatch(fetchPinnedAccountsRequest(accountId)); - return getClient(getState).accounts.getAccountEndorsements(id).then(response => { + return getClient(getState).accounts.getAccountEndorsements(accountId).then(response => { dispatch(importFetchedAccounts(response)); - dispatch(fetchPinnedAccountsSuccess(id, response, null)); + dispatch(fetchPinnedAccountsSuccess(accountId, response, null)); }).catch(error => { - dispatch(fetchPinnedAccountsFail(id, error)); + dispatch(fetchPinnedAccountsFail(accountId, error)); }); }; -const fetchPinnedAccountsRequest = (id: string) => ({ +const fetchPinnedAccountsRequest = (accountId: string) => ({ type: PINNED_ACCOUNTS_FETCH_REQUEST, - id, + accountId, }); -const fetchPinnedAccountsSuccess = (id: string, accounts: APIEntity[], next: string | null) => ({ +const fetchPinnedAccountsSuccess = (accountId: string, accounts: Array, next: string | null) => ({ type: PINNED_ACCOUNTS_FETCH_SUCCESS, - id, + accountId, accounts, next, }); -const fetchPinnedAccountsFail = (id: string, error: unknown) => ({ +const fetchPinnedAccountsFail = (accountId: string, error: unknown) => ({ type: PINNED_ACCOUNTS_FETCH_FAIL, - id, + accountId, error, }); @@ -799,7 +794,7 @@ const fetchBirthdayReminders = (month: number, day: number) => const me = getState().me; - dispatch({ type: BIRTHDAY_REMINDERS_FETCH_REQUEST, day, month, id: me }); + dispatch({ type: BIRTHDAY_REMINDERS_FETCH_REQUEST, day, month, accountId: me }); return getClient(getState).accounts.getBirthdays(day, month).then(response => { dispatch(importFetchedAccounts(response)); @@ -808,10 +803,10 @@ const fetchBirthdayReminders = (month: number, day: number) => accounts: response, day, month, - id: me, + accountId: me, }); }).catch(() => { - dispatch({ type: BIRTHDAY_REMINDERS_FETCH_FAIL, day, month, id: me }); + dispatch({ type: BIRTHDAY_REMINDERS_FETCH_FAIL, day, month, accountId: me }); }); }; diff --git a/src/actions/admin.ts b/src/actions/admin.ts index 97ab4730c..86e2161f5 100644 --- a/src/actions/admin.ts +++ b/src/actions/admin.ts @@ -3,78 +3,77 @@ import { fetchRelationships } from 'soapbox/actions/accounts'; import { importFetchedAccount, importFetchedAccounts, importFetchedStatuses } from 'soapbox/actions/importer'; import { accountIdsToAccts } from 'soapbox/selectors'; import { filterBadges, getTagDiff } from 'soapbox/utils/badges'; -import { getFeatures } from 'soapbox/utils/features'; import { getClient, getNextLink } from '../api'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity } from 'soapbox/types/entities'; -const ADMIN_CONFIG_FETCH_REQUEST = 'ADMIN_CONFIG_FETCH_REQUEST'; -const ADMIN_CONFIG_FETCH_SUCCESS = 'ADMIN_CONFIG_FETCH_SUCCESS'; -const ADMIN_CONFIG_FETCH_FAIL = 'ADMIN_CONFIG_FETCH_FAIL'; +const ADMIN_CONFIG_FETCH_REQUEST = 'ADMIN_CONFIG_FETCH_REQUEST' as const; +const ADMIN_CONFIG_FETCH_SUCCESS = 'ADMIN_CONFIG_FETCH_SUCCESS' as const; +const ADMIN_CONFIG_FETCH_FAIL = 'ADMIN_CONFIG_FETCH_FAIL' as const; -const ADMIN_CONFIG_UPDATE_REQUEST = 'ADMIN_CONFIG_UPDATE_REQUEST'; -const ADMIN_CONFIG_UPDATE_SUCCESS = 'ADMIN_CONFIG_UPDATE_SUCCESS'; -const ADMIN_CONFIG_UPDATE_FAIL = 'ADMIN_CONFIG_UPDATE_FAIL'; +const ADMIN_CONFIG_UPDATE_REQUEST = 'ADMIN_CONFIG_UPDATE_REQUEST' as const; +const ADMIN_CONFIG_UPDATE_SUCCESS = 'ADMIN_CONFIG_UPDATE_SUCCESS' as const; +const ADMIN_CONFIG_UPDATE_FAIL = 'ADMIN_CONFIG_UPDATE_FAIL' as const; -const ADMIN_REPORTS_FETCH_REQUEST = 'ADMIN_REPORTS_FETCH_REQUEST'; -const ADMIN_REPORTS_FETCH_SUCCESS = 'ADMIN_REPORTS_FETCH_SUCCESS'; -const ADMIN_REPORTS_FETCH_FAIL = 'ADMIN_REPORTS_FETCH_FAIL'; +const ADMIN_REPORTS_FETCH_REQUEST = 'ADMIN_REPORTS_FETCH_REQUEST' as const; +const ADMIN_REPORTS_FETCH_SUCCESS = 'ADMIN_REPORTS_FETCH_SUCCESS' as const; +const ADMIN_REPORTS_FETCH_FAIL = 'ADMIN_REPORTS_FETCH_FAIL' as const; -const ADMIN_REPORTS_PATCH_REQUEST = 'ADMIN_REPORTS_PATCH_REQUEST'; -const ADMIN_REPORTS_PATCH_SUCCESS = 'ADMIN_REPORTS_PATCH_SUCCESS'; -const ADMIN_REPORTS_PATCH_FAIL = 'ADMIN_REPORTS_PATCH_FAIL'; +const ADMIN_REPORTS_PATCH_REQUEST = 'ADMIN_REPORTS_PATCH_REQUEST' as const; +const ADMIN_REPORTS_PATCH_SUCCESS = 'ADMIN_REPORTS_PATCH_SUCCESS' as const; +const ADMIN_REPORTS_PATCH_FAIL = 'ADMIN_REPORTS_PATCH_FAIL' as const; -const ADMIN_USERS_FETCH_REQUEST = 'ADMIN_USERS_FETCH_REQUEST'; -const ADMIN_USERS_FETCH_SUCCESS = 'ADMIN_USERS_FETCH_SUCCESS'; -const ADMIN_USERS_FETCH_FAIL = 'ADMIN_USERS_FETCH_FAIL'; +const ADMIN_USERS_FETCH_REQUEST = 'ADMIN_USERS_FETCH_REQUEST' as const; +const ADMIN_USERS_FETCH_SUCCESS = 'ADMIN_USERS_FETCH_SUCCESS' as const; +const ADMIN_USERS_FETCH_FAIL = 'ADMIN_USERS_FETCH_FAIL' as const; -const ADMIN_USERS_DELETE_REQUEST = 'ADMIN_USERS_DELETE_REQUEST'; -const ADMIN_USERS_DELETE_SUCCESS = 'ADMIN_USERS_DELETE_SUCCESS'; -const ADMIN_USERS_DELETE_FAIL = 'ADMIN_USERS_DELETE_FAIL'; +const ADMIN_USERS_DELETE_REQUEST = 'ADMIN_USERS_DELETE_REQUEST' as const; +const ADMIN_USERS_DELETE_SUCCESS = 'ADMIN_USERS_DELETE_SUCCESS' as const; +const ADMIN_USERS_DELETE_FAIL = 'ADMIN_USERS_DELETE_FAIL' as const; -const ADMIN_USERS_APPROVE_REQUEST = 'ADMIN_USERS_APPROVE_REQUEST'; -const ADMIN_USERS_APPROVE_SUCCESS = 'ADMIN_USERS_APPROVE_SUCCESS'; -const ADMIN_USERS_APPROVE_FAIL = 'ADMIN_USERS_APPROVE_FAIL'; +const ADMIN_USERS_APPROVE_REQUEST = 'ADMIN_USERS_APPROVE_REQUEST' as const; +const ADMIN_USERS_APPROVE_SUCCESS = 'ADMIN_USERS_APPROVE_SUCCESS' as const; +const ADMIN_USERS_APPROVE_FAIL = 'ADMIN_USERS_APPROVE_FAIL' as const; -const ADMIN_USERS_DEACTIVATE_REQUEST = 'ADMIN_USERS_DEACTIVATE_REQUEST'; -const ADMIN_USERS_DEACTIVATE_SUCCESS = 'ADMIN_USERS_DEACTIVATE_SUCCESS'; -const ADMIN_USERS_DEACTIVATE_FAIL = 'ADMIN_USERS_DEACTIVATE_FAIL'; +const ADMIN_USERS_DEACTIVATE_REQUEST = 'ADMIN_USERS_DEACTIVATE_REQUEST' as const; +const ADMIN_USERS_DEACTIVATE_SUCCESS = 'ADMIN_USERS_DEACTIVATE_SUCCESS' as const; +const ADMIN_USERS_DEACTIVATE_FAIL = 'ADMIN_USERS_DEACTIVATE_FAIL' as const; -const ADMIN_STATUS_DELETE_REQUEST = 'ADMIN_STATUS_DELETE_REQUEST'; -const ADMIN_STATUS_DELETE_SUCCESS = 'ADMIN_STATUS_DELETE_SUCCESS'; -const ADMIN_STATUS_DELETE_FAIL = 'ADMIN_STATUS_DELETE_FAIL'; +const ADMIN_STATUS_DELETE_REQUEST = 'ADMIN_STATUS_DELETE_REQUEST' as const; +const ADMIN_STATUS_DELETE_SUCCESS = 'ADMIN_STATUS_DELETE_SUCCESS' as const; +const ADMIN_STATUS_DELETE_FAIL = 'ADMIN_STATUS_DELETE_FAIL' as const; -const ADMIN_STATUS_TOGGLE_SENSITIVITY_REQUEST = 'ADMIN_STATUS_TOGGLE_SENSITIVITY_REQUEST'; -const ADMIN_STATUS_TOGGLE_SENSITIVITY_SUCCESS = 'ADMIN_STATUS_TOGGLE_SENSITIVITY_SUCCESS'; -const ADMIN_STATUS_TOGGLE_SENSITIVITY_FAIL = 'ADMIN_STATUS_TOGGLE_SENSITIVITY_FAIL'; +const ADMIN_STATUS_TOGGLE_SENSITIVITY_REQUEST = 'ADMIN_STATUS_TOGGLE_SENSITIVITY_REQUEST' as const; +const ADMIN_STATUS_TOGGLE_SENSITIVITY_SUCCESS = 'ADMIN_STATUS_TOGGLE_SENSITIVITY_SUCCESS' as const; +const ADMIN_STATUS_TOGGLE_SENSITIVITY_FAIL = 'ADMIN_STATUS_TOGGLE_SENSITIVITY_FAIL' as const; -const ADMIN_USERS_TAG_REQUEST = 'ADMIN_USERS_TAG_REQUEST'; -const ADMIN_USERS_TAG_SUCCESS = 'ADMIN_USERS_TAG_SUCCESS'; -const ADMIN_USERS_TAG_FAIL = 'ADMIN_USERS_TAG_FAIL'; +const ADMIN_USERS_TAG_REQUEST = 'ADMIN_USERS_TAG_REQUEST' as const; +const ADMIN_USERS_TAG_SUCCESS = 'ADMIN_USERS_TAG_SUCCESS' as const; +const ADMIN_USERS_TAG_FAIL = 'ADMIN_USERS_TAG_FAIL' as const; -const ADMIN_USERS_UNTAG_REQUEST = 'ADMIN_USERS_UNTAG_REQUEST'; -const ADMIN_USERS_UNTAG_SUCCESS = 'ADMIN_USERS_UNTAG_SUCCESS'; -const ADMIN_USERS_UNTAG_FAIL = 'ADMIN_USERS_UNTAG_FAIL'; +const ADMIN_USERS_UNTAG_REQUEST = 'ADMIN_USERS_UNTAG_REQUEST' as const; +const ADMIN_USERS_UNTAG_SUCCESS = 'ADMIN_USERS_UNTAG_SUCCESS' as const; +const ADMIN_USERS_UNTAG_FAIL = 'ADMIN_USERS_UNTAG_FAIL' as const; -const ADMIN_ADD_PERMISSION_GROUP_REQUEST = 'ADMIN_ADD_PERMISSION_GROUP_REQUEST'; -const ADMIN_ADD_PERMISSION_GROUP_SUCCESS = 'ADMIN_ADD_PERMISSION_GROUP_SUCCESS'; -const ADMIN_ADD_PERMISSION_GROUP_FAIL = 'ADMIN_ADD_PERMISSION_GROUP_FAIL'; +const ADMIN_ADD_PERMISSION_GROUP_REQUEST = 'ADMIN_ADD_PERMISSION_GROUP_REQUEST' as const; +const ADMIN_ADD_PERMISSION_GROUP_SUCCESS = 'ADMIN_ADD_PERMISSION_GROUP_SUCCESS' as const; +const ADMIN_ADD_PERMISSION_GROUP_FAIL = 'ADMIN_ADD_PERMISSION_GROUP_FAIL' as const; -const ADMIN_REMOVE_PERMISSION_GROUP_REQUEST = 'ADMIN_REMOVE_PERMISSION_GROUP_REQUEST'; -const ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS = 'ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS'; -const ADMIN_REMOVE_PERMISSION_GROUP_FAIL = 'ADMIN_REMOVE_PERMISSION_GROUP_FAIL'; +const ADMIN_REMOVE_PERMISSION_GROUP_REQUEST = 'ADMIN_REMOVE_PERMISSION_GROUP_REQUEST' as const; +const ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS = 'ADMIN_REMOVE_PERMISSION_GROUP_SUCCESS' as const; +const ADMIN_REMOVE_PERMISSION_GROUP_FAIL = 'ADMIN_REMOVE_PERMISSION_GROUP_FAIL' as const; -const ADMIN_USER_INDEX_EXPAND_FAIL = 'ADMIN_USER_INDEX_EXPAND_FAIL'; -const ADMIN_USER_INDEX_EXPAND_REQUEST = 'ADMIN_USER_INDEX_EXPAND_REQUEST'; -const ADMIN_USER_INDEX_EXPAND_SUCCESS = 'ADMIN_USER_INDEX_EXPAND_SUCCESS'; +const ADMIN_USER_INDEX_EXPAND_FAIL = 'ADMIN_USER_INDEX_EXPAND_FAIL' as const; +const ADMIN_USER_INDEX_EXPAND_REQUEST = 'ADMIN_USER_INDEX_EXPAND_REQUEST' as const; +const ADMIN_USER_INDEX_EXPAND_SUCCESS = 'ADMIN_USER_INDEX_EXPAND_SUCCESS' as const; -const ADMIN_USER_INDEX_FETCH_FAIL = 'ADMIN_USER_INDEX_FETCH_FAIL'; -const ADMIN_USER_INDEX_FETCH_REQUEST = 'ADMIN_USER_INDEX_FETCH_REQUEST'; -const ADMIN_USER_INDEX_FETCH_SUCCESS = 'ADMIN_USER_INDEX_FETCH_SUCCESS'; +const ADMIN_USER_INDEX_FETCH_FAIL = 'ADMIN_USER_INDEX_FETCH_FAIL' as const; +const ADMIN_USER_INDEX_FETCH_REQUEST = 'ADMIN_USER_INDEX_FETCH_REQUEST' as const; +const ADMIN_USER_INDEX_FETCH_SUCCESS = 'ADMIN_USER_INDEX_FETCH_SUCCESS' as const; -const ADMIN_USER_INDEX_QUERY_SET = 'ADMIN_USER_INDEX_QUERY_SET'; +const ADMIN_USER_INDEX_QUERY_SET = 'ADMIN_USER_INDEX_QUERY_SET' as const; const fetchConfig = () => (dispatch: AppDispatch, getState: () => RootState) => { @@ -99,7 +98,7 @@ const updateConfig = (configs: Record[]) => }; const updateSoapboxConfig = (data: Record) => - (dispatch: AppDispatch, _getState: () => RootState) => { + (dispatch: AppDispatch) => { const params = [{ group: ':pleroma', key: ':frontend_configurations', @@ -143,8 +142,7 @@ const fetchReports = (params: Record = {}) => (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); - const instance = state.instance; - const features = getFeatures(instance); + const features = state.auth.client.features; dispatch({ type: ADMIN_REPORTS_FETCH_REQUEST, params }); @@ -190,8 +188,7 @@ const patchReports = (ids: string[], reportState: string) => (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); - const instance = state.instance; - const features = getFeatures(instance); + const features = state.auth.client.features; const reports = ids.map(id => ({ id, state: reportState })); @@ -254,8 +251,7 @@ const fetchUsers = (filters: string[] = [], page = 1, query?: string | null, pag (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); - const instance = state.instance; - const features = getFeatures(instance); + const features = state.auth.client.features; dispatch({ type: ADMIN_USERS_FETCH_REQUEST, filters, page, pageSize }); @@ -301,8 +297,7 @@ const deactivateUsers = (accountIds: string[], reportId?: string) => (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); - const instance = state.instance; - const features = getFeatures(instance); + const features = state.auth.client.features; dispatch({ type: ADMIN_USERS_DEACTIVATE_REQUEST, accountIds }); @@ -355,8 +350,7 @@ const approveUsers = (accountIds: string[]) => (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); - const instance = state.instance; - const features = getFeatures(instance); + const features = state.auth.client.features; dispatch({ type: ADMIN_USERS_APPROVE_REQUEST, accountIds }); @@ -367,26 +361,26 @@ const approveUsers = (accountIds: string[]) => } }; -const deleteStatus = (id: string) => +const deleteStatus = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - dispatch({ type: ADMIN_STATUS_DELETE_REQUEST, id }); - return getClient(getState).request(`/api/v1/pleroma/admin/statuses/${id}`, { method: 'DELETE' }) + dispatch({ type: ADMIN_STATUS_DELETE_REQUEST, statusId }); + return getClient(getState).request(`/api/v1/pleroma/admin/statuses/${statusId}`, { method: 'DELETE' }) .then(() => { - dispatch({ type: ADMIN_STATUS_DELETE_SUCCESS, id }); + dispatch({ type: ADMIN_STATUS_DELETE_SUCCESS, statusId }); }).catch(error => { - dispatch({ type: ADMIN_STATUS_DELETE_FAIL, error, id }); + dispatch({ type: ADMIN_STATUS_DELETE_FAIL, error, statusId }); }); }; -const toggleStatusSensitivity = (id: string, sensitive: boolean) => +const toggleStatusSensitivity = (statusId: string, sensitive: boolean) => (dispatch: AppDispatch, getState: () => RootState) => { - dispatch({ type: ADMIN_STATUS_TOGGLE_SENSITIVITY_REQUEST, id }); - return getClient(getState).request(`/api/v1/pleroma/admin/statuses/${id}`, { + dispatch({ type: ADMIN_STATUS_TOGGLE_SENSITIVITY_REQUEST, statusId }); + return getClient(getState).request(`/api/v1/pleroma/admin/statuses/${statusId}`, { method: 'PUT', body: { sensitive: !sensitive }, }).then(() => { - dispatch({ type: ADMIN_STATUS_TOGGLE_SENSITIVITY_SUCCESS, id }); + dispatch({ type: ADMIN_STATUS_TOGGLE_SENSITIVITY_SUCCESS, statusId }); }).catch(error => { - dispatch({ type: ADMIN_STATUS_TOGGLE_SENSITIVITY_FAIL, error, id }); + dispatch({ type: ADMIN_STATUS_TOGGLE_SENSITIVITY_FAIL, error, statusId }); }); }; @@ -408,11 +402,6 @@ const untagUsers = (accountIds: string[], tags: string[]) => (dispatch: AppDispatch, getState: () => RootState) => { const nicknames = accountIdsToAccts(getState(), accountIds); - // Legacy: allow removing legacy 'donor' tags. - if (tags.includes('badge:donor')) { - tags = [...tags, 'donor']; - } - dispatch({ type: ADMIN_USERS_UNTAG_REQUEST, accountIds, tags }); return getClient(getState).request('/api/v1/pleroma/admin/users/tag', { method: 'DELETE', @@ -430,8 +419,8 @@ const setTags = (accountId: string, oldTags: string[], newTags: string[]) => async(dispatch: AppDispatch) => { const diff = getTagDiff(oldTags, newTags); - await dispatch(tagUsers([accountId], diff.added)); - await dispatch(untagUsers([accountId], diff.removed)); + if (diff.added.length) await dispatch(tagUsers([accountId], diff.added)); + if (diff.removed.length) await dispatch(untagUsers([accountId], diff.removed)); }; /** Synchronizes badges to the backend. */ @@ -546,6 +535,18 @@ const expandUserIndex = () => }); }; +const getSubscribersCsv = () => + (dispatch: any, getState: () => RootState) => + getClient(getState).request('/api/v1/pleroma/admin/email_list/subscribers.csv', { contentType: '' }); + +const getUnsubscribersCsv = () => + (dispatch: any, getState: () => RootState) => + getClient(getState).request('/api/v1/pleroma/admin/email_list/unsubscribers.csv', { contentType: '' }); + +const getCombinedCsv = () => + (dispatch: any, getState: () => RootState) => + getClient(getState).request('/api/v1/pleroma/admin/email_list/combined.csv', { contentType: '' }); + export { ADMIN_CONFIG_FETCH_REQUEST, ADMIN_CONFIG_FETCH_SUCCESS, @@ -620,4 +621,7 @@ export { setUserIndexQuery, fetchUserIndex, expandUserIndex, + getSubscribersCsv, + getUnsubscribersCsv, + getCombinedCsv, }; diff --git a/src/actions/aliases.ts b/src/actions/aliases.ts index ac40cda95..837f8d4f8 100644 --- a/src/actions/aliases.ts +++ b/src/actions/aliases.ts @@ -7,24 +7,25 @@ import { getClient } from '../api'; import { importFetchedAccounts } from './importer'; -import type { Account } from 'soapbox/schemas'; +import type { Account as BaseAccount } from 'pl-api'; +import type { Account } from 'soapbox/normalizers'; import type { AppDispatch, RootState } from 'soapbox/store'; -const ALIASES_FETCH_REQUEST = 'ALIASES_FETCH_REQUEST'; -const ALIASES_FETCH_SUCCESS = 'ALIASES_FETCH_SUCCESS'; -const ALIASES_FETCH_FAIL = 'ALIASES_FETCH_FAIL'; +const ALIASES_FETCH_REQUEST = 'ALIASES_FETCH_REQUEST' as const; +const ALIASES_FETCH_SUCCESS = 'ALIASES_FETCH_SUCCESS' as const; +const ALIASES_FETCH_FAIL = 'ALIASES_FETCH_FAIL' as const; -const ALIASES_SUGGESTIONS_CHANGE = 'ALIASES_SUGGESTIONS_CHANGE'; -const ALIASES_SUGGESTIONS_READY = 'ALIASES_SUGGESTIONS_READY'; -const ALIASES_SUGGESTIONS_CLEAR = 'ALIASES_SUGGESTIONS_CLEAR'; +const ALIASES_SUGGESTIONS_CHANGE = 'ALIASES_SUGGESTIONS_CHANGE' as const; +const ALIASES_SUGGESTIONS_READY = 'ALIASES_SUGGESTIONS_READY' as const; +const ALIASES_SUGGESTIONS_CLEAR = 'ALIASES_SUGGESTIONS_CLEAR' as const; -const ALIASES_ADD_REQUEST = 'ALIASES_ADD_REQUEST'; -const ALIASES_ADD_SUCCESS = 'ALIASES_ADD_SUCCESS'; -const ALIASES_ADD_FAIL = 'ALIASES_ADD_FAIL'; +const ALIASES_ADD_REQUEST = 'ALIASES_ADD_REQUEST' as const; +const ALIASES_ADD_SUCCESS = 'ALIASES_ADD_SUCCESS' as const; +const ALIASES_ADD_FAIL = 'ALIASES_ADD_FAIL' as const; -const ALIASES_REMOVE_REQUEST = 'ALIASES_REMOVE_REQUEST'; -const ALIASES_REMOVE_SUCCESS = 'ALIASES_REMOVE_SUCCESS'; -const ALIASES_REMOVE_FAIL = 'ALIASES_REMOVE_FAIL'; +const ALIASES_REMOVE_REQUEST = 'ALIASES_REMOVE_REQUEST' as const; +const ALIASES_REMOVE_SUCCESS = 'ALIASES_REMOVE_SUCCESS' as const; +const ALIASES_REMOVE_FAIL = 'ALIASES_REMOVE_FAIL' as const; const messages = defineMessages({ createSuccess: { id: 'aliases.success.add', defaultMessage: 'Account alias created successfully' }, @@ -46,7 +47,7 @@ const fetchAliasesRequest = () => ({ type: ALIASES_FETCH_REQUEST, }); -const fetchAliasesSuccess = (aliases: unknown[]) => ({ +const fetchAliasesSuccess = (aliases: Array) => ({ type: ALIASES_FETCH_SUCCESS, value: aliases, }); @@ -67,7 +68,7 @@ const fetchAliasesSuggestions = (q: string) => }).catch(error => toast.showAlertForError(error)); }; -const fetchAliasesSuggestionsReady = (query: string, accounts: unknown[]) => ({ +const fetchAliasesSuggestionsReady = (query: string, accounts: BaseAccount[]) => ({ type: ALIASES_SUGGESTIONS_READY, query, accounts, @@ -133,6 +134,20 @@ const removeFromAliasesFail = (error: unknown) => ({ error, }); +type AliasesAction = + ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType; + export { ALIASES_FETCH_REQUEST, ALIASES_FETCH_SUCCESS, @@ -162,4 +177,5 @@ export { removeFromAliasesRequest, removeFromAliasesSuccess, removeFromAliasesFail, + type AliasesAction, }; diff --git a/src/actions/apps.ts b/src/actions/apps.ts index 28bb785d5..26f08f984 100644 --- a/src/actions/apps.ts +++ b/src/actions/apps.ts @@ -12,9 +12,9 @@ import * as BuildConfig from 'soapbox/build-config'; import type { AnyAction } from 'redux'; -const APP_CREATE_REQUEST = 'APP_CREATE_REQUEST'; -const APP_CREATE_SUCCESS = 'APP_CREATE_SUCCESS'; -const APP_CREATE_FAIL = 'APP_CREATE_FAIL'; +const APP_CREATE_REQUEST = 'APP_CREATE_REQUEST' as const; +const APP_CREATE_SUCCESS = 'APP_CREATE_SUCCESS' as const; +const APP_CREATE_FAIL = 'APP_CREATE_FAIL' as const; const createApp = (params: CreateApplicationParams, baseURL?: string) => (dispatch: React.Dispatch) => { diff --git a/src/actions/auth.ts b/src/actions/auth.ts index 97b99f2a0..b4df7249e 100644 --- a/src/actions/auth.ts +++ b/src/actions/auth.ts @@ -6,7 +6,7 @@ * @see module:soapbox/actions/oauth * @see module:soapbox/actions/security */ -import { PlApiClient, type CreateAccountParams, type Token } from 'pl-api'; +import { credentialAccountSchema, PlApiClient, type CreateAccountParams, type Token } from 'pl-api'; import { defineMessages } from 'react-intl'; import { createAccount } from 'soapbox/actions/accounts'; @@ -33,20 +33,20 @@ import { importFetchedAccount } from './importer'; import type { AppDispatch, RootState } from 'soapbox/store'; -const SWITCH_ACCOUNT = 'SWITCH_ACCOUNT'; +const SWITCH_ACCOUNT = 'SWITCH_ACCOUNT' as const; -const AUTH_APP_CREATED = 'AUTH_APP_CREATED'; -const AUTH_APP_AUTHORIZED = 'AUTH_APP_AUTHORIZED'; -const AUTH_LOGGED_IN = 'AUTH_LOGGED_IN'; -const AUTH_LOGGED_OUT = 'AUTH_LOGGED_OUT'; +const AUTH_APP_CREATED = 'AUTH_APP_CREATED' as const; +const AUTH_APP_AUTHORIZED = 'AUTH_APP_AUTHORIZED' as const; +const AUTH_LOGGED_IN = 'AUTH_LOGGED_IN' as const; +const AUTH_LOGGED_OUT = 'AUTH_LOGGED_OUT' as const; -const VERIFY_CREDENTIALS_REQUEST = 'VERIFY_CREDENTIALS_REQUEST'; -const VERIFY_CREDENTIALS_SUCCESS = 'VERIFY_CREDENTIALS_SUCCESS'; -const VERIFY_CREDENTIALS_FAIL = 'VERIFY_CREDENTIALS_FAIL'; +const VERIFY_CREDENTIALS_REQUEST = 'VERIFY_CREDENTIALS_REQUEST' as const; +const VERIFY_CREDENTIALS_SUCCESS = 'VERIFY_CREDENTIALS_SUCCESS' as const; +const VERIFY_CREDENTIALS_FAIL = 'VERIFY_CREDENTIALS_FAIL' as const; -const AUTH_ACCOUNT_REMEMBER_REQUEST = 'AUTH_ACCOUNT_REMEMBER_REQUEST'; -const AUTH_ACCOUNT_REMEMBER_SUCCESS = 'AUTH_ACCOUNT_REMEMBER_SUCCESS'; -const AUTH_ACCOUNT_REMEMBER_FAIL = 'AUTH_ACCOUNT_REMEMBER_FAIL'; +const AUTH_ACCOUNT_REMEMBER_REQUEST = 'AUTH_ACCOUNT_REMEMBER_REQUEST' as const; +const AUTH_ACCOUNT_REMEMBER_SUCCESS = 'AUTH_ACCOUNT_REMEMBER_SUCCESS' as const; +const AUTH_ACCOUNT_REMEMBER_FAIL = 'AUTH_ACCOUNT_REMEMBER_FAIL' as const; const customApp = custom('app'); @@ -93,8 +93,8 @@ const createAppToken = () => const app = getState().auth.app; const params = { - client_id: app.client_id!, - client_secret: app.client_secret!, + client_id: app?.client_id!, + client_secret: app?.client_secret!, redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', grant_type: 'client_credentials', scope: getScopes(getState()), @@ -110,8 +110,8 @@ const createUserToken = (username: string, password: string) => const app = getState().auth.app; const params = { - client_id: app.client_id!, - client_secret: app.client_secret!, + client_id: app?.client_id!, + client_secret: app?.client_secret!, redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', grant_type: 'password', username: username, @@ -128,19 +128,16 @@ const otpVerify = (code: string, mfa_token: string) => const state = getState(); const app = state.auth.app; const baseUrl = parseBaseURL(state.me) || BuildConfig.BACKEND_URL; - const client = new PlApiClient(baseUrl, app.access_token!, { fetchInstance: false }); - return client.request('/oauth/mfa/challenge', { - method: 'POST', - body: { - client_id: app.client_id, - client_secret: app.client_secret, - mfa_token: mfa_token, - code: code, - challenge_type: 'totp', - redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', - scope: getScopes(getState()), - }, - }).then(({ json: token }) => dispatch(authLoggedIn(token))); + const client = new PlApiClient(baseUrl, undefined, { fetchInstance: false }); + return client.oauth.mfaChallenge({ + client_id: app?.client_id!, + client_secret: app?.client_secret!, + mfa_token: mfa_token, + code: code, + challenge_type: 'totp', + // redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', + // scope: getScopes(getState()), + }).then((token) => dispatch(authLoggedIn(token))); }; const verifyCredentials = (token: string, accountUrl?: string) => @@ -160,10 +157,11 @@ const verifyCredentials = (token: string, accountUrl?: string) => if (error?.response?.status === 403 && error?.response?.json?.id) { // The user is waitlisted const account = error.response.json; - dispatch(importFetchedAccount(account)); - dispatch({ type: VERIFY_CREDENTIALS_SUCCESS, token, account }); - if (account.id === getState().me) dispatch(fetchMeSuccess(account)); - return account; + const parsedAccount = credentialAccountSchema.parse(error.response.json); + dispatch(importFetchedAccount(parsedAccount)); + dispatch({ type: VERIFY_CREDENTIALS_SUCCESS, token, account: parsedAccount }); + if (account.id === getState().me) dispatch(fetchMeSuccess(parsedAccount)); + return parsedAccount; } else { if (getState().me === null) dispatch(fetchMeFail(error)); dispatch({ type: VERIFY_CREDENTIALS_FAIL, token, error }); @@ -205,9 +203,6 @@ const logIn = (username: string, password: string) => throw error; }); -const deleteSession = () => - (dispatch: AppDispatch, getState: () => RootState) => getClient(getState).request('/api/sign_out', { method: 'DELETE' }); - const logOut = () => (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); @@ -217,8 +212,8 @@ const logOut = () => if (!account) return dispatch(noOp); const params = { - client_id: state.auth.app.client_id!, - client_secret: state.auth.app.client_secret!, + client_id: state.auth.app?.client_id!, + client_secret: state.auth.app?.client_secret!, token: state.auth.users.get(account.url)!.access_token, }; @@ -272,7 +267,7 @@ const register = (params: CreateAccountParams) => }; const fetchCaptcha = () => - (_dispatch: AppDispatch, getState: () => RootState) => getClient(getState).request('/api/pleroma/captcha'); + (_dispatch: AppDispatch, getState: () => RootState) => getClient(getState).oauth.getCaptcha(); const authLoggedIn = (token: Token) => (dispatch: AppDispatch) => { @@ -298,7 +293,6 @@ export { rememberAuthAccount, loadCredentials, logIn, - deleteSession, logOut, switchAccount, fetchOwnAccounts, diff --git a/src/actions/backups.ts b/src/actions/backups.ts index 6c0fac0b2..9651f50b9 100644 --- a/src/actions/backups.ts +++ b/src/actions/backups.ts @@ -2,13 +2,13 @@ import { getClient } from '../api'; import type { AppDispatch, RootState } from 'soapbox/store'; -const BACKUPS_FETCH_REQUEST = 'BACKUPS_FETCH_REQUEST'; -const BACKUPS_FETCH_SUCCESS = 'BACKUPS_FETCH_SUCCESS'; -const BACKUPS_FETCH_FAIL = 'BACKUPS_FETCH_FAIL'; +const BACKUPS_FETCH_REQUEST = 'BACKUPS_FETCH_REQUEST' as const; +const BACKUPS_FETCH_SUCCESS = 'BACKUPS_FETCH_SUCCESS' as const; +const BACKUPS_FETCH_FAIL = 'BACKUPS_FETCH_FAIL' as const; -const BACKUPS_CREATE_REQUEST = 'BACKUPS_CREATE_REQUEST'; -const BACKUPS_CREATE_SUCCESS = 'BACKUPS_CREATE_SUCCESS'; -const BACKUPS_CREATE_FAIL = 'BACKUPS_CREATE_FAIL'; +const BACKUPS_CREATE_REQUEST = 'BACKUPS_CREATE_REQUEST' as const; +const BACKUPS_CREATE_SUCCESS = 'BACKUPS_CREATE_SUCCESS' as const; +const BACKUPS_CREATE_FAIL = 'BACKUPS_CREATE_FAIL' as const; const fetchBackups = () => (dispatch: AppDispatch, getState: () => RootState) => { diff --git a/src/actions/bookmarks.ts b/src/actions/bookmarks.ts index 930512196..bef80aedd 100644 --- a/src/actions/bookmarks.ts +++ b/src/actions/bookmarks.ts @@ -4,15 +4,14 @@ import { importFetchedStatuses } from './importer'; import type { PaginatedResponse, Status } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; -const BOOKMARKED_STATUSES_FETCH_REQUEST = 'BOOKMARKED_STATUSES_FETCH_REQUEST'; -const BOOKMARKED_STATUSES_FETCH_SUCCESS = 'BOOKMARKED_STATUSES_FETCH_SUCCESS'; -const BOOKMARKED_STATUSES_FETCH_FAIL = 'BOOKMARKED_STATUSES_FETCH_FAIL'; +const BOOKMARKED_STATUSES_FETCH_REQUEST = 'BOOKMARKED_STATUSES_FETCH_REQUEST' as const; +const BOOKMARKED_STATUSES_FETCH_SUCCESS = 'BOOKMARKED_STATUSES_FETCH_SUCCESS' as const; +const BOOKMARKED_STATUSES_FETCH_FAIL = 'BOOKMARKED_STATUSES_FETCH_FAIL' as const; -const BOOKMARKED_STATUSES_EXPAND_REQUEST = 'BOOKMARKED_STATUSES_EXPAND_REQUEST'; -const BOOKMARKED_STATUSES_EXPAND_SUCCESS = 'BOOKMARKED_STATUSES_EXPAND_SUCCESS'; -const BOOKMARKED_STATUSES_EXPAND_FAIL = 'BOOKMARKED_STATUSES_EXPAND_FAIL'; +const BOOKMARKED_STATUSES_EXPAND_REQUEST = 'BOOKMARKED_STATUSES_EXPAND_REQUEST' as const; +const BOOKMARKED_STATUSES_EXPAND_SUCCESS = 'BOOKMARKED_STATUSES_EXPAND_SUCCESS' as const; +const BOOKMARKED_STATUSES_EXPAND_FAIL = 'BOOKMARKED_STATUSES_EXPAND_FAIL' as const; const noOp = () => new Promise(f => f(undefined)); @@ -37,7 +36,7 @@ const fetchBookmarkedStatusesRequest = (folderId?: string) => ({ folderId, }); -const fetchBookmarkedStatusesSuccess = (statuses: APIEntity[], next: (() => Promise>) | null, folderId?: string) => ({ +const fetchBookmarkedStatusesSuccess = (statuses: Array, next: (() => Promise>) | null, folderId?: string) => ({ type: BOOKMARKED_STATUSES_FETCH_SUCCESS, statuses, next, @@ -74,7 +73,7 @@ const expandBookmarkedStatusesRequest = (folderId?: string) => ({ folderId, }); -const expandBookmarkedStatusesSuccess = (statuses: APIEntity[], next: (() => Promise>) | null, folderId?: string) => ({ +const expandBookmarkedStatusesSuccess = (statuses: Array, next: (() => Promise>) | null, folderId?: string) => ({ type: BOOKMARKED_STATUSES_EXPAND_SUCCESS, statuses, next, @@ -87,6 +86,15 @@ const expandBookmarkedStatusesFail = (error: unknown, folderId?: string) => ({ folderId, }); +type BookmarksAction = + ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType; + export { BOOKMARKED_STATUSES_FETCH_REQUEST, BOOKMARKED_STATUSES_FETCH_SUCCESS, @@ -102,4 +110,5 @@ export { expandBookmarkedStatusesRequest, expandBookmarkedStatusesSuccess, expandBookmarkedStatusesFail, + type BookmarksAction, }; diff --git a/src/actions/circle.ts b/src/actions/circle.ts index 9f1ebd6ef..178f079a5 100644 --- a/src/actions/circle.ts +++ b/src/actions/circle.ts @@ -26,9 +26,9 @@ const processCircle = (setProgress: (progress: { const interactions: Record = {}; - const initInteraction = (id: string) => { - if (interactions[id]) return; - interactions[id] = { + const initInteraction = (accountId: string) => { + if (interactions[accountId]) return; + interactions[accountId] = { acct: '', replies: 0, reblogs: 0, diff --git a/src/actions/compose.test.ts b/src/actions/compose.test.ts index c2141cbb0..74fbde610 100644 --- a/src/actions/compose.test.ts +++ b/src/actions/compose.test.ts @@ -45,8 +45,8 @@ describe('uploadCompose()', () => { } as unknown as IntlShape; const expectedActions = [ - { type: 'COMPOSE_UPLOAD_REQUEST', id: 'home', skipLoading: true }, - { type: 'COMPOSE_UPLOAD_FAIL', id: 'home', error: true, skipLoading: true }, + { type: 'COMPOSE_UPLOAD_REQUEST', id: 'home' }, + { type: 'COMPOSE_UPLOAD_FAIL', id: 'home', error: true }, ]; await store.dispatch(uploadCompose('home', files, mockIntl)); @@ -91,8 +91,8 @@ describe('uploadCompose()', () => { } as unknown as IntlShape; const expectedActions = [ - { type: 'COMPOSE_UPLOAD_REQUEST', id: 'home', skipLoading: true }, - { type: 'COMPOSE_UPLOAD_FAIL', id: 'home', error: true, skipLoading: true }, + { type: 'COMPOSE_UPLOAD_REQUEST', id: 'home' }, + { type: 'COMPOSE_UPLOAD_FAIL', id: 'home', error: true }, ]; await store.dispatch(uploadCompose('home', files, mockIntl)); diff --git a/src/actions/compose.ts b/src/actions/compose.ts index 1f6dea9ba..470ad8f88 100644 --- a/src/actions/compose.ts +++ b/src/actions/compose.ts @@ -9,7 +9,6 @@ import { selectAccount, selectOwnAccount, makeGetAccount } from 'soapbox/selecto import { tagHistory } from 'soapbox/settings'; import toast from 'soapbox/toast'; import { isLoggedIn } from 'soapbox/utils/auth'; -import { getFeatures, parseVersion } from 'soapbox/utils/features'; import { chooseEmoji } from './emojis'; import { importFetchedAccounts } from './importer'; @@ -20,12 +19,11 @@ import { getSettings } from './settings'; import { createStatus } from './statuses'; import type { EditorState } from 'lexical'; -import type { CreateStatusParams, Tag } from 'pl-api'; +import type { Account as BaseAccount, BackendVersion, CreateStatusParams, Group, MediaAttachment, Status as BaseStatus, Tag, Poll } from 'pl-api'; import type { AutoSuggestion } from 'soapbox/components/autosuggest-input'; import type { Emoji } from 'soapbox/features/emoji'; -import type { Account, Group } from 'soapbox/schemas'; +import type { Account, Status } from 'soapbox/normalizers'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity, Status } from 'soapbox/types/entities'; import type { History } from 'soapbox/types/history'; let cancelFetchComposeSuggestions = new AbortController(); @@ -110,32 +108,34 @@ const messages = defineMessages({ interface ComposeSetStatusAction { type: typeof COMPOSE_SET_STATUS; - id: string; - status: Status; + composeId: string; + status: Pick; + poll?: Poll; rawText: string; explicitAddressing: boolean; spoilerText?: string; contentType?: string | false; - v: ReturnType; + v: BackendVersion; withRedraft?: boolean; draftId?: string; editorState?: string | null; } -const setComposeToStatus = (status: Status, rawText: string, spoilerText?: string, contentType?: string | false, withRedraft?: boolean, draftId?: string, editorState?: string | null) => +const setComposeToStatus = (status: ComposeSetStatusAction['status'], poll: Poll, rawText: string, spoilerText?: string, contentType?: string | false, withRedraft?: boolean, draftId?: string, editorState?: string | null) => (dispatch: AppDispatch, getState: () => RootState) => { - const { instance } = getState(); - const { explicitAddressing } = getFeatures(instance); + const client = getClient(getState); + const { createStatusExplicitAddressing: explicitAddressing, version: v } = client.features; const action: ComposeSetStatusAction = { type: COMPOSE_SET_STATUS, - id: 'compose-modal', + composeId: 'compose-modal', status, + poll, rawText, explicitAddressing, spoilerText, contentType, - v: parseVersion(instance.version), + v, withRedraft, draftId, editorState, @@ -146,25 +146,28 @@ const setComposeToStatus = (status: Status, rawText: string, spoilerText?: strin const changeCompose = (composeId: string, text: string) => ({ type: COMPOSE_CHANGE, - id: composeId, + composeId, text: text, }); interface ComposeReplyAction { type: typeof COMPOSE_REPLY; - id: string; - status: Status; - account: Account; + composeId: string; + status: Pick; + account: Pick; explicitAddressing: boolean; preserveSpoilers: boolean; - rebloggedBy?: Account; + rebloggedBy?: Pick; } -const replyCompose = (status: Status, rebloggedBy?: Account) => +const replyCompose = ( + status: ComposeReplyAction['status'], + rebloggedBy?: ComposeReplyAction['rebloggedBy'], +) => (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); - const instance = state.instance; - const { explicitAddressing } = getFeatures(instance); + const client = getClient(state); + const { createStatusExplicitAddressing: explicitAddressing } = client.features; const preserveSpoilers = !!getSettings(state).get('preserveSpoilers'); const account = selectOwnAccount(state); @@ -172,7 +175,7 @@ const replyCompose = (status: Status, rebloggedBy?: Account) => const action: ComposeReplyAction = { type: COMPOSE_REPLY, - id: 'compose-modal', + composeId: 'compose-modal', status: status, account, explicitAddressing, @@ -186,26 +189,25 @@ const replyCompose = (status: Status, rebloggedBy?: Account) => const cancelReplyCompose = () => ({ type: COMPOSE_REPLY_CANCEL, - id: 'compose-modal', + composeId: 'compose-modal', }); interface ComposeQuoteAction { type: typeof COMPOSE_QUOTE; - id: string; - status: Status; - account: Account | undefined; + composeId: string; + status: Pick; + account: Pick | undefined; explicitAddressing: boolean; } -const quoteCompose = (status: Status) => +const quoteCompose = (status: ComposeQuoteAction['status']) => (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); - const instance = state.instance; - const { explicitAddressing } = getFeatures(instance); + const { createStatusExplicitAddressing: explicitAddressing } = state.auth.client.features; const action: ComposeQuoteAction = { type: COMPOSE_QUOTE, - id: 'compose-modal', + composeId: 'compose-modal', status: status, account: selectOwnAccount(state), explicitAddressing, @@ -217,10 +219,10 @@ const quoteCompose = (status: Status) => const cancelQuoteCompose = (composeId: string) => ({ type: COMPOSE_QUOTE_CANCEL, - id: composeId, + composeId, }); -const groupComposeModal = (group: Group) => +const groupComposeModal = (group: Pick) => (dispatch: AppDispatch, getState: () => RootState) => { const composeId = `group:${group.id}`; @@ -230,20 +232,20 @@ const groupComposeModal = (group: Group) => const resetCompose = (composeId = 'compose-modal') => ({ type: COMPOSE_RESET, - id: composeId, + composeId, }); interface ComposeMentionAction { type: typeof COMPOSE_MENTION; - id: string; - account: Account; + composeId: string; + account: Pick; } -const mentionCompose = (account: Account) => +const mentionCompose = (account: ComposeMentionAction['account']) => (dispatch: AppDispatch) => { const action: ComposeMentionAction = { type: COMPOSE_MENTION, - id: 'compose-modal', + composeId: 'compose-modal', account: account, }; @@ -253,15 +255,15 @@ const mentionCompose = (account: Account) => interface ComposeDirectAction { type: typeof COMPOSE_DIRECT; - id: string; - account: Account; + composeId: string; + account: Pick; } -const directCompose = (account: Account) => +const directCompose = (account: ComposeDirectAction['account']) => (dispatch: AppDispatch) => { const action: ComposeDirectAction = { type: COMPOSE_DIRECT, - id: 'compose-modal', + composeId: 'compose-modal', account, }; @@ -276,7 +278,7 @@ const directComposeById = (accountId: string) => const action: ComposeDirectAction = { type: COMPOSE_DIRECT, - id: 'compose-modal', + composeId: 'compose-modal', account, }; @@ -284,7 +286,7 @@ const directComposeById = (accountId: string) => dispatch(openModal('COMPOSE')); }; -const handleComposeSubmit = (dispatch: AppDispatch, getState: () => RootState, composeId: string, data: APIEntity, status: string, edit?: boolean) => { +const handleComposeSubmit = (dispatch: AppDispatch, getState: () => RootState, composeId: string, data: BaseStatus, status: string, edit?: boolean) => { if (!dispatch || !getState) return; const state = getState(); @@ -419,7 +421,7 @@ const submitCompose = (composeId: string, opts: SubmitComposeOpts = {}) => return dispatch(createStatus(params, idempotencyKey, statusId)).then((data) => { if (!statusId && data.visibility === 'direct' && getState().conversations.mounted <= 0 && history) { - history.push('/messages'); + history.push('/conversations'); } handleComposeSubmit(dispatch, getState, composeId, data, status, !!statusId); }).catch((error) => { @@ -429,12 +431,12 @@ const submitCompose = (composeId: string, opts: SubmitComposeOpts = {}) => const submitComposeRequest = (composeId: string) => ({ type: COMPOSE_SUBMIT_REQUEST, - id: composeId, + composeId, }); -const submitComposeSuccess = (composeId: string, status: APIEntity, accountUrl: string, draftId?: string | null) => ({ +const submitComposeSuccess = (composeId: string, status: BaseStatus, accountUrl: string, draftId?: string | null) => ({ type: COMPOSE_SUBMIT_SUCCESS, - id: composeId, + composeId, status: status, accountUrl, draftId, @@ -442,8 +444,8 @@ const submitComposeSuccess = (composeId: string, status: APIEntity, accountUrl: const submitComposeFail = (composeId: string, error: unknown) => ({ type: COMPOSE_SUBMIT_FAIL, - id: composeId, - error: error, + composeId, + error, }); const uploadCompose = (composeId: string, files: FileList, intl: IntlShape) => @@ -484,76 +486,70 @@ const uploadCompose = (composeId: string, files: FileList, intl: IntlShape) => const uploadComposeRequest = (composeId: string) => ({ type: COMPOSE_UPLOAD_REQUEST, - id: composeId, - skipLoading: true, + composeId, }); const uploadComposeProgress = (composeId: string, loaded: number, total: number) => ({ type: COMPOSE_UPLOAD_PROGRESS, - id: composeId, - loaded: loaded, - total: total, + composeId, + loaded, + total, }); -const uploadComposeSuccess = (composeId: string, media: APIEntity, file: File) => ({ +const uploadComposeSuccess = (composeId: string, media: MediaAttachment, file: File) => ({ type: COMPOSE_UPLOAD_SUCCESS, - id: composeId, - media: media, + composeId, + media, file, - skipLoading: true, }); const uploadComposeFail = (composeId: string, error: unknown) => ({ type: COMPOSE_UPLOAD_FAIL, - id: composeId, - error: error, - skipLoading: true, + composeId, + error, }); -const changeUploadCompose = (composeId: string, id: string, params: Record) => +const changeUploadCompose = (composeId: string, mediaId: string, params: Record) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; dispatch(changeUploadComposeRequest(composeId)); - dispatch(updateMedia(id, params)).then(response => { + dispatch(updateMedia(mediaId, params)).then(response => { dispatch(changeUploadComposeSuccess(composeId, response)); }).catch(error => { - dispatch(changeUploadComposeFail(composeId, id, error)); + dispatch(changeUploadComposeFail(composeId, mediaId, error)); }); }; const changeUploadComposeRequest = (composeId: string) => ({ type: COMPOSE_UPLOAD_CHANGE_REQUEST, - id: composeId, - skipLoading: true, + composeId, }); -const changeUploadComposeSuccess = (composeId: string, media: APIEntity) => ({ +const changeUploadComposeSuccess = (composeId: string, media: MediaAttachment) => ({ type: COMPOSE_UPLOAD_CHANGE_SUCCESS, - id: composeId, - media: media, - skipLoading: true, + composeId, + media, }); -const changeUploadComposeFail = (composeId: string, id: string, error: unknown) => ({ +const changeUploadComposeFail = (composeId: string, mediaId: string, error: unknown) => ({ type: COMPOSE_UPLOAD_CHANGE_FAIL, composeId, - id, - error: error, - skipLoading: true, + mediaId, + error, }); -const undoUploadCompose = (composeId: string, media_id: string) => ({ +const undoUploadCompose = (composeId: string, mediaId: string) => ({ type: COMPOSE_UPLOAD_UNDO, - id: composeId, - media_id: media_id, + composeId, + mediaId, }); const groupCompose = (composeId: string, groupId: string) => ({ type: COMPOSE_GROUP_POST, - id: composeId, - group_id: groupId, + composeId, + groupId, }); const clearComposeSuggestions = (composeId: string) => { @@ -563,7 +559,7 @@ const clearComposeSuggestions = (composeId: string) => { } return { type: COMPOSE_SUGGESTIONS_CLEAR, - id: composeId, + composeId, }; }; @@ -603,8 +599,7 @@ const fetchComposeSuggestionsTags = (dispatch: AppDispatch, getState: () => Root const state = getState(); - const instance = state.instance; - const { trends } = getFeatures(instance); + const { trends } = state.auth.client.features; if (trends) { const currentTrends = state.trends.items.toArray(); @@ -638,29 +633,29 @@ const fetchComposeSuggestions = (composeId: string, token: string) => interface ComposeSuggestionsReadyAction { type: typeof COMPOSE_SUGGESTIONS_READY; - id: string; + composeId: string; token: string; emojis?: Emoji[]; - accounts?: APIEntity[]; + accounts?: BaseAccount[]; } const readyComposeSuggestionsEmojis = (composeId: string, token: string, emojis: Emoji[]) => ({ type: COMPOSE_SUGGESTIONS_READY, - id: composeId, + composeId, token, emojis, }); -const readyComposeSuggestionsAccounts = (composeId: string, token: string, accounts: APIEntity[]) => ({ +const readyComposeSuggestionsAccounts = (composeId: string, token: string, accounts: BaseAccount[]) => ({ type: COMPOSE_SUGGESTIONS_READY, - id: composeId, + composeId, token, accounts, }); interface ComposeSuggestionSelectAction { type: typeof COMPOSE_SUGGESTION_SELECT; - id: string; + composeId: string; position: number; token: string | null; completion: string; @@ -686,7 +681,7 @@ const selectComposeSuggestion = (composeId: string, position: number, token: str const action: ComposeSuggestionSelectAction = { type: COMPOSE_SUGGESTION_SELECT, - id: composeId, + composeId, position: startPosition, token, completion, @@ -698,18 +693,18 @@ const selectComposeSuggestion = (composeId: string, position: number, token: str const updateSuggestionTags = (composeId: string, token: string, tags: Array) => ({ type: COMPOSE_SUGGESTION_TAGS_UPDATE, - id: composeId, + composeId, token, tags, }); const updateTagHistory = (composeId: string, tags: string[]) => ({ type: COMPOSE_TAG_HISTORY_UPDATE, - id: composeId, + composeId, tags, }); -const insertIntoTagHistory = (composeId: string, recognizedTags: APIEntity[], text: string) => +const insertIntoTagHistory = (composeId: string, recognizedTags: Array, text: string) => (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); const oldHistory = state.compose.get(composeId)!.tagHistory; @@ -729,54 +724,54 @@ const insertIntoTagHistory = (composeId: string, recognizedTags: APIEntity[], te const changeComposeSpoilerness = (composeId: string) => ({ type: COMPOSE_SPOILERNESS_CHANGE, - id: composeId, + composeId, }); const changeComposeContentType = (composeId: string, value: string) => ({ type: COMPOSE_TYPE_CHANGE, - id: composeId, + composeId, value, }); const changeComposeSpoilerText = (composeId: string, text: string) => ({ type: COMPOSE_SPOILER_TEXT_CHANGE, - id: composeId, + composeId, text, }); const changeComposeVisibility = (composeId: string, value: string) => ({ type: COMPOSE_VISIBILITY_CHANGE, - id: composeId, + composeId, value, }); const changeComposeLanguage = (composeId: string, value: Language | null) => ({ type: COMPOSE_LANGUAGE_CHANGE, - id: composeId, + composeId, value, }); const changeComposeModifiedLanguage = (composeId: string, value: Language | null) => ({ type: COMPOSE_MODIFIED_LANGUAGE_CHANGE, - id: composeId, + composeId, value, }); const addComposeLanguage = (composeId: string, value: Language) => ({ type: COMPOSE_LANGUAGE_ADD, - id: composeId, + composeId, value, }); const deleteComposeLanguage = (composeId: string, value: Language) => ({ type: COMPOSE_LANGUAGE_DELETE, - id: composeId, + composeId, value, }); const insertEmojiCompose = (composeId: string, position: number, emoji: Emoji, needsSpace: boolean) => ({ type: COMPOSE_EMOJI_INSERT, - id: composeId, + composeId, position, emoji, needsSpace, @@ -784,52 +779,52 @@ const insertEmojiCompose = (composeId: string, position: number, emoji: Emoji, n const addPoll = (composeId: string) => ({ type: COMPOSE_POLL_ADD, - id: composeId, + composeId, }); const removePoll = (composeId: string) => ({ type: COMPOSE_POLL_REMOVE, - id: composeId, + composeId, }); const addSchedule = (composeId: string) => ({ type: COMPOSE_SCHEDULE_ADD, - id: composeId, + composeId, }); const setSchedule = (composeId: string, date: Date) => ({ type: COMPOSE_SCHEDULE_SET, - id: composeId, + composeId, date: date, }); const removeSchedule = (composeId: string) => ({ type: COMPOSE_SCHEDULE_REMOVE, - id: composeId, + composeId, }); const addPollOption = (composeId: string, title: string) => ({ type: COMPOSE_POLL_OPTION_ADD, - id: composeId, + composeId, title, }); const changePollOption = (composeId: string, index: number, title: string) => ({ type: COMPOSE_POLL_OPTION_CHANGE, - id: composeId, + composeId, index, title, }); const removePollOption = (composeId: string, index: number) => ({ type: COMPOSE_POLL_OPTION_REMOVE, - id: composeId, + composeId, index, }); const changePollSettings = (composeId: string, expiresIn?: number, isMultiple?: boolean) => ({ type: COMPOSE_POLL_SETTINGS_CHANGE, - id: composeId, + composeId, expiresIn, isMultiple, }); @@ -843,7 +838,7 @@ const openComposeWithText = (composeId: string, text = '') => interface ComposeAddToMentionsAction { type: typeof COMPOSE_ADD_TO_MENTIONS; - id: string; + composeId: string; account: string; } @@ -855,7 +850,7 @@ const addToMentions = (composeId: string, accountId: string) => const action: ComposeAddToMentionsAction = { type: COMPOSE_ADD_TO_MENTIONS, - id: composeId, + composeId, account: account.acct, }; @@ -864,7 +859,7 @@ const addToMentions = (composeId: string, accountId: string) => interface ComposeRemoveFromMentionsAction { type: typeof COMPOSE_REMOVE_FROM_MENTIONS; - id: string; + composeId: string; account: string; } @@ -876,7 +871,7 @@ const removeFromMentions = (composeId: string, accountId: string) => const action: ComposeRemoveFromMentionsAction = { type: COMPOSE_REMOVE_FROM_MENTIONS, - id: composeId, + composeId, account: account.acct, }; @@ -885,21 +880,20 @@ const removeFromMentions = (composeId: string, accountId: string) => interface ComposeEventReplyAction { type: typeof COMPOSE_EVENT_REPLY; - id: string; - status: Status; - account: Account; + composeId: string; + status: Pick; + account: Pick; explicitAddressing: boolean; } -const eventDiscussionCompose = (composeId: string, status: Status) => +const eventDiscussionCompose = (composeId: string, status: ComposeEventReplyAction['status']) => (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); - const instance = state.instance; - const { explicitAddressing } = getFeatures(instance); + const { createStatusExplicitAddressing: explicitAddressing } = state.auth.client.features; return dispatch({ type: COMPOSE_EVENT_REPLY, - id: composeId, + composeId, status: status, account: selectOwnAccount(state), explicitAddressing, @@ -908,33 +902,33 @@ const eventDiscussionCompose = (composeId: string, status: Status) => const setEditorState = (composeId: string, editorState: EditorState | string | null, text?: string) => ({ type: COMPOSE_EDITOR_STATE_SET, - id: composeId, + composeId, editorState, text, }); const changeMediaOrder = (composeId: string, a: string, b: string) => ({ type: COMPOSE_CHANGE_MEDIA_ORDER, - id: composeId, + composeId, a, b, }); const addSuggestedQuote = (composeId: string, quoteId: string) => ({ type: COMPOSE_ADD_SUGGESTED_QUOTE, - id: composeId, + composeId, quoteId, }); const addSuggestedLanguage = (composeId: string, language: string) => ({ type: COMPOSE_ADD_SUGGESTED_LANGUAGE, - id: composeId, + composeId, language, }); const changeComposeFederated = (composeId: string) => ({ type: COMPOSE_FEDERATED_CHANGE, - id: composeId, + composeId, }); type ComposeAction = @@ -989,7 +983,7 @@ type ComposeAction = | ReturnType | ReturnType | ReturnType - | ReturnType + | ReturnType; export { COMPOSE_CHANGE, diff --git a/src/actions/conversations.ts b/src/actions/conversations.ts index 4de129724..6bb1d7714 100644 --- a/src/actions/conversations.ts +++ b/src/actions/conversations.ts @@ -8,67 +8,57 @@ import { importFetchedStatus, } from './importer'; -import type { Conversation, PaginatedResponse } from 'pl-api'; +import type { Account, Conversation, PaginatedResponse, Status } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; -const CONVERSATIONS_MOUNT = 'CONVERSATIONS_MOUNT'; -const CONVERSATIONS_UNMOUNT = 'CONVERSATIONS_UNMOUNT'; +const CONVERSATIONS_MOUNT = 'CONVERSATIONS_MOUNT' as const; +const CONVERSATIONS_UNMOUNT = 'CONVERSATIONS_UNMOUNT' as const; -const CONVERSATIONS_FETCH_REQUEST = 'CONVERSATIONS_FETCH_REQUEST'; -const CONVERSATIONS_FETCH_SUCCESS = 'CONVERSATIONS_FETCH_SUCCESS'; -const CONVERSATIONS_FETCH_FAIL = 'CONVERSATIONS_FETCH_FAIL'; -const CONVERSATIONS_UPDATE = 'CONVERSATIONS_UPDATE'; +const CONVERSATIONS_FETCH_REQUEST = 'CONVERSATIONS_FETCH_REQUEST' as const; +const CONVERSATIONS_FETCH_SUCCESS = 'CONVERSATIONS_FETCH_SUCCESS' as const; +const CONVERSATIONS_FETCH_FAIL = 'CONVERSATIONS_FETCH_FAIL' as const; +const CONVERSATIONS_UPDATE = 'CONVERSATIONS_UPDATE' as const; -const CONVERSATIONS_READ = 'CONVERSATIONS_READ'; +const CONVERSATIONS_READ = 'CONVERSATIONS_READ' as const; -const mountConversations = () => ({ - type: CONVERSATIONS_MOUNT, -}); +const mountConversations = () => ({ type: CONVERSATIONS_MOUNT }); -const unmountConversations = () => ({ - type: CONVERSATIONS_UNMOUNT, -}); +const unmountConversations = () => ({ type: CONVERSATIONS_UNMOUNT }); const markConversationRead = (conversationId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; dispatch({ type: CONVERSATIONS_READ, - id: conversationId, + conversationId, }); return getClient(getState).timelines.markConversationRead(conversationId); }; -const expandConversations = ({ maxId }: Record = {}) => (dispatch: AppDispatch, getState: () => RootState) => { +const expandConversations = (expand = true) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; + const state = getState(); dispatch(expandConversationsRequest()); - const params: Record = { max_id: maxId }; + const isLoadingRecent = !!state.conversations.next; - if (!maxId) { - params.since_id = getState().conversations.items.getIn([0, 'id']); - } + if (isLoadingRecent && !expand) return; - const isLoadingRecent = !!params.since_id; - - return getClient(getState).timelines.getConversations(params) + return (state.conversations.next?.() || getClient(state).timelines.getConversations()) .then(response => { - dispatch(importFetchedAccounts(response.items.reduce((aggr: Array, item: APIEntity) => aggr.concat(item.accounts), []))); - dispatch(importFetchedStatuses(response.items.map((item: Record) => item.last_status).filter((x?: APIEntity) => !!x))); + dispatch(importFetchedAccounts(response.items.reduce((aggr: Array, item) => aggr.concat(item.accounts), []))); + dispatch(importFetchedStatuses(response.items.map((item) => item.last_status).filter((x): x is Status => x !== null))); dispatch(expandConversationsSuccess(response.items, response.next, isLoadingRecent)); }) .catch(err => dispatch(expandConversationsFail(err))); }; -const expandConversationsRequest = () => ({ - type: CONVERSATIONS_FETCH_REQUEST, -}); +const expandConversationsRequest = () => ({ type: CONVERSATIONS_FETCH_REQUEST }); const expandConversationsSuccess = ( - conversations: APIEntity[], + conversations: Conversation[], next: (() => Promise>) | null, isLoadingRecent: boolean, ) => ({ @@ -83,7 +73,7 @@ const expandConversationsFail = (error: unknown) => ({ error, }); -const updateConversations = (conversation: APIEntity) => (dispatch: AppDispatch) => { +const updateConversations = (conversation: Conversation) => (dispatch: AppDispatch) => { dispatch(importFetchedAccounts(conversation.accounts)); if (conversation.last_status) { diff --git a/src/actions/custom-emojis.ts b/src/actions/custom-emojis.ts index fd40a9cbe..03d6965d8 100644 --- a/src/actions/custom-emojis.ts +++ b/src/actions/custom-emojis.ts @@ -1,11 +1,11 @@ import { getClient } from '../api'; +import type { CustomEmoji } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; -const CUSTOM_EMOJIS_FETCH_REQUEST = 'CUSTOM_EMOJIS_FETCH_REQUEST'; -const CUSTOM_EMOJIS_FETCH_SUCCESS = 'CUSTOM_EMOJIS_FETCH_SUCCESS'; -const CUSTOM_EMOJIS_FETCH_FAIL = 'CUSTOM_EMOJIS_FETCH_FAIL'; +const CUSTOM_EMOJIS_FETCH_REQUEST = 'CUSTOM_EMOJIS_FETCH_REQUEST' as const; +const CUSTOM_EMOJIS_FETCH_SUCCESS = 'CUSTOM_EMOJIS_FETCH_SUCCESS' as const; +const CUSTOM_EMOJIS_FETCH_FAIL = 'CUSTOM_EMOJIS_FETCH_FAIL' as const; const fetchCustomEmojis = () => (dispatch: AppDispatch, getState: () => RootState) => { @@ -23,21 +23,23 @@ const fetchCustomEmojis = () => const fetchCustomEmojisRequest = () => ({ type: CUSTOM_EMOJIS_FETCH_REQUEST, - skipLoading: true, }); -const fetchCustomEmojisSuccess = (custom_emojis: APIEntity[]) => ({ +const fetchCustomEmojisSuccess = (custom_emojis: Array) => ({ type: CUSTOM_EMOJIS_FETCH_SUCCESS, custom_emojis, - skipLoading: true, }); const fetchCustomEmojisFail = (error: unknown) => ({ type: CUSTOM_EMOJIS_FETCH_FAIL, error, - skipLoading: true, }); +type CustomEmojisAction = + ReturnType + | ReturnType + | ReturnType; + export { CUSTOM_EMOJIS_FETCH_REQUEST, CUSTOM_EMOJIS_FETCH_SUCCESS, @@ -46,4 +48,5 @@ export { fetchCustomEmojisRequest, fetchCustomEmojisSuccess, fetchCustomEmojisFail, + type CustomEmojisAction, }; diff --git a/src/actions/directory.ts b/src/actions/directory.ts index b02d1c493..01601fcda 100644 --- a/src/actions/directory.ts +++ b/src/actions/directory.ts @@ -3,25 +3,25 @@ import { getClient } from '../api'; import { fetchRelationships } from './accounts'; import { importFetchedAccounts } from './importer'; +import type { Account, ProfileDirectoryParams } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; -const DIRECTORY_FETCH_REQUEST = 'DIRECTORY_FETCH_REQUEST'; -const DIRECTORY_FETCH_SUCCESS = 'DIRECTORY_FETCH_SUCCESS'; -const DIRECTORY_FETCH_FAIL = 'DIRECTORY_FETCH_FAIL'; +const DIRECTORY_FETCH_REQUEST = 'DIRECTORY_FETCH_REQUEST' as const; +const DIRECTORY_FETCH_SUCCESS = 'DIRECTORY_FETCH_SUCCESS' as const; +const DIRECTORY_FETCH_FAIL = 'DIRECTORY_FETCH_FAIL' as const; -const DIRECTORY_EXPAND_REQUEST = 'DIRECTORY_EXPAND_REQUEST'; -const DIRECTORY_EXPAND_SUCCESS = 'DIRECTORY_EXPAND_SUCCESS'; -const DIRECTORY_EXPAND_FAIL = 'DIRECTORY_EXPAND_FAIL'; +const DIRECTORY_EXPAND_REQUEST = 'DIRECTORY_EXPAND_REQUEST' as const; +const DIRECTORY_EXPAND_SUCCESS = 'DIRECTORY_EXPAND_SUCCESS' as const; +const DIRECTORY_EXPAND_FAIL = 'DIRECTORY_EXPAND_FAIL' as const; -const fetchDirectory = (params: Record) => +const fetchDirectory = (params: ProfileDirectoryParams) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(fetchDirectoryRequest()); return getClient(getState()).instance.profileDirectory({ ...params, limit: 20 }).then((data) => { dispatch(importFetchedAccounts(data)); dispatch(fetchDirectorySuccess(data)); - dispatch(fetchRelationships(data.map((x: APIEntity) => x.id))); + dispatch(fetchRelationships(data.map((x) => x.id))); }).catch(error => dispatch(fetchDirectoryFail(error))); }; @@ -29,7 +29,7 @@ const fetchDirectoryRequest = () => ({ type: DIRECTORY_FETCH_REQUEST, }); -const fetchDirectorySuccess = (accounts: APIEntity[]) => ({ +const fetchDirectorySuccess = (accounts: Array) => ({ type: DIRECTORY_FETCH_SUCCESS, accounts, }); @@ -48,7 +48,7 @@ const expandDirectory = (params: Record) => return getClient(getState()).instance.profileDirectory({ ...params, offset: loadedItems, limit: 20 }).then((data) => { dispatch(importFetchedAccounts(data)); dispatch(expandDirectorySuccess(data)); - dispatch(fetchRelationships(data.map((x: APIEntity) => x.id))); + dispatch(fetchRelationships(data.map((x) => x.id))); }).catch(error => dispatch(expandDirectoryFail(error))); }; @@ -56,7 +56,7 @@ const expandDirectoryRequest = () => ({ type: DIRECTORY_EXPAND_REQUEST, }); -const expandDirectorySuccess = (accounts: APIEntity[]) => ({ +const expandDirectorySuccess = (accounts: Array) => ({ type: DIRECTORY_EXPAND_SUCCESS, accounts, }); diff --git a/src/actions/domain-blocks.ts b/src/actions/domain-blocks.ts index 60b2a7291..a0d7361c1 100644 --- a/src/actions/domain-blocks.ts +++ b/src/actions/domain-blocks.ts @@ -5,24 +5,24 @@ import { getClient } from '../api'; import type { PaginatedResponse } from 'pl-api'; import type { EntityStore } from 'soapbox/entity-store/types'; -import type { Account } from 'soapbox/schemas'; +import type { Account } from 'soapbox/normalizers'; import type { AppDispatch, RootState } from 'soapbox/store'; -const DOMAIN_BLOCK_REQUEST = 'DOMAIN_BLOCK_REQUEST'; -const DOMAIN_BLOCK_SUCCESS = 'DOMAIN_BLOCK_SUCCESS'; -const DOMAIN_BLOCK_FAIL = 'DOMAIN_BLOCK_FAIL'; +const DOMAIN_BLOCK_REQUEST = 'DOMAIN_BLOCK_REQUEST' as const; +const DOMAIN_BLOCK_SUCCESS = 'DOMAIN_BLOCK_SUCCESS' as const; +const DOMAIN_BLOCK_FAIL = 'DOMAIN_BLOCK_FAIL' as const; -const DOMAIN_UNBLOCK_REQUEST = 'DOMAIN_UNBLOCK_REQUEST'; -const DOMAIN_UNBLOCK_SUCCESS = 'DOMAIN_UNBLOCK_SUCCESS'; -const DOMAIN_UNBLOCK_FAIL = 'DOMAIN_UNBLOCK_FAIL'; +const DOMAIN_UNBLOCK_REQUEST = 'DOMAIN_UNBLOCK_REQUEST' as const; +const DOMAIN_UNBLOCK_SUCCESS = 'DOMAIN_UNBLOCK_SUCCESS' as const; +const DOMAIN_UNBLOCK_FAIL = 'DOMAIN_UNBLOCK_FAIL' as const; -const DOMAIN_BLOCKS_FETCH_REQUEST = 'DOMAIN_BLOCKS_FETCH_REQUEST'; -const DOMAIN_BLOCKS_FETCH_SUCCESS = 'DOMAIN_BLOCKS_FETCH_SUCCESS'; -const DOMAIN_BLOCKS_FETCH_FAIL = 'DOMAIN_BLOCKS_FETCH_FAIL'; +const DOMAIN_BLOCKS_FETCH_REQUEST = 'DOMAIN_BLOCKS_FETCH_REQUEST' as const; +const DOMAIN_BLOCKS_FETCH_SUCCESS = 'DOMAIN_BLOCKS_FETCH_SUCCESS' as const; +const DOMAIN_BLOCKS_FETCH_FAIL = 'DOMAIN_BLOCKS_FETCH_FAIL' as const; -const DOMAIN_BLOCKS_EXPAND_REQUEST = 'DOMAIN_BLOCKS_EXPAND_REQUEST'; -const DOMAIN_BLOCKS_EXPAND_SUCCESS = 'DOMAIN_BLOCKS_EXPAND_SUCCESS'; -const DOMAIN_BLOCKS_EXPAND_FAIL = 'DOMAIN_BLOCKS_EXPAND_FAIL'; +const DOMAIN_BLOCKS_EXPAND_REQUEST = 'DOMAIN_BLOCKS_EXPAND_REQUEST' as const; +const DOMAIN_BLOCKS_EXPAND_SUCCESS = 'DOMAIN_BLOCKS_EXPAND_SUCCESS' as const; +const DOMAIN_BLOCKS_EXPAND_FAIL = 'DOMAIN_BLOCKS_EXPAND_FAIL' as const; const blockDomain = (domain: string) => (dispatch: AppDispatch, getState: () => RootState) => { @@ -157,6 +157,20 @@ const expandDomainBlocksFail = (error: unknown) => ({ error, }); +type DomainBlocksAction = + ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType; + export { DOMAIN_BLOCK_REQUEST, DOMAIN_BLOCK_SUCCESS, @@ -186,4 +200,5 @@ export { expandDomainBlocksRequest, expandDomainBlocksSuccess, expandDomainBlocksFail, + type DomainBlocksAction, }; diff --git a/src/actions/draft-statuses.ts b/src/actions/draft-statuses.ts index 90d10c652..5ff5ca8e0 100644 --- a/src/actions/draft-statuses.ts +++ b/src/actions/draft-statuses.ts @@ -8,7 +8,7 @@ import type { AppDispatch, RootState } from 'soapbox/store'; const DRAFT_STATUSES_FETCH_SUCCESS = 'DRAFT_STATUSES_FETCH_SUCCESS'; const PERSIST_DRAFT_STATUS = 'PERSIST_DRAFT_STATUS'; -const CANCEL_DRAFT_STATUS = 'DELETE_DRAFT_STATUS'; +const CANCEL_DRAFT_STATUS = 'DELETE_DRAFT_STATUS'; const getAccount = makeGetAccount(); @@ -44,14 +44,14 @@ const saveDraftStatus = (composeId: string) => }); }; -const cancelDraftStatus = (id: string) => +const cancelDraftStatus = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); const accountUrl = getAccount(state, state.me as string)!.url; dispatch({ type: CANCEL_DRAFT_STATUS, - id, + statusId, accountUrl, }); }; diff --git a/src/actions/email-list.ts b/src/actions/email-list.ts deleted file mode 100644 index 11750a124..000000000 --- a/src/actions/email-list.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { getClient } from '../api'; - -import type { RootState } from 'soapbox/store'; - -const getSubscribersCsv = () => - (dispatch: any, getState: () => RootState) => - getClient(getState).request('/api/v1/pleroma/admin/email_list/subscribers.csv', { contentType: '' }); - -const getUnsubscribersCsv = () => - (dispatch: any, getState: () => RootState) => - getClient(getState).request('/api/v1/pleroma/admin/email_list/unsubscribers.csv', { contentType: '' }); - -const getCombinedCsv = () => - (dispatch: any, getState: () => RootState) => - getClient(getState).request('/api/v1/pleroma/admin/email_list/combined.csv', { contentType: '' }); - -export { - getSubscribersCsv, - getUnsubscribersCsv, - getCombinedCsv, -}; diff --git a/src/actions/emoji-reacts.ts b/src/actions/emoji-reacts.ts index 0f122c21f..a9930a6c1 100644 --- a/src/actions/emoji-reacts.ts +++ b/src/actions/emoji-reacts.ts @@ -1,5 +1,3 @@ -import { List as ImmutableList } from 'immutable'; - import { isLoggedIn } from 'soapbox/utils/auth'; import { getClient } from '../api'; @@ -7,32 +5,32 @@ import { getClient } from '../api'; import { importFetchedStatus } from './importer'; import { favourite, unfavourite } from './interactions'; +import type { EmojiReaction, Status } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { EmojiReaction, Status } from 'soapbox/types/entities'; -const EMOJI_REACT_REQUEST = 'EMOJI_REACT_REQUEST'; -const EMOJI_REACT_SUCCESS = 'EMOJI_REACT_SUCCESS'; -const EMOJI_REACT_FAIL = 'EMOJI_REACT_FAIL'; +const EMOJI_REACT_REQUEST = 'EMOJI_REACT_REQUEST' as const; +const EMOJI_REACT_SUCCESS = 'EMOJI_REACT_SUCCESS' as const; +const EMOJI_REACT_FAIL = 'EMOJI_REACT_FAIL' as const; -const UNEMOJI_REACT_REQUEST = 'UNEMOJI_REACT_REQUEST'; -const UNEMOJI_REACT_SUCCESS = 'UNEMOJI_REACT_SUCCESS'; -const UNEMOJI_REACT_FAIL = 'UNEMOJI_REACT_FAIL'; +const UNEMOJI_REACT_REQUEST = 'UNEMOJI_REACT_REQUEST' as const; +const UNEMOJI_REACT_SUCCESS = 'UNEMOJI_REACT_SUCCESS' as const; +const UNEMOJI_REACT_FAIL = 'UNEMOJI_REACT_FAIL' as const; const noOp = () => () => new Promise(f => f(undefined)); -const simpleEmojiReact = (status: Status, emoji: string, custom?: string) => +const simpleEmojiReact = (status: Pick, emoji: string, custom?: string) => (dispatch: AppDispatch) => { - const emojiReacts: ImmutableList = status.reactions || ImmutableList(); + const emojiReacts: Array = status.emoji_reactions || []; if (emoji === 'πŸ‘' && status.favourited) return dispatch(unfavourite(status)); - const undo = emojiReacts.filter(e => e.me === true && e.name === emoji).count() > 0; + const undo = emojiReacts.filter(e => e.me === true && e.name === emoji).length > 0; if (undo) return dispatch(unEmojiReact(status, emoji)); return Promise.all([ ...emojiReacts .filter((emojiReact) => emojiReact.me === true) - .map(emojiReact => dispatch(unEmojiReact(status, emojiReact.name))).toArray(), + .map(emojiReact => dispatch(unEmojiReact(status, emojiReact.name))), status.favourited && dispatch(unfavourite(status)), ]).then(() => { if (emoji === 'πŸ‘') { @@ -45,77 +43,73 @@ const simpleEmojiReact = (status: Status, emoji: string, custom?: string) => }); }; -const emojiReact = (status: Status, emoji: string, custom?: string) => +const emojiReact = (status: Pick, emoji: string, custom?: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return dispatch(noOp()); - dispatch(emojiReactRequest(status, emoji, custom)); + dispatch(emojiReactRequest(status.id, emoji, custom)); return getClient(getState).statuses.createStatusReaction(status.id, emoji).then((response) => { dispatch(importFetchedStatus(response)); - dispatch(emojiReactSuccess(status, emoji)); + dispatch(emojiReactSuccess(response, emoji)); }).catch((error) => { - dispatch(emojiReactFail(status, emoji, error)); + dispatch(emojiReactFail(status.id, emoji, error)); }); }; -const unEmojiReact = (status: Status, emoji: string) => +const unEmojiReact = (status: Pick, emoji: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return dispatch(noOp()); - dispatch(unEmojiReactRequest(status, emoji)); + dispatch(unEmojiReactRequest(status.id, emoji)); return getClient(getState).statuses.deleteStatusReaction(status.id, emoji).then(response => { dispatch(importFetchedStatus(response)); - dispatch(unEmojiReactSuccess(status, emoji)); + dispatch(unEmojiReactSuccess(response, emoji)); }).catch(error => { - dispatch(unEmojiReactFail(status, emoji, error)); + dispatch(unEmojiReactFail(status.id, emoji, error)); }); }; -const emojiReactRequest = (status: Status, emoji: string, custom?: string) => ({ +const emojiReactRequest = (statusId: string, emoji: string, custom?: string) => ({ type: EMOJI_REACT_REQUEST, - status, + statusId, emoji, custom, - skipLoading: true, }); const emojiReactSuccess = (status: Status, emoji: string) => ({ type: EMOJI_REACT_SUCCESS, status, + statusId: status.id, emoji, - skipLoading: true, }); -const emojiReactFail = (status: Status, emoji: string, error: unknown) => ({ +const emojiReactFail = (statusId: string, emoji: string, error: unknown) => ({ type: EMOJI_REACT_FAIL, - status, + statusId, emoji, error, - skipLoading: true, }); -const unEmojiReactRequest = (status: Status, emoji: string) => ({ +const unEmojiReactRequest = (statusId: string, emoji: string) => ({ type: UNEMOJI_REACT_REQUEST, - status, + statusId, emoji, - skipLoading: true, }); const unEmojiReactSuccess = (status: Status, emoji: string) => ({ type: UNEMOJI_REACT_SUCCESS, status, + statusId: status.id, emoji, - skipLoading: true, }); -const unEmojiReactFail = (status: Status, emoji: string, error: unknown) => ({ +const unEmojiReactFail = (statusId: string, emoji: string, error: unknown) => ({ type: UNEMOJI_REACT_FAIL, - status, + statusId, emoji, error, - skipLoading: true, }); export { diff --git a/src/actions/events.ts b/src/actions/events.ts index 2660aa77c..7451589fb 100644 --- a/src/actions/events.ts +++ b/src/actions/events.ts @@ -12,64 +12,63 @@ import { STATUS_FETCH_SOURCE_SUCCESS, } from './statuses'; -import type { Account, CreateEventParams, PaginatedResponse } from 'pl-api'; +import type { Account, CreateEventParams, MediaAttachment, PaginatedResponse, Status } from 'pl-api'; import type { ReducerStatus } from 'soapbox/reducers/statuses'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity, Status as StatusEntity } from 'soapbox/types/entities'; const LOCATION_SEARCH_REQUEST = 'LOCATION_SEARCH_REQUEST' as const; const LOCATION_SEARCH_SUCCESS = 'LOCATION_SEARCH_SUCCESS' as const; -const LOCATION_SEARCH_FAIL = 'LOCATION_SEARCH_FAIL' as const; +const LOCATION_SEARCH_FAIL = 'LOCATION_SEARCH_FAIL' as const; -const EDIT_EVENT_NAME_CHANGE = 'EDIT_EVENT_NAME_CHANGE' as const; -const EDIT_EVENT_DESCRIPTION_CHANGE = 'EDIT_EVENT_DESCRIPTION_CHANGE' as const; -const EDIT_EVENT_START_TIME_CHANGE = 'EDIT_EVENT_START_TIME_CHANGE' as const; -const EDIT_EVENT_HAS_END_TIME_CHANGE = 'EDIT_EVENT_HAS_END_TIME_CHANGE' as const; -const EDIT_EVENT_END_TIME_CHANGE = 'EDIT_EVENT_END_TIME_CHANGE' as const; +const EDIT_EVENT_NAME_CHANGE = 'EDIT_EVENT_NAME_CHANGE' as const; +const EDIT_EVENT_DESCRIPTION_CHANGE = 'EDIT_EVENT_DESCRIPTION_CHANGE' as const; +const EDIT_EVENT_START_TIME_CHANGE = 'EDIT_EVENT_START_TIME_CHANGE' as const; +const EDIT_EVENT_HAS_END_TIME_CHANGE = 'EDIT_EVENT_HAS_END_TIME_CHANGE' as const; +const EDIT_EVENT_END_TIME_CHANGE = 'EDIT_EVENT_END_TIME_CHANGE' as const; const EDIT_EVENT_APPROVAL_REQUIRED_CHANGE = 'EDIT_EVENT_APPROVAL_REQUIRED_CHANGE' as const; -const EDIT_EVENT_LOCATION_CHANGE = 'EDIT_EVENT_LOCATION_CHANGE' as const; +const EDIT_EVENT_LOCATION_CHANGE = 'EDIT_EVENT_LOCATION_CHANGE' as const; -const EVENT_BANNER_UPLOAD_REQUEST = 'EVENT_BANNER_UPLOAD_REQUEST' as const; +const EVENT_BANNER_UPLOAD_REQUEST = 'EVENT_BANNER_UPLOAD_REQUEST' as const; const EVENT_BANNER_UPLOAD_PROGRESS = 'EVENT_BANNER_UPLOAD_PROGRESS' as const; -const EVENT_BANNER_UPLOAD_SUCCESS = 'EVENT_BANNER_UPLOAD_SUCCESS' as const; -const EVENT_BANNER_UPLOAD_FAIL = 'EVENT_BANNER_UPLOAD_FAIL' as const; -const EVENT_BANNER_UPLOAD_UNDO = 'EVENT_BANNER_UPLOAD_UNDO' as const; +const EVENT_BANNER_UPLOAD_SUCCESS = 'EVENT_BANNER_UPLOAD_SUCCESS' as const; +const EVENT_BANNER_UPLOAD_FAIL = 'EVENT_BANNER_UPLOAD_FAIL' as const; +const EVENT_BANNER_UPLOAD_UNDO = 'EVENT_BANNER_UPLOAD_UNDO' as const; const EVENT_SUBMIT_REQUEST = 'EVENT_SUBMIT_REQUEST' as const; const EVENT_SUBMIT_SUCCESS = 'EVENT_SUBMIT_SUCCESS' as const; -const EVENT_SUBMIT_FAIL = 'EVENT_SUBMIT_FAIL' as const; +const EVENT_SUBMIT_FAIL = 'EVENT_SUBMIT_FAIL' as const; const EVENT_JOIN_REQUEST = 'EVENT_JOIN_REQUEST' as const; const EVENT_JOIN_SUCCESS = 'EVENT_JOIN_SUCCESS' as const; -const EVENT_JOIN_FAIL = 'EVENT_JOIN_FAIL' as const; +const EVENT_JOIN_FAIL = 'EVENT_JOIN_FAIL' as const; const EVENT_LEAVE_REQUEST = 'EVENT_LEAVE_REQUEST' as const; const EVENT_LEAVE_SUCCESS = 'EVENT_LEAVE_SUCCESS' as const; -const EVENT_LEAVE_FAIL = 'EVENT_LEAVE_FAIL' as const; +const EVENT_LEAVE_FAIL = 'EVENT_LEAVE_FAIL' as const; const EVENT_PARTICIPATIONS_FETCH_REQUEST = 'EVENT_PARTICIPATIONS_FETCH_REQUEST' as const; const EVENT_PARTICIPATIONS_FETCH_SUCCESS = 'EVENT_PARTICIPATIONS_FETCH_SUCCESS' as const; -const EVENT_PARTICIPATIONS_FETCH_FAIL = 'EVENT_PARTICIPATIONS_FETCH_FAIL' as const; +const EVENT_PARTICIPATIONS_FETCH_FAIL = 'EVENT_PARTICIPATIONS_FETCH_FAIL' as const; const EVENT_PARTICIPATIONS_EXPAND_REQUEST = 'EVENT_PARTICIPATIONS_EXPAND_REQUEST' as const; const EVENT_PARTICIPATIONS_EXPAND_SUCCESS = 'EVENT_PARTICIPATIONS_EXPAND_SUCCESS' as const; -const EVENT_PARTICIPATIONS_EXPAND_FAIL = 'EVENT_PARTICIPATIONS_EXPAND_FAIL' as const; +const EVENT_PARTICIPATIONS_EXPAND_FAIL = 'EVENT_PARTICIPATIONS_EXPAND_FAIL' as const; const EVENT_PARTICIPATION_REQUESTS_FETCH_REQUEST = 'EVENT_PARTICIPATION_REQUESTS_FETCH_REQUEST' as const; const EVENT_PARTICIPATION_REQUESTS_FETCH_SUCCESS = 'EVENT_PARTICIPATION_REQUESTS_FETCH_SUCCESS' as const; -const EVENT_PARTICIPATION_REQUESTS_FETCH_FAIL = 'EVENT_PARTICIPATION_REQUESTS_FETCH_FAIL' as const; +const EVENT_PARTICIPATION_REQUESTS_FETCH_FAIL = 'EVENT_PARTICIPATION_REQUESTS_FETCH_FAIL' as const; const EVENT_PARTICIPATION_REQUESTS_EXPAND_REQUEST = 'EVENT_PARTICIPATION_REQUESTS_EXPAND_REQUEST' as const; const EVENT_PARTICIPATION_REQUESTS_EXPAND_SUCCESS = 'EVENT_PARTICIPATION_REQUESTS_EXPAND_SUCCESS' as const; -const EVENT_PARTICIPATION_REQUESTS_EXPAND_FAIL = 'EVENT_PARTICIPATION_REQUESTS_EXPAND_FAIL' as const; +const EVENT_PARTICIPATION_REQUESTS_EXPAND_FAIL = 'EVENT_PARTICIPATION_REQUESTS_EXPAND_FAIL' as const; const EVENT_PARTICIPATION_REQUEST_AUTHORIZE_REQUEST = 'EVENT_PARTICIPATION_REQUEST_AUTHORIZE_REQUEST' as const; const EVENT_PARTICIPATION_REQUEST_AUTHORIZE_SUCCESS = 'EVENT_PARTICIPATION_REQUEST_AUTHORIZE_SUCCESS' as const; -const EVENT_PARTICIPATION_REQUEST_AUTHORIZE_FAIL = 'EVENT_PARTICIPATION_REQUEST_AUTHORIZE_FAIL' as const; +const EVENT_PARTICIPATION_REQUEST_AUTHORIZE_FAIL = 'EVENT_PARTICIPATION_REQUEST_AUTHORIZE_FAIL' as const; const EVENT_PARTICIPATION_REQUEST_REJECT_REQUEST = 'EVENT_PARTICIPATION_REQUEST_REJECT_REQUEST' as const; const EVENT_PARTICIPATION_REQUEST_REJECT_SUCCESS = 'EVENT_PARTICIPATION_REQUEST_REJECT_SUCCESS' as const; -const EVENT_PARTICIPATION_REQUEST_REJECT_FAIL = 'EVENT_PARTICIPATION_REQUEST_REJECT_FAIL' as const; +const EVENT_PARTICIPATION_REQUEST_REJECT_FAIL = 'EVENT_PARTICIPATION_REQUEST_REJECT_FAIL' as const; const EVENT_COMPOSE_CANCEL = 'EVENT_COMPOSE_CANCEL' as const; @@ -178,7 +177,7 @@ const uploadEventBannerProgress = (loaded: number) => ({ loaded, }); -const uploadEventBannerSuccess = (media: APIEntity, file: File) => ({ +const uploadEventBannerSuccess = (media: MediaAttachment, file: File) => ({ type: EVENT_BANNER_UPLOAD_SUCCESS, media, file, @@ -197,14 +196,14 @@ const submitEvent = () => (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); - const id = state.compose_event.id; - const name = state.compose_event.name; - const status = state.compose_event.status; - const banner = state.compose_event.banner; + const statusId = state.compose_event.id; + const name = state.compose_event.name; + const status = state.compose_event.status; + const banner = state.compose_event.banner; const startTime = state.compose_event.start_time; - const endTime = state.compose_event.end_time; - const joinMode = state.compose_event.approval_required ? 'restricted' : 'free'; - const location = state.compose_event.location; + const endTime = state.compose_event.end_time; + const joinMode = state.compose_event.approval_required ? 'restricted' : 'free'; + const location = state.compose_event.location; if (!name || !name.length) { return; @@ -224,12 +223,16 @@ const submitEvent = () => if (banner) params.banner_id = banner.id; if (location) params.location_id = location.origin_id; - return (id === null ? getClient(state).events.createEvent(params) : getClient(state).events.editEvent(id, params)).then((data) => { + return ( + statusId === null + ? getClient(state).events.createEvent(params) + : getClient(state).events.editEvent(statusId, params) + ).then((data) => { dispatch(closeModal('COMPOSE_EVENT')); dispatch(importFetchedStatus(data)); dispatch(submitEventSuccess(data)); toast.success( - id ? messages.editSuccess : messages.success, + statusId ? messages.editSuccess : messages.success, { actionLabel: messages.view, actionLink: `/@${data.account.acct}/events/${data.id}`, @@ -244,7 +247,7 @@ const submitEventRequest = () => ({ type: EVENT_SUBMIT_REQUEST, }); -const submitEventSuccess = (status: APIEntity) => ({ +const submitEventSuccess = (status: Status) => ({ type: EVENT_SUBMIT_SUCCESS, status, }); @@ -254,19 +257,19 @@ const submitEventFail = (error: unknown) => ({ error, }); -const joinEvent = (id: string, participationMessage?: string) => +const joinEvent = (statusId: string, participationMessage?: string) => (dispatch: AppDispatch, getState: () => RootState) => { - const status = getState().statuses.get(id); + const status = getState().statuses.get(statusId); if (!status || !status.event || status.event.join_state) { return dispatch(noOp); } - dispatch(joinEventRequest(status)); + dispatch(joinEventRequest(status.id)); - return getClient(getState).events.joinEvent(id, participationMessage).then((data) => { + return getClient(getState).events.joinEvent(statusId, participationMessage).then((data) => { dispatch(importFetchedStatus(data)); - dispatch(joinEventSuccess(data)); + dispatch(joinEventSuccess(status.id)); toast.success( data.event?.join_state === 'pending' ? messages.joinRequestSuccess : messages.joinSuccess, { @@ -275,254 +278,260 @@ const joinEvent = (id: string, participationMessage?: string) => }, ); }).catch((error) => { - dispatch(joinEventFail(error, status, status?.event?.join_state || null)); + dispatch(joinEventFail(error, status.id, status?.event?.join_state || null)); }); }; -const joinEventRequest = (status: StatusEntity) => ({ +const joinEventRequest = (statusId: string) => ({ type: EVENT_JOIN_REQUEST, - id: status.id, + statusId, }); -const joinEventSuccess = (status: APIEntity) => ({ +const joinEventSuccess = (statusId: string) => ({ type: EVENT_JOIN_SUCCESS, - id: status.id, + statusId, }); -const joinEventFail = (error: unknown, status: StatusEntity, previousState: string | null) => ({ +const joinEventFail = (error: unknown, statusId: string, previousState: string | null) => ({ type: EVENT_JOIN_FAIL, error, - id: status.id, + statusId, previousState, }); -const leaveEvent = (id: string) => +const leaveEvent = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - const status = getState().statuses.get(id); + const status = getState().statuses.get(statusId); if (!status || !status.event || !status.event.join_state) { return dispatch(noOp); } - dispatch(leaveEventRequest(status)); + dispatch(leaveEventRequest(status.id)); - return getClient(getState).events.leaveEvent(id).then((data) => { + return getClient(getState).events.leaveEvent(statusId).then((data) => { dispatch(importFetchedStatus(data)); - dispatch(leaveEventSuccess(data)); + dispatch(leaveEventSuccess(status.id)); }).catch((error) => { - dispatch(leaveEventFail(error, status)); + dispatch(leaveEventFail(error, status.id)); }); }; -const leaveEventRequest = (status: StatusEntity) => ({ +const leaveEventRequest = (statusId: string) => ({ type: EVENT_LEAVE_REQUEST, - id: status.id, + statusId, }); -const leaveEventSuccess = (status: APIEntity) => ({ +const leaveEventSuccess = (statusId: string) => ({ type: EVENT_LEAVE_SUCCESS, - id: status.id, + statusId, }); -const leaveEventFail = (error: unknown, status: StatusEntity) => ({ +const leaveEventFail = (error: unknown, statusId: string) => ({ type: EVENT_LEAVE_FAIL, - id: status.id, + statusId, error, }); -const fetchEventParticipations = (id: string) => +const fetchEventParticipations = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - dispatch(fetchEventParticipationsRequest(id)); + dispatch(fetchEventParticipationsRequest(statusId)); - return getClient(getState).events.getEventParticipations(id).then(response => { + return getClient(getState).events.getEventParticipations(statusId).then(response => { dispatch(importFetchedAccounts(response.items)); - return dispatch(fetchEventParticipationsSuccess(id, response.items, response.next)); + return dispatch(fetchEventParticipationsSuccess(statusId, response.items, response.next)); }).catch(error => { - dispatch(fetchEventParticipationsFail(id, error)); + dispatch(fetchEventParticipationsFail(statusId, error)); }); }; -const fetchEventParticipationsRequest = (id: string) => ({ +const fetchEventParticipationsRequest = (statusId: string) => ({ type: EVENT_PARTICIPATIONS_FETCH_REQUEST, - id, + statusId, }); -const fetchEventParticipationsSuccess = (id: string, accounts: APIEntity[], next: (() => Promise>) | null) => ({ +const fetchEventParticipationsSuccess = (statusId: string, accounts: Array, next: (() => Promise>) | null) => ({ type: EVENT_PARTICIPATIONS_FETCH_SUCCESS, - id, + statusId, accounts, next, }); -const fetchEventParticipationsFail = (id: string, error: unknown) => ({ +const fetchEventParticipationsFail = (statusId: string, error: unknown) => ({ type: EVENT_PARTICIPATIONS_FETCH_FAIL, - id, + statusId, error, }); -const expandEventParticipations = (id: string) => +const expandEventParticipations = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - const next = getState().user_lists.event_participations.get(id)?.next || null; + const next = getState().user_lists.event_participations.get(statusId)?.next || null; if (next === null) { return dispatch(noOp); } - dispatch(expandEventParticipationsRequest(id)); + dispatch(expandEventParticipationsRequest(statusId)); return next().then(response => { dispatch(importFetchedAccounts(response.items)); - return dispatch(expandEventParticipationsSuccess(id, response.items, response.next)); + return dispatch(expandEventParticipationsSuccess(statusId, response.items, response.next)); }).catch(error => { - dispatch(expandEventParticipationsFail(id, error)); + dispatch(expandEventParticipationsFail(statusId, error)); }); }; -const expandEventParticipationsRequest = (id: string) => ({ +const expandEventParticipationsRequest = (statusId: string) => ({ type: EVENT_PARTICIPATIONS_EXPAND_REQUEST, - id, + statusId, }); -const expandEventParticipationsSuccess = (id: string, accounts: APIEntity[], next: (() => Promise>) | null) => ({ +const expandEventParticipationsSuccess = (statusId: string, accounts: Array, next: (() => Promise>) | null) => ({ type: EVENT_PARTICIPATIONS_EXPAND_SUCCESS, - id, + statusId, accounts, next, }); -const expandEventParticipationsFail = (id: string, error: unknown) => ({ +const expandEventParticipationsFail = (statusId: string, error: unknown) => ({ type: EVENT_PARTICIPATIONS_EXPAND_FAIL, - id, + statusId, error, }); -const fetchEventParticipationRequests = (id: string) => +const fetchEventParticipationRequests = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - dispatch(fetchEventParticipationRequestsRequest(id)); + dispatch(fetchEventParticipationRequestsRequest(statusId)); - return getClient(getState).events.getEventParticipationRequests(id).then(response => { - dispatch(importFetchedAccounts(response.items.map(({ account }: APIEntity) => account))); - return dispatch(fetchEventParticipationRequestsSuccess(id, response.items, response.next)); + return getClient(getState).events.getEventParticipationRequests(statusId).then(response => { + dispatch(importFetchedAccounts(response.items.map(({ account }) => account))); + return dispatch(fetchEventParticipationRequestsSuccess(statusId, response.items, response.next)); }).catch(error => { - dispatch(fetchEventParticipationRequestsFail(id, error)); + dispatch(fetchEventParticipationRequestsFail(statusId, error)); }); }; -const fetchEventParticipationRequestsRequest = (id: string) => ({ +const fetchEventParticipationRequestsRequest = (statusId: string) => ({ type: EVENT_PARTICIPATION_REQUESTS_FETCH_REQUEST, - id, + statusId, }); -const fetchEventParticipationRequestsSuccess = (id: string, participations: APIEntity[], next: (() => Promise>) | null) => ({ +const fetchEventParticipationRequestsSuccess = (statusId: string, participations: Array<{ + account: Account; + participation_message: string; +}>, next: (() => Promise>) | null) => ({ type: EVENT_PARTICIPATION_REQUESTS_FETCH_SUCCESS, - id, + statusId, participations, next, }); -const fetchEventParticipationRequestsFail = (id: string, error: unknown) => ({ +const fetchEventParticipationRequestsFail = (statusId: string, error: unknown) => ({ type: EVENT_PARTICIPATION_REQUESTS_FETCH_FAIL, - id, + statusId, error, }); -const expandEventParticipationRequests = (id: string) => +const expandEventParticipationRequests = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - const next = getState().user_lists.event_participation_requests.get(id)?.next || null; + const next = getState().user_lists.event_participation_requests.get(statusId)?.next || null; if (next === null) { return dispatch(noOp); } - dispatch(expandEventParticipationRequestsRequest(id)); + dispatch(expandEventParticipationRequestsRequest(statusId)); return next().then(response => { - dispatch(importFetchedAccounts(response.items.map(({ account }: APIEntity) => account))); - return dispatch(expandEventParticipationRequestsSuccess(id, response.items, response.next)); + dispatch(importFetchedAccounts(response.items.map(({ account }) => account))); + return dispatch(expandEventParticipationRequestsSuccess(statusId, response.items, response.next)); }).catch(error => { - dispatch(expandEventParticipationRequestsFail(id, error)); + dispatch(expandEventParticipationRequestsFail(statusId, error)); }); }; -const expandEventParticipationRequestsRequest = (id: string) => ({ +const expandEventParticipationRequestsRequest = (statusId: string) => ({ type: EVENT_PARTICIPATION_REQUESTS_EXPAND_REQUEST, - id, + statusId, }); -const expandEventParticipationRequestsSuccess = (id: string, participations: APIEntity[], next: (() => Promise>) | null) => ({ +const expandEventParticipationRequestsSuccess = (statusId: string, participations: Array<{ + account: Account; + participation_message: string; +}>, next: (() => Promise>) | null) => ({ type: EVENT_PARTICIPATION_REQUESTS_EXPAND_SUCCESS, - id, + statusId, participations, next, }); -const expandEventParticipationRequestsFail = (id: string, error: unknown) => ({ +const expandEventParticipationRequestsFail = (statusId: string, error: unknown) => ({ type: EVENT_PARTICIPATION_REQUESTS_EXPAND_FAIL, - id, + statusId, error, }); -const authorizeEventParticipationRequest = (id: string, accountId: string) => +const authorizeEventParticipationRequest = (statusId: string, accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - dispatch(authorizeEventParticipationRequestRequest(id, accountId)); + dispatch(authorizeEventParticipationRequestRequest(statusId, accountId)); - return getClient(getState).events.acceptEventParticipationRequest(id, accountId).then(() => { - dispatch(authorizeEventParticipationRequestSuccess(id, accountId)); + return getClient(getState).events.acceptEventParticipationRequest(statusId, accountId).then(() => { + dispatch(authorizeEventParticipationRequestSuccess(statusId, accountId)); toast.success(messages.authorized); - }).catch(error => dispatch(authorizeEventParticipationRequestFail(id, accountId, error))); + }).catch(error => dispatch(authorizeEventParticipationRequestFail(statusId, accountId, error))); }; -const authorizeEventParticipationRequestRequest = (id: string, accountId: string) => ({ +const authorizeEventParticipationRequestRequest = (statusId: string, accountId: string) => ({ type: EVENT_PARTICIPATION_REQUEST_AUTHORIZE_REQUEST, - id, + statusId, accountId, }); -const authorizeEventParticipationRequestSuccess = (id: string, accountId: string) => ({ +const authorizeEventParticipationRequestSuccess = (statusId: string, accountId: string) => ({ type: EVENT_PARTICIPATION_REQUEST_AUTHORIZE_SUCCESS, - id, + statusId, accountId, }); -const authorizeEventParticipationRequestFail = (id: string, accountId: string, error: unknown) => ({ +const authorizeEventParticipationRequestFail = (statusId: string, accountId: string, error: unknown) => ({ type: EVENT_PARTICIPATION_REQUEST_AUTHORIZE_FAIL, - id, + statusId, accountId, error, }); -const rejectEventParticipationRequest = (id: string, accountId: string) => +const rejectEventParticipationRequest = (statusId: string, accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - dispatch(rejectEventParticipationRequestRequest(id, accountId)); + dispatch(rejectEventParticipationRequestRequest(statusId, accountId)); - return getClient(getState).events.rejectEventParticipationRequest(id, accountId).then(() => { - dispatch(rejectEventParticipationRequestSuccess(id, accountId)); + return getClient(getState).events.rejectEventParticipationRequest(statusId, accountId).then(() => { + dispatch(rejectEventParticipationRequestSuccess(statusId, accountId)); toast.success(messages.rejected); - }).catch(error => dispatch(rejectEventParticipationRequestFail(id, accountId, error))); + }).catch(error => dispatch(rejectEventParticipationRequestFail(statusId, accountId, error))); }; -const rejectEventParticipationRequestRequest = (id: string, accountId: string) => ({ +const rejectEventParticipationRequestRequest = (statusId: string, accountId: string) => ({ type: EVENT_PARTICIPATION_REQUEST_REJECT_REQUEST, - id, + statusId, accountId, }); -const rejectEventParticipationRequestSuccess = (id: string, accountId: string) => ({ +const rejectEventParticipationRequestSuccess = (statusId: string, accountId: string) => ({ type: EVENT_PARTICIPATION_REQUEST_REJECT_SUCCESS, - id, + statusId, accountId, }); -const rejectEventParticipationRequestFail = (id: string, accountId: string, error: unknown) => ({ +const rejectEventParticipationRequestFail = (statusId: string, accountId: string, error: unknown) => ({ type: EVENT_PARTICIPATION_REQUEST_REJECT_FAIL, - id, + statusId, accountId, error, }); -const fetchEventIcs = (id: string) => +const fetchEventIcs = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => - getClient(getState).events.getEventIcs(id); + getClient(getState).events.getEventIcs(statusId); const cancelEventCompose = () => ({ type: EVENT_COMPOSE_CANCEL, @@ -535,13 +544,13 @@ interface EventFormSetAction { location: Record; } -const editEvent = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { - const status = getState().statuses.get(id)!; +const editEvent = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { + const status = getState().statuses.get(statusId)!; - dispatch({ type: STATUS_FETCH_SOURCE_REQUEST }); + dispatch({ type: STATUS_FETCH_SOURCE_REQUEST, statusId }); - return getClient(getState()).statuses.getStatusSource(id).then(response => { - dispatch({ type: STATUS_FETCH_SOURCE_SUCCESS }); + return getClient(getState()).statuses.getStatusSource(statusId).then(response => { + dispatch({ type: STATUS_FETCH_SOURCE_SUCCESS, statusId }); dispatch({ type: EVENT_FORM_SET, status, @@ -550,7 +559,7 @@ const editEvent = (id: string) => (dispatch: AppDispatch, getState: () => RootSt }); dispatch(openModal('COMPOSE_EVENT')); }).catch(error => { - dispatch({ type: STATUS_FETCH_SOURCE_FAIL, error }); + dispatch({ type: STATUS_FETCH_SOURCE_FAIL, statusId, error }); }); }; diff --git a/src/actions/export-data.ts b/src/actions/export-data.ts index ac51345c3..b3e68aa3e 100644 --- a/src/actions/export-data.ts +++ b/src/actions/export-data.ts @@ -7,17 +7,17 @@ import toast from 'soapbox/toast'; import type { Account, PaginatedResponse } from 'pl-api'; import type { RootState } from 'soapbox/store'; -const EXPORT_FOLLOWS_REQUEST = 'EXPORT_FOLLOWS_REQUEST'; -const EXPORT_FOLLOWS_SUCCESS = 'EXPORT_FOLLOWS_SUCCESS'; -const EXPORT_FOLLOWS_FAIL = 'EXPORT_FOLLOWS_FAIL'; +const EXPORT_FOLLOWS_REQUEST = 'EXPORT_FOLLOWS_REQUEST' as const; +const EXPORT_FOLLOWS_SUCCESS = 'EXPORT_FOLLOWS_SUCCESS' as const; +const EXPORT_FOLLOWS_FAIL = 'EXPORT_FOLLOWS_FAIL' as const; -const EXPORT_BLOCKS_REQUEST = 'EXPORT_BLOCKS_REQUEST'; -const EXPORT_BLOCKS_SUCCESS = 'EXPORT_BLOCKS_SUCCESS'; -const EXPORT_BLOCKS_FAIL = 'EXPORT_BLOCKS_FAIL'; +const EXPORT_BLOCKS_REQUEST = 'EXPORT_BLOCKS_REQUEST' as const; +const EXPORT_BLOCKS_SUCCESS = 'EXPORT_BLOCKS_SUCCESS' as const; +const EXPORT_BLOCKS_FAIL = 'EXPORT_BLOCKS_FAIL' as const; -const EXPORT_MUTES_REQUEST = 'EXPORT_MUTES_REQUEST'; -const EXPORT_MUTES_SUCCESS = 'EXPORT_MUTES_SUCCESS'; -const EXPORT_MUTES_FAIL = 'EXPORT_MUTES_FAIL'; +const EXPORT_MUTES_REQUEST = 'EXPORT_MUTES_REQUEST' as const; +const EXPORT_MUTES_SUCCESS = 'EXPORT_MUTES_SUCCESS' as const; +const EXPORT_MUTES_FAIL = 'EXPORT_MUTES_FAIL' as const; const messages = defineMessages({ blocksSuccess: { id: 'export_data.success.blocks', defaultMessage: 'Blocks exported successfully' }, @@ -25,7 +25,7 @@ const messages = defineMessages({ mutesSuccess: { id: 'export_data.success.mutes', defaultMessage: 'Mutes exported successfully' }, }); -type ExportDataActions = { +type ExportDataAction = { type: typeof EXPORT_FOLLOWS_REQUEST | typeof EXPORT_FOLLOWS_SUCCESS | typeof EXPORT_FOLLOWS_FAIL @@ -61,7 +61,7 @@ const listAccounts = async (response: PaginatedResponse) => { return Array.from(new Set(accounts)); }; -const exportFollows = () => async (dispatch: React.Dispatch, getState: () => RootState) => { +const exportFollows = () => async (dispatch: React.Dispatch, getState: () => RootState) => { dispatch({ type: EXPORT_FOLLOWS_REQUEST }); const me = getState().me; if (!me) return; @@ -80,7 +80,7 @@ const exportFollows = () => async (dispatch: React.Dispatch, }); }; -const exportBlocks = () => (dispatch: React.Dispatch, getState: () => RootState) => { +const exportBlocks = () => (dispatch: React.Dispatch, getState: () => RootState) => { dispatch({ type: EXPORT_BLOCKS_REQUEST }); return getClient(getState()).filtering.getBlocks({ limit: 40 }) .then(listAccounts) @@ -94,7 +94,7 @@ const exportBlocks = () => (dispatch: React.Dispatch, getStat }); }; -const exportMutes = () => (dispatch: React.Dispatch, getState: () => RootState) => { +const exportMutes = () => (dispatch: React.Dispatch, getState: () => RootState) => { dispatch({ type: EXPORT_MUTES_REQUEST }); return getClient(getState()).filtering.getMutes({ limit: 40 }) .then(listAccounts) @@ -121,4 +121,5 @@ export { exportFollows, exportBlocks, exportMutes, + type ExportDataAction, }; diff --git a/src/actions/external-auth.ts b/src/actions/external-auth.ts index 6b0f51f8f..7fe812687 100644 --- a/src/actions/external-auth.ts +++ b/src/actions/external-auth.ts @@ -16,7 +16,7 @@ import sourceCode from 'soapbox/utils/code'; import { getQuirks } from 'soapbox/utils/quirks'; import { getInstanceScopes } from 'soapbox/utils/scopes'; -import type { AppDispatch, RootState } from 'soapbox/store'; +import type { AppDispatch } from 'soapbox/store'; const fetchExternalInstance = (baseURL: string) => (new PlApiClient(baseURL, undefined, { fetchInstance: false })).instance.getInstance() @@ -32,7 +32,7 @@ const fetchExternalInstance = (baseURL: string) => }); const createExternalApp = (instance: Instance, baseURL?: string) => - (dispatch: AppDispatch, _getState: () => RootState) => { + (dispatch: AppDispatch) => { // Mitra: skip creating the auth app if (getQuirks(instance).noApps) return new Promise(f => f({})); @@ -47,7 +47,7 @@ const createExternalApp = (instance: Instance, baseURL?: string) => }; const externalAuthorize = (instance: Instance, baseURL: string) => - (dispatch: AppDispatch, _getState: () => RootState) => { + (dispatch: AppDispatch) => { const scopes = getInstanceScopes(instance); return dispatch(createExternalApp(instance, baseURL)).then((app) => { @@ -81,7 +81,7 @@ const loginWithCode = (code: string) => (dispatch: AppDispatch) => { const { client_id, client_secret, redirect_uri } = JSON.parse(localStorage.getItem('plfe:external:app')!); const baseURL = localStorage.getItem('plfe:external:baseurl')!; - const scope = localStorage.getItem('plfe:external:scopes')!; + const scope = localStorage.getItem('plfe:external:scopes')!; const params = { client_id, diff --git a/src/actions/familiar-followers.ts b/src/actions/familiar-followers.ts index fba1ba6ad..1a8ee0a88 100644 --- a/src/actions/familiar-followers.ts +++ b/src/actions/familiar-followers.ts @@ -5,16 +5,14 @@ import { getClient } from '../api'; import { fetchRelationships } from './accounts'; import { importFetchedAccounts } from './importer'; -import type { APIEntity } from 'soapbox/types/entities'; - -const FAMILIAR_FOLLOWERS_FETCH_REQUEST = 'FAMILIAR_FOLLOWERS_FETCH_REQUEST'; -const FAMILIAR_FOLLOWERS_FETCH_SUCCESS = 'FAMILIAR_FOLLOWERS_FETCH_SUCCESS'; -const FAMILIAR_FOLLOWERS_FETCH_FAIL = 'FAMILIAR_FOLLOWERS_FETCH_FAIL'; +const FAMILIAR_FOLLOWERS_FETCH_REQUEST = 'FAMILIAR_FOLLOWERS_FETCH_REQUEST' as const; +const FAMILIAR_FOLLOWERS_FETCH_SUCCESS = 'FAMILIAR_FOLLOWERS_FETCH_SUCCESS' as const; +const FAMILIAR_FOLLOWERS_FETCH_FAIL = 'FAMILIAR_FOLLOWERS_FETCH_FAIL' as const; const fetchAccountFamiliarFollowers = (accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: FAMILIAR_FOLLOWERS_FETCH_REQUEST, - id: accountId, + accountId, }); getClient(getState()).accounts.getFamiliarFollowers([accountId]) @@ -22,16 +20,16 @@ const fetchAccountFamiliarFollowers = (accountId: string) => (dispatch: AppDispa const accounts = data.find(({ id }: { id: string }) => id === accountId)!.accounts; dispatch(importFetchedAccounts(accounts)); - dispatch(fetchRelationships(accounts.map((item: APIEntity) => item.id))); + dispatch(fetchRelationships(accounts.map((item) => item.id))); dispatch({ type: FAMILIAR_FOLLOWERS_FETCH_SUCCESS, - id: accountId, + accountId, accounts, }); }) .catch(error => dispatch({ type: FAMILIAR_FOLLOWERS_FETCH_FAIL, - id: accountId, + accountId, error, skipAlert: true, })); diff --git a/src/actions/favourites.ts b/src/actions/favourites.ts index 470206751..91829713a 100644 --- a/src/actions/favourites.ts +++ b/src/actions/favourites.ts @@ -6,23 +6,22 @@ import { importFetchedStatuses } from './importer'; import type { PaginatedResponse, Status } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; -const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST'; -const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS'; -const FAVOURITED_STATUSES_FETCH_FAIL = 'FAVOURITED_STATUSES_FETCH_FAIL'; +const FAVOURITED_STATUSES_FETCH_REQUEST = 'FAVOURITED_STATUSES_FETCH_REQUEST' as const; +const FAVOURITED_STATUSES_FETCH_SUCCESS = 'FAVOURITED_STATUSES_FETCH_SUCCESS' as const; +const FAVOURITED_STATUSES_FETCH_FAIL = 'FAVOURITED_STATUSES_FETCH_FAIL' as const; -const FAVOURITED_STATUSES_EXPAND_REQUEST = 'FAVOURITED_STATUSES_EXPAND_REQUEST'; -const FAVOURITED_STATUSES_EXPAND_SUCCESS = 'FAVOURITED_STATUSES_EXPAND_SUCCESS'; -const FAVOURITED_STATUSES_EXPAND_FAIL = 'FAVOURITED_STATUSES_EXPAND_FAIL'; +const FAVOURITED_STATUSES_EXPAND_REQUEST = 'FAVOURITED_STATUSES_EXPAND_REQUEST' as const; +const FAVOURITED_STATUSES_EXPAND_SUCCESS = 'FAVOURITED_STATUSES_EXPAND_SUCCESS' as const; +const FAVOURITED_STATUSES_EXPAND_FAIL = 'FAVOURITED_STATUSES_EXPAND_FAIL' as const; -const ACCOUNT_FAVOURITED_STATUSES_FETCH_REQUEST = 'ACCOUNT_FAVOURITED_STATUSES_FETCH_REQUEST'; -const ACCOUNT_FAVOURITED_STATUSES_FETCH_SUCCESS = 'ACCOUNT_FAVOURITED_STATUSES_FETCH_SUCCESS'; -const ACCOUNT_FAVOURITED_STATUSES_FETCH_FAIL = 'ACCOUNT_FAVOURITED_STATUSES_FETCH_FAIL'; +const ACCOUNT_FAVOURITED_STATUSES_FETCH_REQUEST = 'ACCOUNT_FAVOURITED_STATUSES_FETCH_REQUEST' as const; +const ACCOUNT_FAVOURITED_STATUSES_FETCH_SUCCESS = 'ACCOUNT_FAVOURITED_STATUSES_FETCH_SUCCESS' as const; +const ACCOUNT_FAVOURITED_STATUSES_FETCH_FAIL = 'ACCOUNT_FAVOURITED_STATUSES_FETCH_FAIL' as const; -const ACCOUNT_FAVOURITED_STATUSES_EXPAND_REQUEST = 'ACCOUNT_FAVOURITED_STATUSES_EXPAND_REQUEST'; -const ACCOUNT_FAVOURITED_STATUSES_EXPAND_SUCCESS = 'ACCOUNT_FAVOURITED_STATUSES_EXPAND_SUCCESS'; -const ACCOUNT_FAVOURITED_STATUSES_EXPAND_FAIL = 'ACCOUNT_FAVOURITED_STATUSES_EXPAND_FAIL'; +const ACCOUNT_FAVOURITED_STATUSES_EXPAND_REQUEST = 'ACCOUNT_FAVOURITED_STATUSES_EXPAND_REQUEST' as const; +const ACCOUNT_FAVOURITED_STATUSES_EXPAND_SUCCESS = 'ACCOUNT_FAVOURITED_STATUSES_EXPAND_SUCCESS' as const; +const ACCOUNT_FAVOURITED_STATUSES_EXPAND_FAIL = 'ACCOUNT_FAVOURITED_STATUSES_EXPAND_FAIL' as const; const fetchFavouritedStatuses = () => (dispatch: AppDispatch, getState: () => RootState) => { @@ -44,20 +43,17 @@ const fetchFavouritedStatuses = () => const fetchFavouritedStatusesRequest = () => ({ type: FAVOURITED_STATUSES_FETCH_REQUEST, - skipLoading: true, }); -const fetchFavouritedStatusesSuccess = (statuses: APIEntity[], next: (() => Promise>) | null) => ({ +const fetchFavouritedStatusesSuccess = (statuses: Array, next: (() => Promise>) | null) => ({ type: FAVOURITED_STATUSES_FETCH_SUCCESS, statuses, next, - skipLoading: true, }); const fetchFavouritedStatusesFail = (error: unknown) => ({ type: FAVOURITED_STATUSES_FETCH_FAIL, error, - skipLoading: true, }); const expandFavouritedStatuses = () => @@ -84,7 +80,7 @@ const expandFavouritedStatusesRequest = () => ({ type: FAVOURITED_STATUSES_EXPAND_REQUEST, }); -const expandFavouritedStatusesSuccess = (statuses: APIEntity[], next: (() => Promise>) | null) => ({ +const expandFavouritedStatusesSuccess = (statuses: Array, next: (() => Promise>) | null) => ({ type: FAVOURITED_STATUSES_EXPAND_SUCCESS, statuses, next, @@ -116,22 +112,19 @@ const fetchAccountFavouritedStatuses = (accountId: string) => const fetchAccountFavouritedStatusesRequest = (accountId: string) => ({ type: ACCOUNT_FAVOURITED_STATUSES_FETCH_REQUEST, accountId, - skipLoading: true, }); -const fetchAccountFavouritedStatusesSuccess = (accountId: string, statuses: APIEntity, next: (() => Promise>) | null) => ({ +const fetchAccountFavouritedStatusesSuccess = (accountId: string, statuses: Array, next: (() => Promise>) | null) => ({ type: ACCOUNT_FAVOURITED_STATUSES_FETCH_SUCCESS, accountId, statuses, next, - skipLoading: true, }); const fetchAccountFavouritedStatusesFail = (accountId: string, error: unknown) => ({ type: ACCOUNT_FAVOURITED_STATUSES_FETCH_FAIL, accountId, error, - skipLoading: true, }); const expandAccountFavouritedStatuses = (accountId: string) => @@ -159,7 +152,7 @@ const expandAccountFavouritedStatusesRequest = (accountId: string) => ({ accountId, }); -const expandAccountFavouritedStatusesSuccess = (accountId: string, statuses: APIEntity[], next: (() => Promise>) | null) => ({ +const expandAccountFavouritedStatusesSuccess = (accountId: string, statuses: Array, next: (() => Promise>) | null) => ({ type: ACCOUNT_FAVOURITED_STATUSES_EXPAND_SUCCESS, accountId, statuses, @@ -172,6 +165,20 @@ const expandAccountFavouritedStatusesFail = (accountId: string, error: unknown) error, }); +type FavouritesAction = + ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType; + export { FAVOURITED_STATUSES_FETCH_REQUEST, FAVOURITED_STATUSES_FETCH_SUCCESS, @@ -201,4 +208,5 @@ export { expandAccountFavouritedStatusesRequest, expandAccountFavouritedStatusesSuccess, expandAccountFavouritedStatusesFail, + type FavouritesAction, }; diff --git a/src/actions/filters.ts b/src/actions/filters.ts index 35b5867cf..6e1183317 100644 --- a/src/actions/filters.ts +++ b/src/actions/filters.ts @@ -8,25 +8,25 @@ import { getClient } from '../api'; import type { FilterContext } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -const FILTERS_FETCH_REQUEST = 'FILTERS_FETCH_REQUEST'; -const FILTERS_FETCH_SUCCESS = 'FILTERS_FETCH_SUCCESS'; -const FILTERS_FETCH_FAIL = 'FILTERS_FETCH_FAIL'; +const FILTERS_FETCH_REQUEST = 'FILTERS_FETCH_REQUEST' as const; +const FILTERS_FETCH_SUCCESS = 'FILTERS_FETCH_SUCCESS' as const; +const FILTERS_FETCH_FAIL = 'FILTERS_FETCH_FAIL' as const; -const FILTER_FETCH_REQUEST = 'FILTER_FETCH_REQUEST'; -const FILTER_FETCH_SUCCESS = 'FILTER_FETCH_SUCCESS'; -const FILTER_FETCH_FAIL = 'FILTER_FETCH_FAIL'; +const FILTER_FETCH_REQUEST = 'FILTER_FETCH_REQUEST' as const; +const FILTER_FETCH_SUCCESS = 'FILTER_FETCH_SUCCESS' as const; +const FILTER_FETCH_FAIL = 'FILTER_FETCH_FAIL' as const; -const FILTERS_CREATE_REQUEST = 'FILTERS_CREATE_REQUEST'; -const FILTERS_CREATE_SUCCESS = 'FILTERS_CREATE_SUCCESS'; -const FILTERS_CREATE_FAIL = 'FILTERS_CREATE_FAIL'; +const FILTERS_CREATE_REQUEST = 'FILTERS_CREATE_REQUEST' as const; +const FILTERS_CREATE_SUCCESS = 'FILTERS_CREATE_SUCCESS' as const; +const FILTERS_CREATE_FAIL = 'FILTERS_CREATE_FAIL' as const; -const FILTERS_UPDATE_REQUEST = 'FILTERS_UPDATE_REQUEST'; -const FILTERS_UPDATE_SUCCESS = 'FILTERS_UPDATE_SUCCESS'; -const FILTERS_UPDATE_FAIL = 'FILTERS_UPDATE_FAIL'; +const FILTERS_UPDATE_REQUEST = 'FILTERS_UPDATE_REQUEST' as const; +const FILTERS_UPDATE_SUCCESS = 'FILTERS_UPDATE_SUCCESS' as const; +const FILTERS_UPDATE_FAIL = 'FILTERS_UPDATE_FAIL' as const; -const FILTERS_DELETE_REQUEST = 'FILTERS_DELETE_REQUEST'; -const FILTERS_DELETE_SUCCESS = 'FILTERS_DELETE_SUCCESS'; -const FILTERS_DELETE_FAIL = 'FILTERS_DELETE_FAIL'; +const FILTERS_DELETE_REQUEST = 'FILTERS_DELETE_REQUEST' as const; +const FILTERS_DELETE_SUCCESS = 'FILTERS_DELETE_SUCCESS' as const; +const FILTERS_DELETE_FAIL = 'FILTERS_DELETE_FAIL' as const; const messages = defineMessages({ added: { id: 'filters.added', defaultMessage: 'Filter added.' }, @@ -35,48 +35,46 @@ const messages = defineMessages({ type FilterKeywords = { keyword: string; whole_word: boolean }[]; -const fetchFilters = (fromFiltersPage = false) => +const fetchFilters = () => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; dispatch({ type: FILTERS_FETCH_REQUEST, - skipLoading: true, }); return getClient(getState).filtering.getFilters() .then((data) => dispatch({ type: FILTERS_FETCH_SUCCESS, filters: data, - skipLoading: true, })) .catch(err => dispatch({ type: FILTERS_FETCH_FAIL, err, - skipLoading: true, skipAlert: true, })); }; -const fetchFilter = (id: string) => +const fetchFilter = (filterId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - dispatch({ - type: FILTER_FETCH_REQUEST, - skipLoading: true, - }); + dispatch({ type: FILTER_FETCH_REQUEST }); - return getClient(getState).filtering.getFilter(id) - .then((data) => dispatch({ - type: FILTER_FETCH_SUCCESS, - filter: data, - skipLoading: true, - })) - .catch(err => dispatch({ - type: FILTER_FETCH_FAIL, - err, - skipLoading: true, - skipAlert: true, - })); + return getClient(getState).filtering.getFilter(filterId) + .then((data) => { + dispatch({ + type: FILTER_FETCH_SUCCESS, + filter: data, + }); + + return data; + }) + .catch(err => { + dispatch({ + type: FILTER_FETCH_FAIL, + err, + skipAlert: true, + }); + }); }; const createFilter = (title: string, expires_in: number | undefined, context: Array, hide: boolean, keywords_attributes: FilterKeywords) => @@ -92,16 +90,18 @@ const createFilter = (title: string, expires_in: number | undefined, context: Ar }).then(response => { dispatch({ type: FILTERS_CREATE_SUCCESS, filter: response }); toast.success(messages.added); + + return response; }).catch(error => { dispatch({ type: FILTERS_CREATE_FAIL, error }); }); }; -const updateFilter = (id: string, title: string, expires_in: number | undefined, context: Array, hide: boolean, keywords_attributes: FilterKeywords) => +const updateFilter = (filterId: string, title: string, expires_in: number | undefined, context: Array, hide: boolean, keywords_attributes: FilterKeywords) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: FILTERS_UPDATE_REQUEST }); - return getClient(getState).filtering.updateFilter(id, { + return getClient(getState).filtering.updateFilter(filterId, { title, context, filter_action: hide ? 'hide' : 'warn', @@ -110,19 +110,23 @@ const updateFilter = (id: string, title: string, expires_in: number | undefined, }).then(response => { dispatch({ type: FILTERS_UPDATE_SUCCESS, filter: response }); toast.success(messages.added); + + return response; }).catch(error => { - dispatch({ type: FILTERS_UPDATE_FAIL, error }); + dispatch({ type: FILTERS_UPDATE_FAIL, filterId, error }); }); }; -const deleteFilter = (id: string) => +const deleteFilter = (filterId: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: FILTERS_DELETE_REQUEST }); - return getClient(getState).filtering.deleteFilter(id).then(response => { - dispatch({ type: FILTERS_DELETE_SUCCESS, filter: response }); + return getClient(getState).filtering.deleteFilter(filterId).then(response => { + dispatch({ type: FILTERS_DELETE_SUCCESS, filterId }); toast.success(messages.removed); + + return response; }).catch(error => { - dispatch({ type: FILTERS_DELETE_FAIL, error }); + dispatch({ type: FILTERS_DELETE_FAIL, filterId, error }); }); }; diff --git a/src/actions/groups.ts b/src/actions/groups.ts index 952c8f337..99d11fbe3 100644 --- a/src/actions/groups.ts +++ b/src/actions/groups.ts @@ -4,48 +4,47 @@ import { importFetchedAccounts } from './importer'; import type { Account, PaginatedResponse } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; -const GROUP_BLOCKS_FETCH_REQUEST = 'GROUP_BLOCKS_FETCH_REQUEST'; -const GROUP_BLOCKS_FETCH_SUCCESS = 'GROUP_BLOCKS_FETCH_SUCCESS'; -const GROUP_BLOCKS_FETCH_FAIL = 'GROUP_BLOCKS_FETCH_FAIL'; +const GROUP_BLOCKS_FETCH_REQUEST = 'GROUP_BLOCKS_FETCH_REQUEST' as const; +const GROUP_BLOCKS_FETCH_SUCCESS = 'GROUP_BLOCKS_FETCH_SUCCESS' as const; +const GROUP_BLOCKS_FETCH_FAIL = 'GROUP_BLOCKS_FETCH_FAIL' as const; -const GROUP_UNBLOCK_REQUEST = 'GROUP_UNBLOCK_REQUEST'; -const GROUP_UNBLOCK_SUCCESS = 'GROUP_UNBLOCK_SUCCESS'; -const GROUP_UNBLOCK_FAIL = 'GROUP_UNBLOCK_FAIL'; +const GROUP_UNBLOCK_REQUEST = 'GROUP_UNBLOCK_REQUEST' as const; +const GROUP_UNBLOCK_SUCCESS = 'GROUP_UNBLOCK_SUCCESS' as const; +const GROUP_UNBLOCK_FAIL = 'GROUP_UNBLOCK_FAIL' as const; const groupKick = (groupId: string, accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { return getClient(getState).experimental.groups.kickGroupUsers(groupId, [accountId]); }; -const fetchGroupBlocks = (id: string) => +const fetchGroupBlocks = (groupId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - dispatch(fetchGroupBlocksRequest(id)); + dispatch(fetchGroupBlocksRequest(groupId)); - return getClient(getState).experimental.groups.getGroupBlocks(id).then(response => { + return getClient(getState).experimental.groups.getGroupBlocks(groupId).then(response => { dispatch(importFetchedAccounts(response.items)); - dispatch(fetchGroupBlocksSuccess(id, response.items, response.next)); + dispatch(fetchGroupBlocksSuccess(groupId, response.items, response.next)); }).catch(error => { - dispatch(fetchGroupBlocksFail(id, error)); + dispatch(fetchGroupBlocksFail(groupId, error)); }); }; -const fetchGroupBlocksRequest = (id: string) => ({ +const fetchGroupBlocksRequest = (groupId: string) => ({ type: GROUP_BLOCKS_FETCH_REQUEST, - id, + groupId, }); -const fetchGroupBlocksSuccess = (id: string, accounts: APIEntity[], next: (() => Promise>) | null) => ({ +const fetchGroupBlocksSuccess = (groupId: string, accounts: Array, next: (() => Promise>) | null) => ({ type: GROUP_BLOCKS_FETCH_SUCCESS, - id, + groupId, accounts, next, }); -const fetchGroupBlocksFail = (id: string, error: unknown) => ({ +const fetchGroupBlocksFail = (groupId: string, error: unknown) => ({ type: GROUP_BLOCKS_FETCH_FAIL, - id, + groupId, error, skipNotFound: true, }); diff --git a/src/actions/history.ts b/src/actions/history.ts index 71d53f3c8..4ed8a4f0e 100644 --- a/src/actions/history.ts +++ b/src/actions/history.ts @@ -2,27 +2,25 @@ import { getClient } from 'soapbox/api'; import { importFetchedAccounts } from './importer'; +import type { StatusEdit } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; -const HISTORY_FETCH_REQUEST = 'HISTORY_FETCH_REQUEST'; -const HISTORY_FETCH_SUCCESS = 'HISTORY_FETCH_SUCCESS'; -const HISTORY_FETCH_FAIL = 'HISTORY_FETCH_FAIL'; +const HISTORY_FETCH_REQUEST = 'HISTORY_FETCH_REQUEST' as const; +const HISTORY_FETCH_SUCCESS = 'HISTORY_FETCH_SUCCESS' as const; +const HISTORY_FETCH_FAIL = 'HISTORY_FETCH_FAIL' as const; const fetchHistory = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { const loading = getState().history.getIn([statusId, 'loading']); - if (loading) { - return; - } + if (loading) return; dispatch(fetchHistoryRequest(statusId)); return getClient(getState()).statuses.getStatusHistory(statusId).then(data => { dispatch(importFetchedAccounts(data.map((x) => x.account))); dispatch(fetchHistorySuccess(statusId, data)); - }).catch(error => dispatch(fetchHistoryFail(error))); + }).catch(error => dispatch(fetchHistoryFail(statusId, error))); }; const fetchHistoryRequest = (statusId: string) => ({ @@ -30,17 +28,20 @@ const fetchHistoryRequest = (statusId: string) => ({ statusId, }); -const fetchHistorySuccess = (statusId: String, history: APIEntity[]) => ({ +const fetchHistorySuccess = (statusId: string, history: Array) => ({ type: HISTORY_FETCH_SUCCESS, statusId, history, }); -const fetchHistoryFail = (error: unknown) => ({ +const fetchHistoryFail = (statusId: string, error: unknown) => ({ type: HISTORY_FETCH_FAIL, + statusId, error, }); +type HistoryAction = ReturnType | ReturnType | ReturnType; + export { HISTORY_FETCH_REQUEST, HISTORY_FETCH_SUCCESS, @@ -49,4 +50,5 @@ export { fetchHistoryRequest, fetchHistorySuccess, fetchHistoryFail, + type HistoryAction, }; \ No newline at end of file diff --git a/src/actions/import-data.ts b/src/actions/import-data.ts index 63d4b02a3..9570d41fe 100644 --- a/src/actions/import-data.ts +++ b/src/actions/import-data.ts @@ -6,17 +6,17 @@ import { getClient } from '../api'; import type { RootState } from 'soapbox/store'; -const IMPORT_FOLLOWS_REQUEST = 'IMPORT_FOLLOWS_REQUEST'; -const IMPORT_FOLLOWS_SUCCESS = 'IMPORT_FOLLOWS_SUCCESS'; -const IMPORT_FOLLOWS_FAIL = 'IMPORT_FOLLOWS_FAIL'; +const IMPORT_FOLLOWS_REQUEST = 'IMPORT_FOLLOWS_REQUEST' as const; +const IMPORT_FOLLOWS_SUCCESS = 'IMPORT_FOLLOWS_SUCCESS' as const; +const IMPORT_FOLLOWS_FAIL = 'IMPORT_FOLLOWS_FAIL' as const; -const IMPORT_BLOCKS_REQUEST = 'IMPORT_BLOCKS_REQUEST'; -const IMPORT_BLOCKS_SUCCESS = 'IMPORT_BLOCKS_SUCCESS'; -const IMPORT_BLOCKS_FAIL = 'IMPORT_BLOCKS_FAIL'; +const IMPORT_BLOCKS_REQUEST = 'IMPORT_BLOCKS_REQUEST' as const; +const IMPORT_BLOCKS_SUCCESS = 'IMPORT_BLOCKS_SUCCESS' as const; +const IMPORT_BLOCKS_FAIL = 'IMPORT_BLOCKS_FAIL' as const; -const IMPORT_MUTES_REQUEST = 'IMPORT_MUTES_REQUEST'; -const IMPORT_MUTES_SUCCESS = 'IMPORT_MUTES_SUCCESS'; -const IMPORT_MUTES_FAIL = 'IMPORT_MUTES_FAIL'; +const IMPORT_MUTES_REQUEST = 'IMPORT_MUTES_REQUEST' as const; +const IMPORT_MUTES_SUCCESS = 'IMPORT_MUTES_SUCCESS' as const; +const IMPORT_MUTES_FAIL = 'IMPORT_MUTES_FAIL' as const; type ImportDataActions = { type: typeof IMPORT_FOLLOWS_REQUEST @@ -29,7 +29,7 @@ type ImportDataActions = { | typeof IMPORT_MUTES_SUCCESS | typeof IMPORT_MUTES_FAIL; error?: any; - config?: string; + response?: string; } const messages = defineMessages({ @@ -38,40 +38,34 @@ const messages = defineMessages({ mutesSuccess: { id: 'import_data.success.mutes', defaultMessage: 'Mutes imported successfully' }, }); -const importFollows = (params: FormData) => +const importFollows = (list: File | string) => (dispatch: React.Dispatch, getState: () => RootState) => { dispatch({ type: IMPORT_FOLLOWS_REQUEST }); - return getClient(getState).request('/api/pleroma/follow_import', { - method: 'POST', body: params, - }).then(response => { + return getClient(getState).settings.importFollows(list).then(response => { toast.success(messages.followersSuccess); - dispatch({ type: IMPORT_FOLLOWS_SUCCESS, config: response.json }); + dispatch({ type: IMPORT_FOLLOWS_SUCCESS, response }); }).catch(error => { dispatch({ type: IMPORT_FOLLOWS_FAIL, error }); }); }; -const importBlocks = (params: FormData) => +const importBlocks = (list: File | string) => (dispatch: React.Dispatch, getState: () => RootState) => { dispatch({ type: IMPORT_BLOCKS_REQUEST }); - return getClient(getState).request('/api/pleroma/blocks_import', { - method: 'POST', body: params, - }).then(response => { + return getClient(getState).settings.importBlocks(list).then(response => { toast.success(messages.blocksSuccess); - dispatch({ type: IMPORT_BLOCKS_SUCCESS, config: response.json }); + dispatch({ type: IMPORT_BLOCKS_SUCCESS, response }); }).catch(error => { dispatch({ type: IMPORT_BLOCKS_FAIL, error }); }); }; -const importMutes = (params: FormData) => +const importMutes = (list: File | string) => (dispatch: React.Dispatch, getState: () => RootState) => { dispatch({ type: IMPORT_MUTES_REQUEST }); - return getClient(getState).request('/api/pleroma/mutes_import', { - method: 'POST', body: params, - }).then(response => { + return getClient(getState).settings.importMutes(list).then(response => { toast.success(messages.mutesSuccess); - dispatch({ type: IMPORT_MUTES_SUCCESS, config: response.json }); + dispatch({ type: IMPORT_MUTES_SUCCESS, response }); }).catch(error => { dispatch({ type: IMPORT_MUTES_FAIL, error }); }); diff --git a/src/actions/importer/index.ts b/src/actions/importer/index.ts index ec361438c..2e76ebf6a 100644 --- a/src/actions/importer/index.ts +++ b/src/actions/importer/index.ts @@ -1,61 +1,57 @@ +import { accountSchema, groupSchema, type Account as BaseAccount, type Group, type Poll, type Status as BaseStatus } from 'pl-api'; + import { importEntities } from 'soapbox/entity-store/actions'; import { Entities } from 'soapbox/entity-store/entities'; -import { accountSchema } from 'soapbox/schemas'; +import { normalizeAccount, normalizeGroup } from 'soapbox/normalizers'; import { filteredArray } from 'soapbox/schemas/utils'; -import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; +import type { AppDispatch } from 'soapbox/store'; -const ACCOUNT_IMPORT = 'ACCOUNT_IMPORT'; +const ACCOUNT_IMPORT = 'ACCOUNT_IMPORT'; const ACCOUNTS_IMPORT = 'ACCOUNTS_IMPORT'; -const STATUS_IMPORT = 'STATUS_IMPORT'; +const STATUS_IMPORT = 'STATUS_IMPORT'; const STATUSES_IMPORT = 'STATUSES_IMPORT'; -const POLLS_IMPORT = 'POLLS_IMPORT'; +const POLLS_IMPORT = 'POLLS_IMPORT'; const ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP = 'ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP'; -const importAccount = (data: APIEntity) => - (dispatch: AppDispatch, _getState: () => RootState) => { - dispatch({ type: ACCOUNT_IMPORT, account: data }); - try { - const account = accountSchema.parse(data); - dispatch(importEntities([account], Entities.ACCOUNTS)); - } catch (e) { - // - } - }; +const importAccount = (data: BaseAccount) => importAccounts([data]); -const importAccounts = (data: APIEntity[]) => - (dispatch: AppDispatch, _getState: () => RootState) => { - dispatch({ type: ACCOUNTS_IMPORT, accounts: data }); - try { - const accounts = filteredArray(accountSchema).parse(data); - dispatch(importEntities(accounts, Entities.ACCOUNTS)); - } catch (e) { - // - } - }; +const importAccounts = (data: Array) => (dispatch: AppDispatch) => { + dispatch({ type: ACCOUNTS_IMPORT, accounts: data }); + try { + const accounts = filteredArray(accountSchema).parse(data).map(normalizeAccount); + dispatch(importEntities(accounts, Entities.ACCOUNTS)); + } catch (e) { + // + } +}; -const importStatus = (status: APIEntity, idempotencyKey?: string) => ({ type: STATUS_IMPORT, status, idempotencyKey }); +const importGroup = (data: Group) => importGroups([data]); -const importStatuses = (statuses: APIEntity[]) => ({ type: STATUSES_IMPORT, statuses }); +const importGroups = (data: Array) => (dispatch: AppDispatch) => { + try { + const groups = filteredArray(groupSchema).parse(data).map(normalizeGroup); + dispatch(importEntities(groups, Entities.GROUPS)); + } catch (e) { + // + } +}; -const importPolls = (polls: APIEntity[]) => - ({ type: POLLS_IMPORT, polls }); +const importStatus = (status: BaseStatus, idempotencyKey?: string) => ({ type: STATUS_IMPORT, status, idempotencyKey }); -const importFetchedAccount = (account: APIEntity) => +const importStatuses = (statuses: Array) => ({ type: STATUSES_IMPORT, statuses }); + +const importPolls = (polls: Array) => ({ type: POLLS_IMPORT, polls }); + +const importFetchedAccount = (account: BaseAccount) => importFetchedAccounts([account]); -const importFetchedAccounts = (accounts: APIEntity[], args = { should_refetch: false }) => { - const { should_refetch } = args; - const normalAccounts: APIEntity[] = []; +const importFetchedAccounts = (accounts: Array) => { + const normalAccounts: Array = []; - const processAccount = (account: APIEntity) => { + const processAccount = (account: BaseAccount) => { if (!account.id) return; - if (should_refetch) { - account.should_refetch = true; - } - normalAccounts.push(account); if (account.moved) { @@ -68,36 +64,41 @@ const importFetchedAccounts = (accounts: APIEntity[], args = { should_refetch: f return importAccounts(normalAccounts); }; -const importFetchedStatus = (status: APIEntity, idempotencyKey?: string) => +const importFetchedStatus = (status: BaseStatus, idempotencyKey?: string) => (dispatch: AppDispatch) => { // Skip broken statuses if (isBroken(status)) return; if (status.reblog?.id) { - dispatch(importFetchedStatus(status.reblog)); + dispatch(importFetchedStatus(status.reblog as BaseStatus)); } // Fedibird quotes if (status.quote?.id) { - dispatch(importFetchedStatus(status.quote)); + dispatch(importFetchedStatus(status.quote as BaseStatus)); } + // WIP restore this // Fedibird quote from reblog - if (status.reblog?.quote?.id) { - dispatch(importFetchedStatus(status.reblog.quote)); - } + // 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: APIEntity) => { +const isBroken = (status: BaseStatus) => { try { // Skip empty accounts // https://gitlab.com/soapbox-pub/soapbox/-/issues/424 @@ -111,29 +112,29 @@ const isBroken = (status: APIEntity) => { } }; -const importFetchedStatuses = (statuses: APIEntity[]) => (dispatch: AppDispatch) => { - const accounts: APIEntity[] = []; - const normalStatuses: APIEntity[] = []; - const polls: APIEntity[] = []; +const importFetchedStatuses = (statuses: Array) => (dispatch: AppDispatch) => { + const accounts: Array = []; + const normalStatuses: Array = []; + const polls: Array = []; - const processStatus = (status: APIEntity) => { + const processStatus = (status: BaseStatus) => { // Skip broken statuses if (isBroken(status)) return; normalStatuses.push(status); accounts.push(status.account); - if (status.accounts) { - accounts.push(...status.accounts); - } + // if (status.accounts) { + // accounts.push(...status.accounts); + // } if (status.reblog?.id) { - processStatus(status.reblog); + processStatus(status.reblog as BaseStatus); } // Fedibird quotes if (status.quote?.id) { - processStatus(status.quote); + processStatus(status.quote as BaseStatus); } if (status.poll?.id) { @@ -148,7 +149,7 @@ const importFetchedStatuses = (statuses: APIEntity[]) => (dispatch: AppDispatch) dispatch(importStatuses(normalStatuses)); }; -const importFetchedPoll = (poll: APIEntity) => +const importFetchedPoll = (poll: Poll) => (dispatch: AppDispatch) => { dispatch(importPolls([poll])); }; @@ -165,6 +166,8 @@ export { ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP, importAccount, importAccounts, + importGroup, + importGroups, importStatus, importStatuses, importPolls, diff --git a/src/actions/instance.ts b/src/actions/instance.ts index 898972ee1..79f354682 100644 --- a/src/actions/instance.ts +++ b/src/actions/instance.ts @@ -2,10 +2,11 @@ import { getAuthUserUrl, getMeUrl } from 'soapbox/utils/auth'; import { getClient } from '../api'; +import type { Instance } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -const INSTANCE_FETCH_FAIL = 'INSTANCE_FETCH_FAIL' as const; const INSTANCE_FETCH_SUCCESS = 'INSTANCE_FETCH_SUCCESS' as const; +const INSTANCE_FETCH_FAIL = 'INSTANCE_FETCH_FAIL' as const; /** Figure out the appropriate instance to fetch depending on the state */ const getHost = (state: RootState) => { @@ -18,19 +19,35 @@ const getHost = (state: RootState) => { } }; +interface InstanceFetchSuccessAction { + type: typeof INSTANCE_FETCH_SUCCESS; + instance: Instance; +} + +interface InstanceFetchFailAction { + type: typeof INSTANCE_FETCH_FAIL; + error: any; +} + const fetchInstance = () => async (dispatch: AppDispatch, getState: () => RootState) => { try { const instance = await getClient(getState).instance.getInstance(); - dispatch({ type: INSTANCE_FETCH_SUCCESS, instance }); + const action: InstanceFetchSuccessAction = { type: INSTANCE_FETCH_SUCCESS, instance }; + dispatch(action); } catch (error) { dispatch({ type: INSTANCE_FETCH_FAIL, error }); } }; +type InstanceAction = + InstanceFetchSuccessAction + | InstanceFetchFailAction + export { - INSTANCE_FETCH_FAIL, INSTANCE_FETCH_SUCCESS, + INSTANCE_FETCH_FAIL, getHost, fetchInstance, + type InstanceAction, }; diff --git a/src/actions/interactions.ts b/src/actions/interactions.ts index e12aba821..80ff50286 100644 --- a/src/actions/interactions.ts +++ b/src/actions/interactions.ts @@ -2,7 +2,6 @@ import { defineMessages } from 'react-intl'; import toast, { type IToastOptions } from 'soapbox/toast'; import { isLoggedIn } from 'soapbox/utils/auth'; -import { getFeatures } from 'soapbox/utils/features'; import { getClient } from '../api'; @@ -10,69 +9,68 @@ import { fetchRelationships } from './accounts'; import { importFetchedAccounts, importFetchedStatus } from './importer'; import { openModal } from './modals'; -import type { Account, PaginatedResponse } from 'pl-api'; +import type { Account, EmojiReaction, PaginatedResponse, Status } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity, Status as StatusEntity } from 'soapbox/types/entities'; const REBLOG_REQUEST = 'REBLOG_REQUEST' as const; const REBLOG_SUCCESS = 'REBLOG_SUCCESS' as const; -const REBLOG_FAIL = 'REBLOG_FAIL' as const; +const REBLOG_FAIL = 'REBLOG_FAIL' as const; const FAVOURITE_REQUEST = 'FAVOURITE_REQUEST' as const; const FAVOURITE_SUCCESS = 'FAVOURITE_SUCCESS' as const; -const FAVOURITE_FAIL = 'FAVOURITE_FAIL' as const; +const FAVOURITE_FAIL = 'FAVOURITE_FAIL' as const; const DISLIKE_REQUEST = 'DISLIKE_REQUEST' as const; const DISLIKE_SUCCESS = 'DISLIKE_SUCCESS' as const; -const DISLIKE_FAIL = 'DISLIKE_FAIL' as const; +const DISLIKE_FAIL = 'DISLIKE_FAIL' as const; const UNREBLOG_REQUEST = 'UNREBLOG_REQUEST' as const; const UNREBLOG_SUCCESS = 'UNREBLOG_SUCCESS' as const; -const UNREBLOG_FAIL = 'UNREBLOG_FAIL' as const; +const UNREBLOG_FAIL = 'UNREBLOG_FAIL' as const; const UNFAVOURITE_REQUEST = 'UNFAVOURITE_REQUEST' as const; const UNFAVOURITE_SUCCESS = 'UNFAVOURITE_SUCCESS' as const; -const UNFAVOURITE_FAIL = 'UNFAVOURITE_FAIL' as const; +const UNFAVOURITE_FAIL = 'UNFAVOURITE_FAIL' as const; const UNDISLIKE_REQUEST = 'UNDISLIKE_REQUEST' as const; const UNDISLIKE_SUCCESS = 'UNDISLIKE_SUCCESS' as const; -const UNDISLIKE_FAIL = 'UNDISLIKE_FAIL' as const; +const UNDISLIKE_FAIL = 'UNDISLIKE_FAIL' as const; const REBLOGS_FETCH_REQUEST = 'REBLOGS_FETCH_REQUEST' as const; const REBLOGS_FETCH_SUCCESS = 'REBLOGS_FETCH_SUCCESS' as const; -const REBLOGS_FETCH_FAIL = 'REBLOGS_FETCH_FAIL' as const; +const REBLOGS_FETCH_FAIL = 'REBLOGS_FETCH_FAIL' as const; const FAVOURITES_FETCH_REQUEST = 'FAVOURITES_FETCH_REQUEST' as const; const FAVOURITES_FETCH_SUCCESS = 'FAVOURITES_FETCH_SUCCESS' as const; -const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL' as const; +const FAVOURITES_FETCH_FAIL = 'FAVOURITES_FETCH_FAIL' as const; const DISLIKES_FETCH_REQUEST = 'DISLIKES_FETCH_REQUEST' as const; const DISLIKES_FETCH_SUCCESS = 'DISLIKES_FETCH_SUCCESS' as const; -const DISLIKES_FETCH_FAIL = 'DISLIKES_FETCH_FAIL' as const; +const DISLIKES_FETCH_FAIL = 'DISLIKES_FETCH_FAIL' as const; const REACTIONS_FETCH_REQUEST = 'REACTIONS_FETCH_REQUEST' as const; const REACTIONS_FETCH_SUCCESS = 'REACTIONS_FETCH_SUCCESS' as const; -const REACTIONS_FETCH_FAIL = 'REACTIONS_FETCH_FAIL' as const; +const REACTIONS_FETCH_FAIL = 'REACTIONS_FETCH_FAIL' as const; const PIN_REQUEST = 'PIN_REQUEST' as const; const PIN_SUCCESS = 'PIN_SUCCESS' as const; -const PIN_FAIL = 'PIN_FAIL' as const; +const PIN_FAIL = 'PIN_FAIL' as const; const UNPIN_REQUEST = 'UNPIN_REQUEST' as const; const UNPIN_SUCCESS = 'UNPIN_SUCCESS' as const; -const UNPIN_FAIL = 'UNPIN_FAIL' as const; +const UNPIN_FAIL = 'UNPIN_FAIL' as const; const BOOKMARK_REQUEST = 'BOOKMARK_REQUEST' as const; const BOOKMARK_SUCCESS = 'BOOKMARKED_SUCCESS' as const; -const BOOKMARK_FAIL = 'BOOKMARKED_FAIL' as const; +const BOOKMARK_FAIL = 'BOOKMARKED_FAIL' as const; const UNBOOKMARK_REQUEST = 'UNBOOKMARKED_REQUEST' as const; const UNBOOKMARK_SUCCESS = 'UNBOOKMARKED_SUCCESS' as const; -const UNBOOKMARK_FAIL = 'UNBOOKMARKED_FAIL' as const; +const UNBOOKMARK_FAIL = 'UNBOOKMARKED_FAIL' as const; const REMOTE_INTERACTION_REQUEST = 'REMOTE_INTERACTION_REQUEST' as const; const REMOTE_INTERACTION_SUCCESS = 'REMOTE_INTERACTION_SUCCESS' as const; -const REMOTE_INTERACTION_FAIL = 'REMOTE_INTERACTION_FAIL' as const; +const REMOTE_INTERACTION_FAIL = 'REMOTE_INTERACTION_FAIL' as const; const FAVOURITES_EXPAND_SUCCESS = 'FAVOURITES_EXPAND_SUCCESS' as const; const FAVOURITES_EXPAND_FAIL = 'FAVOURITES_EXPAND_FAIL' as const; @@ -90,36 +88,36 @@ const messages = defineMessages({ selectFolder: { id: 'status.bookmark.select_folder', defaultMessage: 'Select folder' }, }); -const reblog = (status: StatusEntity) => +const reblog = (status: Pick) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - dispatch(reblogRequest(status)); + dispatch(reblogRequest(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)); - dispatch(reblogSuccess(status)); + if (response.reblog) dispatch(importFetchedStatus(response.reblog as Status)); + dispatch(reblogSuccess(response)); }).catch(error => { - dispatch(reblogFail(status, error)); + dispatch(reblogFail(status.id, error)); }); }; -const unreblog = (status: StatusEntity) => +const unreblog = (status: Pick) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - dispatch(unreblogRequest(status)); + dispatch(unreblogRequest(status.id)); return getClient(getState()).statuses.unreblogStatus(status.id).then(() => { - dispatch(unreblogSuccess(status)); + dispatch(unreblogSuccess(status.id)); }).catch(error => { - dispatch(unreblogFail(status, error)); + dispatch(unreblogFail(status.id, error)); }); }; -const toggleReblog = (status: StatusEntity) => +const toggleReblog = (status: Pick) => (dispatch: AppDispatch) => { if (status.reblogged) { dispatch(unreblog(status)); @@ -128,71 +126,66 @@ const toggleReblog = (status: StatusEntity) => } }; -const reblogRequest = (status: StatusEntity) => ({ +const reblogRequest = (statusId: string) => ({ type: REBLOG_REQUEST, - status: status, - skipLoading: true, + statusId, }); -const reblogSuccess = (status: StatusEntity) => ({ +const reblogSuccess = (status: Status) => ({ type: REBLOG_SUCCESS, - status: status, - skipLoading: true, + status, + statusId: status.id, }); -const reblogFail = (status: StatusEntity, error: unknown) => ({ +const reblogFail = (statusId: string, error: unknown) => ({ type: REBLOG_FAIL, - status: status, - error: error, - skipLoading: true, + statusId, + error, }); -const unreblogRequest = (status: StatusEntity) => ({ +const unreblogRequest = (statusId: string) => ({ type: UNREBLOG_REQUEST, - status: status, - skipLoading: true, + statusId, }); -const unreblogSuccess = (status: StatusEntity) => ({ +const unreblogSuccess = (statusId: string) => ({ type: UNREBLOG_SUCCESS, - status: status, - skipLoading: true, + statusId, }); -const unreblogFail = (status: StatusEntity, error: unknown) => ({ +const unreblogFail = (statusId: string, error: unknown) => ({ type: UNREBLOG_FAIL, - status: status, - error: error, - skipLoading: true, + statusId, + error, }); -const favourite = (status: StatusEntity) => +const favourite = (status: Pick) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - dispatch(favouriteRequest(status)); + dispatch(favouriteRequest(status.id)); - return getClient(getState()).statuses.favouriteStatus(status.id).then(() => { - dispatch(favouriteSuccess(status)); + return getClient(getState()).statuses.favouriteStatus(status.id).then((response) => { + dispatch(favouriteSuccess(response)); }).catch((error) => { - dispatch(favouriteFail(status, error)); + dispatch(favouriteFail(status.id, error)); }); }; -const unfavourite = (status: StatusEntity) => +const unfavourite = (status: Pick) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - dispatch(unfavouriteRequest(status)); + dispatch(unfavouriteRequest(status.id)); - return getClient(getState()).statuses.unfavouriteStatus(status.id).then(() => { - dispatch(unfavouriteSuccess(status)); + return getClient(getState()).statuses.unfavouriteStatus(status.id).then((response) => { + dispatch(unfavouriteSuccess(response)); }).catch(error => { - dispatch(unfavouriteFail(status, error)); + dispatch(unfavouriteFail(status.id, error)); }); }; -const toggleFavourite = (status: StatusEntity) => +const toggleFavourite = (status: Pick) => (dispatch: AppDispatch) => { if (status.favourited) { dispatch(unfavourite(status)); @@ -201,71 +194,67 @@ const toggleFavourite = (status: StatusEntity) => } }; -const favouriteRequest = (status: StatusEntity) => ({ +const favouriteRequest = (statusId: string) => ({ type: FAVOURITE_REQUEST, - status: status, - skipLoading: true, + statusId, }); -const favouriteSuccess = (status: StatusEntity) => ({ +const favouriteSuccess = (status: Status) => ({ type: FAVOURITE_SUCCESS, - status: status, - skipLoading: true, + status, + statusId: status.id, }); -const favouriteFail = (status: StatusEntity, error: unknown) => ({ +const favouriteFail = (statusId: string, error: unknown) => ({ type: FAVOURITE_FAIL, - status: status, - error: error, - skipLoading: true, + statusId, + error, }); -const unfavouriteRequest = (status: StatusEntity) => ({ +const unfavouriteRequest = (statusId: string) => ({ type: UNFAVOURITE_REQUEST, - status: status, - skipLoading: true, + statusId, }); -const unfavouriteSuccess = (status: StatusEntity) => ({ +const unfavouriteSuccess = (status: Status) => ({ type: UNFAVOURITE_SUCCESS, - status: status, - skipLoading: true, + status, + statusId: status.id, }); -const unfavouriteFail = (status: StatusEntity, error: unknown) => ({ +const unfavouriteFail = (statusId: string, error: unknown) => ({ type: UNFAVOURITE_FAIL, - status: status, - error: error, - skipLoading: true, + statusId, + error, }); -const dislike = (status: StatusEntity) => +const dislike = (status: Pick) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - dispatch(dislikeRequest(status)); + dispatch(dislikeRequest(status.id)); - return getClient(getState).request(`/api/friendica/statuses/${status.id}/dislike`, { method: 'POST' }).then(() => { - dispatch(dislikeSuccess(status)); + return getClient(getState).statuses.dislikeStatus(status.id).then((response) => { + dispatch(dislikeSuccess(response)); }).catch((error) => { - dispatch(dislikeFail(status, error)); + dispatch(dislikeFail(status.id, error)); }); }; -const undislike = (status: StatusEntity) => +const undislike = (status: Pick) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - dispatch(undislikeRequest(status)); + dispatch(undislikeRequest(status.id)); - return getClient(getState).request(`/api/friendica/statuses/${status.id}/undislike`, { method: 'POST' }).then(() => { - dispatch(undislikeSuccess(status)); + return getClient(getState).statuses.undislikeStatus(status.id).then((response) => { + dispatch(undislikeSuccess(response)); }).catch(error => { - dispatch(undislikeFail(status, error)); + dispatch(undislikeFail(status.id, error)); }); }; -const toggleDislike = (status: StatusEntity) => +const toggleDislike = (status: Pick) => (dispatch: AppDispatch) => { if (status.disliked) { dispatch(undislike(status)); @@ -274,56 +263,51 @@ const toggleDislike = (status: StatusEntity) => } }; -const dislikeRequest = (status: StatusEntity) => ({ +const dislikeRequest = (statusId: string) => ({ type: DISLIKE_REQUEST, - status: status, - skipLoading: true, + statusId, }); -const dislikeSuccess = (status: StatusEntity) => ({ +const dislikeSuccess = (status: Status) => ({ type: DISLIKE_SUCCESS, - status: status, - skipLoading: true, + status, + statusId: status.id, }); -const dislikeFail = (status: StatusEntity, error: unknown) => ({ +const dislikeFail = (statusId: string, error: unknown) => ({ type: DISLIKE_FAIL, - status: status, - error: error, - skipLoading: true, + statusId, + error, }); -const undislikeRequest = (status: StatusEntity) => ({ +const undislikeRequest = (statusId: string) => ({ type: UNDISLIKE_REQUEST, - status: status, - skipLoading: true, + statusId, }); -const undislikeSuccess = (status: StatusEntity) => ({ +const undislikeSuccess = (status: Status) => ({ type: UNDISLIKE_SUCCESS, - status: status, - skipLoading: true, + status, + statusId: status.id, }); -const undislikeFail = (status: StatusEntity, error: unknown) => ({ +const undislikeFail = (statusId: string, error: unknown) => ({ type: UNDISLIKE_FAIL, - status: status, - error: error, - skipLoading: true, + statusId, + error, }); -const bookmark = (status: StatusEntity, folderId?: string) => +const bookmark = (status: Pick, folderId?: string) => (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); - const instance = state.instance; - const features = getFeatures(instance); + const features = state.auth.client.features; - dispatch(bookmarkRequest(status)); + dispatch(bookmarkRequest(status.id)); return getClient(getState()).statuses.bookmarkStatus(status.id, folderId).then((response) => { dispatch(importFetchedStatus(response)); - dispatch(bookmarkSuccess(status, response)); + dispatch(bookmarkSuccess(response)); let opts: IToastOptions = { actionLabel: messages.view, @@ -341,25 +325,25 @@ const bookmark = (status: StatusEntity, folderId?: string) => toast.success(typeof folderId === 'string' ? messages.folderChanged : messages.bookmarkAdded, opts); }).catch((error) => { - dispatch(bookmarkFail(status, error)); + dispatch(bookmarkFail(status.id, error)); }); }; -const unbookmark = (status: StatusEntity) => +const unbookmark = (status: Pick) => (dispatch: AppDispatch, getState: () => RootState) => { - dispatch(unbookmarkRequest(status)); + dispatch(unbookmarkRequest(status.id)); return getClient(getState()).statuses.unbookmarkStatus(status.id).then(response => { dispatch(importFetchedStatus(response)); - dispatch(unbookmarkSuccess(status, response)); + dispatch(unbookmarkSuccess(response)); toast.success(messages.bookmarkRemoved); }).catch(error => { - dispatch(unbookmarkFail(status, error)); + dispatch(unbookmarkFail(status.id, error)); }); }; -const toggleBookmark = (status: StatusEntity) => - (dispatch: AppDispatch, getState: () => RootState) => { +const toggleBookmark = (status: Pick) => + (dispatch: AppDispatch) => { if (status.bookmarked) { dispatch(unbookmark(status)); } else { @@ -367,266 +351,264 @@ const toggleBookmark = (status: StatusEntity) => } }; -const bookmarkRequest = (status: StatusEntity) => ({ +const bookmarkRequest = (statusId: string) => ({ type: BOOKMARK_REQUEST, - status: status, + statusId, }); -const bookmarkSuccess = (status: StatusEntity, response: APIEntity) => ({ +const bookmarkSuccess = (status: Status) => ({ type: BOOKMARK_SUCCESS, - status: status, - response: response, + status, + statusId: status.id, }); -const bookmarkFail = (status: StatusEntity, error: unknown) => ({ +const bookmarkFail = (statusId: string, error: unknown) => ({ type: BOOKMARK_FAIL, - status: status, - error: error, -}); - -const unbookmarkRequest = (status: StatusEntity) => ({ - type: UNBOOKMARK_REQUEST, - status: status, -}); - -const unbookmarkSuccess = (status: StatusEntity, response: APIEntity) => ({ - type: UNBOOKMARK_SUCCESS, - status: status, - response: response, -}); - -const unbookmarkFail = (status: StatusEntity, error: unknown) => ({ - type: UNBOOKMARK_FAIL, - status: status, + statusId, error, }); -const fetchReblogs = (id: string) => +const unbookmarkRequest = (statusId: string) => ({ + type: UNBOOKMARK_REQUEST, + statusId, +}); + +const unbookmarkSuccess = (status: Status) => ({ + type: UNBOOKMARK_SUCCESS, + status, + statusId: status.id, +}); + +const unbookmarkFail = (statusId: string, error: unknown) => ({ + type: UNBOOKMARK_FAIL, + statusId, + error, +}); + +const fetchReblogs = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - dispatch(fetchReblogsRequest(id)); + dispatch(fetchReblogsRequest(statusId)); - return getClient(getState()).statuses.getRebloggedBy(id).then(response => { + return getClient(getState()).statuses.getRebloggedBy(statusId).then(response => { dispatch(importFetchedAccounts(response.items)); dispatch(fetchRelationships(response.items.map((item) => item.id))); - dispatch(fetchReblogsSuccess(id, response.items, response.next)); + dispatch(fetchReblogsSuccess(statusId, response.items, response.next)); }).catch(error => { - dispatch(fetchReblogsFail(id, error)); + dispatch(fetchReblogsFail(statusId, error)); }); }; -const fetchReblogsRequest = (id: string) => ({ +const fetchReblogsRequest = (statusId: string) => ({ type: REBLOGS_FETCH_REQUEST, - id, + statusId, }); -const fetchReblogsSuccess = (id: string, accounts: APIEntity[], next: AccountListLink | null) => ({ +const fetchReblogsSuccess = (statusId: string, accounts: Array, next: AccountListLink | null) => ({ type: REBLOGS_FETCH_SUCCESS, - id, + statusId, accounts, next, }); -const fetchReblogsFail = (id: string, error: unknown) => ({ +const fetchReblogsFail = (statusId: string, error: unknown) => ({ type: REBLOGS_FETCH_FAIL, - id, + statusId, error, }); -const expandReblogs = (id: string, next: AccountListLink) => +const expandReblogs = (statusId: string, next: AccountListLink) => (dispatch: AppDispatch, getState: () => RootState) => { next().then(response => { dispatch(importFetchedAccounts(response.items)); - dispatch(fetchRelationships(response.items.map((item: APIEntity) => item.id))); - dispatch(expandReblogsSuccess(id, response.items, response.next)); + dispatch(fetchRelationships(response.items.map((item) => item.id))); + dispatch(expandReblogsSuccess(statusId, response.items, response.next)); }).catch(error => { - dispatch(expandReblogsFail(id, error)); + dispatch(expandReblogsFail(statusId, error)); }); }; -const expandReblogsSuccess = (id: string, accounts: APIEntity[], next: AccountListLink | null) => ({ +const expandReblogsSuccess = (statusId: string, accounts: Array, next: AccountListLink | null) => ({ type: REBLOGS_EXPAND_SUCCESS, - id, + statusId, accounts, next, }); -const expandReblogsFail = (id: string, error: unknown) => ({ +const expandReblogsFail = (statusId: string, error: unknown) => ({ type: REBLOGS_EXPAND_FAIL, - id, + statusId, error, }); -const fetchFavourites = (id: string) => +const fetchFavourites = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - dispatch(fetchFavouritesRequest(id)); + dispatch(fetchFavouritesRequest(statusId)); - return getClient(getState()).statuses.getFavouritedBy(id).then(response => { + return getClient(getState()).statuses.getFavouritedBy(statusId).then(response => { dispatch(importFetchedAccounts(response.items)); dispatch(fetchRelationships(response.items.map((item) => item.id))); - dispatch(fetchFavouritesSuccess(id, response.items, response.next)); + dispatch(fetchFavouritesSuccess(statusId, response.items, response.next)); }).catch(error => { - dispatch(fetchFavouritesFail(id, error)); + dispatch(fetchFavouritesFail(statusId, error)); }); }; -const fetchFavouritesRequest = (id: string) => ({ +const fetchFavouritesRequest = (statusId: string) => ({ type: FAVOURITES_FETCH_REQUEST, - id, + statusId, }); -const fetchFavouritesSuccess = (id: string, accounts: APIEntity[], next: AccountListLink | null) => ({ +const fetchFavouritesSuccess = (statusId: string, accounts: Array, next: AccountListLink | null) => ({ type: FAVOURITES_FETCH_SUCCESS, - id, + statusId, accounts, next, }); -const fetchFavouritesFail = (id: string, error: unknown) => ({ +const fetchFavouritesFail = (statusId: string, error: unknown) => ({ type: FAVOURITES_FETCH_FAIL, - id, + statusId, error, }); -const expandFavourites = (id: string, next: AccountListLink) => +const expandFavourites = (statusId: string, next: AccountListLink) => (dispatch: AppDispatch) => { next().then(response => { dispatch(importFetchedAccounts(response.items)); dispatch(fetchRelationships(response.items.map((item) => item.id))); - dispatch(expandFavouritesSuccess(id, response.items, response.next)); + dispatch(expandFavouritesSuccess(statusId, response.items, response.next)); }).catch(error => { - dispatch(expandFavouritesFail(id, error)); + dispatch(expandFavouritesFail(statusId, error)); }); }; -const expandFavouritesSuccess = (id: string, accounts: APIEntity[], next: AccountListLink | null) => ({ +const expandFavouritesSuccess = (statusId: string, accounts: Array, next: AccountListLink | null) => ({ type: FAVOURITES_EXPAND_SUCCESS, - id, + statusId, accounts, next, }); -const expandFavouritesFail = (id: string, error: unknown) => ({ +const expandFavouritesFail = (statusId: string, error: unknown) => ({ type: FAVOURITES_EXPAND_FAIL, - id, + statusId, error, }); -const fetchDislikes = (id: string) => +const fetchDislikes = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - dispatch(fetchDislikesRequest(id)); + dispatch(fetchDislikesRequest(statusId)); - return getClient(getState).request(`/api/friendica/statuses/${id}/disliked_by`).then(response => { - dispatch(importFetchedAccounts(response.json)); - dispatch(fetchRelationships(response.json.map((item: APIEntity) => item.id))); - dispatch(fetchDislikesSuccess(id, response.json)); + return getClient(getState).statuses.getDislikedBy(statusId).then(response => { + dispatch(importFetchedAccounts(response)); + dispatch(fetchRelationships(response.map((item) => item.id))); + dispatch(fetchDislikesSuccess(statusId, response)); }).catch(error => { - dispatch(fetchDislikesFail(id, error)); + dispatch(fetchDislikesFail(statusId, error)); }); }; -const fetchDislikesRequest = (id: string) => ({ +const fetchDislikesRequest = (statusId: string) => ({ type: DISLIKES_FETCH_REQUEST, - id, + statusId, }); -const fetchDislikesSuccess = (id: string, accounts: APIEntity[]) => ({ +const fetchDislikesSuccess = (statusId: string, accounts: Array) => ({ type: DISLIKES_FETCH_SUCCESS, - id, + statusId, accounts, }); -const fetchDislikesFail = (id: string, error: unknown) => ({ +const fetchDislikesFail = (statusId: string, error: unknown) => ({ type: DISLIKES_FETCH_FAIL, - id, + statusId, error, }); -const fetchReactions = (id: string) => +const fetchReactions = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - dispatch(fetchReactionsRequest(id)); + dispatch(fetchReactionsRequest(statusId)); - return getClient(getState).statuses.getStatusReactions(id).then(response => { + return getClient(getState).statuses.getStatusReactions(statusId).then(response => { dispatch(importFetchedAccounts((response).map(({ accounts }) => accounts).flat())); - dispatch(fetchReactionsSuccess(id, response)); + dispatch(fetchReactionsSuccess(statusId, response)); }).catch(error => { - dispatch(fetchReactionsFail(id, error)); + dispatch(fetchReactionsFail(statusId, error)); }); }; -const fetchReactionsRequest = (id: string) => ({ +const fetchReactionsRequest = (statusId: string) => ({ type: REACTIONS_FETCH_REQUEST, - id, + statusId, }); -const fetchReactionsSuccess = (id: string, reactions: APIEntity[]) => ({ +const fetchReactionsSuccess = (statusId: string, reactions: EmojiReaction[]) => ({ type: REACTIONS_FETCH_SUCCESS, - id, + statusId, reactions, }); -const fetchReactionsFail = (id: string, error: unknown) => ({ +const fetchReactionsFail = (statusId: string, error: unknown) => ({ type: REACTIONS_FETCH_FAIL, - id, + statusId, error, }); -const pin = (status: StatusEntity, accountId: string) => +const pin = (status: Pick, accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - dispatch(pinRequest(status, accountId)); + dispatch(pinRequest(status.id, accountId)); return getClient(getState()).statuses.pinStatus(status.id).then(response => { dispatch(importFetchedStatus(response)); - dispatch(pinSuccess(status, accountId)); + dispatch(pinSuccess(response, accountId)); }).catch(error => { - dispatch(pinFail(status, error, accountId)); + dispatch(pinFail(status.id, error, accountId)); }); }; -const pinRequest = (status: StatusEntity, accountId: string) => ({ +const pinRequest = (statusId: string, accountId: string) => ({ type: PIN_REQUEST, - status, - skipLoading: true, + statusId, accountId, }); -const pinSuccess = (status: StatusEntity, accountId: string) => ({ +const pinSuccess = (status: Status, accountId: string) => ({ type: PIN_SUCCESS, status, - skipLoading: true, + statusId: status.id, accountId, }); -const pinFail = (status: StatusEntity, error: unknown, accountId: string) => ({ +const pinFail = (statusId: string, error: unknown, accountId: string) => ({ type: PIN_FAIL, - status, + statusId, error, - skipLoading: true, accountId, }); -const unpin = (status: StatusEntity, accountId: string) => +const unpin = (status: Pick, accountId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - dispatch(unpinRequest(status, accountId)); + dispatch(unpinRequest(status.id, accountId)); return getClient(getState()).statuses.unpinStatus(status.id).then(response => { dispatch(importFetchedStatus(response)); - dispatch(unpinSuccess(status, accountId)); + dispatch(unpinSuccess(response, accountId)); }).catch(error => { - dispatch(unpinFail(status, error, accountId)); + dispatch(unpinFail(status.id, error, accountId)); }); }; -const togglePin = (status: StatusEntity) => +const togglePin = (status: Pick) => (dispatch: AppDispatch, getState: () => RootState) => { const accountId = getState().me; @@ -639,25 +621,23 @@ const togglePin = (status: StatusEntity) => } }; -const unpinRequest = (status: StatusEntity, accountId: string) => ({ +const unpinRequest = (statusId: string, accountId: string) => ({ type: UNPIN_REQUEST, - status, - skipLoading: true, + statusId, accountId, }); -const unpinSuccess = (status: StatusEntity, accountId: string) => ({ +const unpinSuccess = (status: Status, accountId: string) => ({ type: UNPIN_SUCCESS, status, - skipLoading: true, + statusId: status.id, accountId, }); -const unpinFail = (status: StatusEntity, error: unknown, accountId: string) => ({ +const unpinFail = (statusId: string, error: unknown, accountId: string) => ({ type: UNPIN_FAIL, - status, + statusId, error, - skipLoading: true, accountId, }); @@ -665,12 +645,7 @@ const remoteInteraction = (ap_id: string, profile: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(remoteInteractionRequest(ap_id, profile)); - return getClient(getState).request('/api/v1/pleroma/remote_interaction', { - method: 'POST', - body: JSON.stringify({ ap_id, profile }), - }).then(({ json: data }) => { - if (data.error) throw new Error(data.error); - + return getClient(getState).accounts.remoteInteraction(ap_id, profile).then((data) => { dispatch(remoteInteractionSuccess(ap_id, profile, data.url)); return data.url; @@ -700,6 +675,53 @@ const remoteInteractionFail = (ap_id: string, profile: string, error: unknown) = error, }); +type InteractionsAction = + ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType + | ReturnType; + export { REBLOG_REQUEST, REBLOG_SUCCESS, @@ -817,4 +839,5 @@ export { remoteInteractionRequest, remoteInteractionSuccess, remoteInteractionFail, + type InteractionsAction, }; diff --git a/src/actions/lists.ts b/src/actions/lists.ts index ccd425d21..41d3bf159 100644 --- a/src/actions/lists.ts +++ b/src/actions/lists.ts @@ -6,84 +6,83 @@ import { getClient } from '../api'; import { importFetchedAccounts } from './importer'; -import type { Account, PaginatedResponse } from 'pl-api'; +import type { Account, List, PaginatedResponse } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; -const LIST_FETCH_REQUEST = 'LIST_FETCH_REQUEST'; -const LIST_FETCH_SUCCESS = 'LIST_FETCH_SUCCESS'; -const LIST_FETCH_FAIL = 'LIST_FETCH_FAIL'; +const LIST_FETCH_REQUEST = 'LIST_FETCH_REQUEST' as const; +const LIST_FETCH_SUCCESS = 'LIST_FETCH_SUCCESS' as const; +const LIST_FETCH_FAIL = 'LIST_FETCH_FAIL' as const; -const LISTS_FETCH_REQUEST = 'LISTS_FETCH_REQUEST'; -const LISTS_FETCH_SUCCESS = 'LISTS_FETCH_SUCCESS'; -const LISTS_FETCH_FAIL = 'LISTS_FETCH_FAIL'; +const LISTS_FETCH_REQUEST = 'LISTS_FETCH_REQUEST' as const; +const LISTS_FETCH_SUCCESS = 'LISTS_FETCH_SUCCESS' as const; +const LISTS_FETCH_FAIL = 'LISTS_FETCH_FAIL' as const; -const LIST_EDITOR_TITLE_CHANGE = 'LIST_EDITOR_TITLE_CHANGE'; -const LIST_EDITOR_RESET = 'LIST_EDITOR_RESET'; -const LIST_EDITOR_SETUP = 'LIST_EDITOR_SETUP'; +const LIST_EDITOR_TITLE_CHANGE = 'LIST_EDITOR_TITLE_CHANGE' as const; +const LIST_EDITOR_RESET = 'LIST_EDITOR_RESET' as const; +const LIST_EDITOR_SETUP = 'LIST_EDITOR_SETUP' as const; -const LIST_CREATE_REQUEST = 'LIST_CREATE_REQUEST'; -const LIST_CREATE_SUCCESS = 'LIST_CREATE_SUCCESS'; -const LIST_CREATE_FAIL = 'LIST_CREATE_FAIL'; +const LIST_CREATE_REQUEST = 'LIST_CREATE_REQUEST' as const; +const LIST_CREATE_SUCCESS = 'LIST_CREATE_SUCCESS' as const; +const LIST_CREATE_FAIL = 'LIST_CREATE_FAIL' as const; -const LIST_UPDATE_REQUEST = 'LIST_UPDATE_REQUEST'; -const LIST_UPDATE_SUCCESS = 'LIST_UPDATE_SUCCESS'; -const LIST_UPDATE_FAIL = 'LIST_UPDATE_FAIL'; +const LIST_UPDATE_REQUEST = 'LIST_UPDATE_REQUEST' as const; +const LIST_UPDATE_SUCCESS = 'LIST_UPDATE_SUCCESS' as const; +const LIST_UPDATE_FAIL = 'LIST_UPDATE_FAIL' as const; -const LIST_DELETE_REQUEST = 'LIST_DELETE_REQUEST'; -const LIST_DELETE_SUCCESS = 'LIST_DELETE_SUCCESS'; -const LIST_DELETE_FAIL = 'LIST_DELETE_FAIL'; +const LIST_DELETE_REQUEST = 'LIST_DELETE_REQUEST' as const; +const LIST_DELETE_SUCCESS = 'LIST_DELETE_SUCCESS' as const; +const LIST_DELETE_FAIL = 'LIST_DELETE_FAIL' as const; -const LIST_ACCOUNTS_FETCH_REQUEST = 'LIST_ACCOUNTS_FETCH_REQUEST'; -const LIST_ACCOUNTS_FETCH_SUCCESS = 'LIST_ACCOUNTS_FETCH_SUCCESS'; -const LIST_ACCOUNTS_FETCH_FAIL = 'LIST_ACCOUNTS_FETCH_FAIL'; +const LIST_ACCOUNTS_FETCH_REQUEST = 'LIST_ACCOUNTS_FETCH_REQUEST' as const; +const LIST_ACCOUNTS_FETCH_SUCCESS = 'LIST_ACCOUNTS_FETCH_SUCCESS' as const; +const LIST_ACCOUNTS_FETCH_FAIL = 'LIST_ACCOUNTS_FETCH_FAIL' as const; -const LIST_EDITOR_SUGGESTIONS_CHANGE = 'LIST_EDITOR_SUGGESTIONS_CHANGE'; -const LIST_EDITOR_SUGGESTIONS_READY = 'LIST_EDITOR_SUGGESTIONS_READY'; -const LIST_EDITOR_SUGGESTIONS_CLEAR = 'LIST_EDITOR_SUGGESTIONS_CLEAR'; +const LIST_EDITOR_SUGGESTIONS_CHANGE = 'LIST_EDITOR_SUGGESTIONS_CHANGE' as const; +const LIST_EDITOR_SUGGESTIONS_READY = 'LIST_EDITOR_SUGGESTIONS_READY' as const; +const LIST_EDITOR_SUGGESTIONS_CLEAR = 'LIST_EDITOR_SUGGESTIONS_CLEAR' as const; -const LIST_EDITOR_ADD_REQUEST = 'LIST_EDITOR_ADD_REQUEST'; -const LIST_EDITOR_ADD_SUCCESS = 'LIST_EDITOR_ADD_SUCCESS'; -const LIST_EDITOR_ADD_FAIL = 'LIST_EDITOR_ADD_FAIL'; +const LIST_EDITOR_ADD_REQUEST = 'LIST_EDITOR_ADD_REQUEST' as const; +const LIST_EDITOR_ADD_SUCCESS = 'LIST_EDITOR_ADD_SUCCESS' as const; +const LIST_EDITOR_ADD_FAIL = 'LIST_EDITOR_ADD_FAIL' as const; -const LIST_EDITOR_REMOVE_REQUEST = 'LIST_EDITOR_REMOVE_REQUEST'; -const LIST_EDITOR_REMOVE_SUCCESS = 'LIST_EDITOR_REMOVE_SUCCESS'; -const LIST_EDITOR_REMOVE_FAIL = 'LIST_EDITOR_REMOVE_FAIL'; +const LIST_EDITOR_REMOVE_REQUEST = 'LIST_EDITOR_REMOVE_REQUEST' as const; +const LIST_EDITOR_REMOVE_SUCCESS = 'LIST_EDITOR_REMOVE_SUCCESS' as const; +const LIST_EDITOR_REMOVE_FAIL = 'LIST_EDITOR_REMOVE_FAIL' as const; -const LIST_ADDER_RESET = 'LIST_ADDER_RESET'; -const LIST_ADDER_SETUP = 'LIST_ADDER_SETUP'; +const LIST_ADDER_RESET = 'LIST_ADDER_RESET' as const; +const LIST_ADDER_SETUP = 'LIST_ADDER_SETUP' as const; -const LIST_ADDER_LISTS_FETCH_REQUEST = 'LIST_ADDER_LISTS_FETCH_REQUEST'; -const LIST_ADDER_LISTS_FETCH_SUCCESS = 'LIST_ADDER_LISTS_FETCH_SUCCESS'; -const LIST_ADDER_LISTS_FETCH_FAIL = 'LIST_ADDER_LISTS_FETCH_FAIL'; +const LIST_ADDER_LISTS_FETCH_REQUEST = 'LIST_ADDER_LISTS_FETCH_REQUEST' as const; +const LIST_ADDER_LISTS_FETCH_SUCCESS = 'LIST_ADDER_LISTS_FETCH_SUCCESS' as const; +const LIST_ADDER_LISTS_FETCH_FAIL = 'LIST_ADDER_LISTS_FETCH_FAIL' as const; -const fetchList = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { +const fetchList = (listId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - if (getState().lists.get(id)) { + if (getState().lists.get(listId)) { return; } - dispatch(fetchListRequest(id)); + dispatch(fetchListRequest(listId)); - return getClient(getState()).lists.getList(id) + return getClient(getState()).lists.getList(listId) .then((data) => dispatch(fetchListSuccess(data))) - .catch(err => dispatch(fetchListFail(id, err))); + .catch(err => dispatch(fetchListFail(listId, err))); }; -const fetchListRequest = (id: string) => ({ +const fetchListRequest = (listId: string) => ({ type: LIST_FETCH_REQUEST, - id, + listId, }); -const fetchListSuccess = (list: APIEntity) => ({ +const fetchListSuccess = (list: List) => ({ type: LIST_FETCH_SUCCESS, list, }); -const fetchListFail = (id: string, error: unknown) => ({ +const fetchListFail = (listId: string, error: unknown) => ({ type: LIST_FETCH_FAIL, - id, + listId, error, }); @@ -101,7 +100,7 @@ const fetchListsRequest = () => ({ type: LISTS_FETCH_REQUEST, }); -const fetchListsSuccess = (lists: APIEntity[]) => ({ +const fetchListsSuccess = (lists: Array) => ({ type: LISTS_FETCH_SUCCESS, lists, }); @@ -113,7 +112,7 @@ const fetchListsFail = (error: unknown) => ({ const submitListEditor = (shouldReset?: boolean) => (dispatch: AppDispatch, getState: () => RootState) => { const listId = getState().listEditor.listId!; - const title = getState().listEditor.title; + const title = getState().listEditor.title; if (listId === null) { dispatch(createList(title, shouldReset)); @@ -154,7 +153,7 @@ const createListRequest = () => ({ type: LIST_CREATE_REQUEST, }); -const createListSuccess = (list: APIEntity) => ({ +const createListSuccess = (list: List) => ({ type: LIST_CREATE_SUCCESS, list, }); @@ -164,33 +163,33 @@ const createListFail = (error: unknown) => ({ error, }); -const updateList = (id: string, title: string, shouldReset?: boolean) => (dispatch: AppDispatch, getState: () => RootState) => { +const updateList = (listId: string, title: string, shouldReset?: boolean) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - dispatch(updateListRequest(id)); + dispatch(updateListRequest(listId)); - return getClient(getState()).lists.updateList(id, { title }).then((data) => { + return getClient(getState()).lists.updateList(listId, { title }).then((data) => { dispatch(updateListSuccess(data)); if (shouldReset) { dispatch(resetListEditor()); } - }).catch(err => dispatch(updateListFail(id, err))); + }).catch(err => dispatch(updateListFail(listId, err))); }; -const updateListRequest = (id: string) => ({ +const updateListRequest = (listId: string) => ({ type: LIST_UPDATE_REQUEST, - id, + listId, }); -const updateListSuccess = (list: APIEntity) => ({ +const updateListSuccess = (list: List) => ({ type: LIST_UPDATE_SUCCESS, list, }); -const updateListFail = (id: string, error: unknown) => ({ +const updateListFail = (listId: string, error: unknown) => ({ type: LIST_UPDATE_FAIL, - id, + listId, error, }); @@ -198,29 +197,29 @@ const resetListEditor = () => ({ type: LIST_EDITOR_RESET, }); -const deleteList = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { +const deleteList = (listId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - dispatch(deleteListRequest(id)); + dispatch(deleteListRequest(listId)); - return getClient(getState()).lists.deleteList(id) - .then(() => dispatch(deleteListSuccess(id))) - .catch(err => dispatch(deleteListFail(id, err))); + return getClient(getState()).lists.deleteList(listId) + .then(() => dispatch(deleteListSuccess(listId))) + .catch(err => dispatch(deleteListFail(listId, err))); }; -const deleteListRequest = (id: string) => ({ +const deleteListRequest = (listId: string) => ({ type: LIST_DELETE_REQUEST, - id, + listId, }); -const deleteListSuccess = (id: string) => ({ +const deleteListSuccess = (listId: string) => ({ type: LIST_DELETE_SUCCESS, - id, + listId, }); -const deleteListFail = (id: string, error: unknown) => ({ +const deleteListFail = (listId: string, error: unknown) => ({ type: LIST_DELETE_FAIL, - id, + listId, error, }); @@ -235,21 +234,21 @@ const fetchListAccounts = (listId: string) => (dispatch: AppDispatch, getState: }).catch(err => dispatch(fetchListAccountsFail(listId, err))); }; -const fetchListAccountsRequest = (id: string) => ({ +const fetchListAccountsRequest = (listId: string) => ({ type: LIST_ACCOUNTS_FETCH_REQUEST, - id, + listId, }); -const fetchListAccountsSuccess = (id: string, accounts: Account[], next: (() => Promise>) | null) => ({ +const fetchListAccountsSuccess = (listId: string, accounts: Account[], next: (() => Promise>) | null) => ({ type: LIST_ACCOUNTS_FETCH_SUCCESS, - id, + listId, accounts, next, }); -const fetchListAccountsFail = (id: string, error: unknown) => ({ +const fetchListAccountsFail = (listId: string, error: unknown) => ({ type: LIST_ACCOUNTS_FETCH_FAIL, - id, + listId, error, }); @@ -262,7 +261,7 @@ const fetchListSuggestions = (q: string) => (dispatch: AppDispatch, getState: () }).catch(error => toast.showAlertForError(error)); }; -const fetchListSuggestionsReady = (query: string, accounts: APIEntity[]) => ({ +const fetchListSuggestionsReady = (query: string, accounts: Array) => ({ type: LIST_EDITOR_SUGGESTIONS_READY, query, accounts, @@ -303,7 +302,7 @@ const addToListSuccess = (listId: string, accountId: string) => ({ accountId, }); -const addToListFail = (listId: string, accountId: string, error: APIEntity) => ({ +const addToListFail = (listId: string, accountId: string, error: any) => ({ type: LIST_EDITOR_ADD_FAIL, listId, accountId, @@ -366,20 +365,20 @@ const fetchAccountLists = (accountId: string) => (dispatch: AppDispatch, getStat .catch(err => dispatch(fetchAccountListsFail(accountId, err))); }; -const fetchAccountListsRequest = (id: string) => ({ +const fetchAccountListsRequest = (listId: string) => ({ type: LIST_ADDER_LISTS_FETCH_REQUEST, - id, + listId, }); -const fetchAccountListsSuccess = (id: string, lists: APIEntity[]) => ({ +const fetchAccountListsSuccess = (listId: string, lists: Array) => ({ type: LIST_ADDER_LISTS_FETCH_SUCCESS, - id, + listId, lists, }); -const fetchAccountListsFail = (id: string, err: unknown) => ({ +const fetchAccountListsFail = (listId: string, err: unknown) => ({ type: LIST_ADDER_LISTS_FETCH_FAIL, - id, + listId, err, }); diff --git a/src/actions/markers.ts b/src/actions/markers.ts index 3440b7bd3..9227e78f0 100644 --- a/src/actions/markers.ts +++ b/src/actions/markers.ts @@ -1,15 +1,15 @@ import { getClient } from '../api'; +import type { SaveMarkersParams } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; -const MARKER_FETCH_REQUEST = 'MARKER_FETCH_REQUEST'; -const MARKER_FETCH_SUCCESS = 'MARKER_FETCH_SUCCESS'; -const MARKER_FETCH_FAIL = 'MARKER_FETCH_FAIL'; +const MARKER_FETCH_REQUEST = 'MARKER_FETCH_REQUEST' as const; +const MARKER_FETCH_SUCCESS = 'MARKER_FETCH_SUCCESS' as const; +const MARKER_FETCH_FAIL = 'MARKER_FETCH_FAIL' as const; -const MARKER_SAVE_REQUEST = 'MARKER_SAVE_REQUEST'; -const MARKER_SAVE_SUCCESS = 'MARKER_SAVE_SUCCESS'; -const MARKER_SAVE_FAIL = 'MARKER_SAVE_FAIL'; +const MARKER_SAVE_REQUEST = 'MARKER_SAVE_REQUEST' as const; +const MARKER_SAVE_SUCCESS = 'MARKER_SAVE_SUCCESS' as const; +const MARKER_SAVE_FAIL = 'MARKER_SAVE_FAIL' as const; const fetchMarker = (timeline: Array) => (dispatch: AppDispatch, getState: () => RootState) => { @@ -21,7 +21,7 @@ const fetchMarker = (timeline: Array) => }); }; -const saveMarker = (marker: APIEntity) => +const saveMarker = (marker: SaveMarkersParams) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: MARKER_SAVE_REQUEST, marker }); return getClient(getState).timelines.saveMarkers(marker).then((marker) => { diff --git a/src/actions/me.ts b/src/actions/me.ts index e28d22baf..912144caf 100644 --- a/src/actions/me.ts +++ b/src/actions/me.ts @@ -8,18 +8,17 @@ import { getClient } from '../api'; import { loadCredentials } from './auth'; import { importFetchedAccount } from './importer'; -import type { Account } from 'pl-api'; +import type { CredentialAccount } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; const ME_FETCH_REQUEST = 'ME_FETCH_REQUEST' as const; const ME_FETCH_SUCCESS = 'ME_FETCH_SUCCESS' as const; -const ME_FETCH_FAIL = 'ME_FETCH_FAIL' as const; -const ME_FETCH_SKIP = 'ME_FETCH_SKIP' as const; +const ME_FETCH_FAIL = 'ME_FETCH_FAIL' as const; +const ME_FETCH_SKIP = 'ME_FETCH_SKIP' as const; const ME_PATCH_REQUEST = 'ME_PATCH_REQUEST' as const; const ME_PATCH_SUCCESS = 'ME_PATCH_SUCCESS' as const; -const ME_PATCH_FAIL = 'ME_PATCH_FAIL' as const; +const ME_PATCH_FAIL = 'ME_PATCH_FAIL' as const; const noOp = () => new Promise(f => f(undefined)); @@ -55,12 +54,10 @@ const fetchMe = () => }; /** Update the auth account in IndexedDB for Mastodon, etc. */ -const persistAuthAccount = (account: APIEntity, params: Record) => { +const persistAuthAccount = (account: CredentialAccount, params: Record) => { if (account && account.url) { - if (!account.pleroma) account.pleroma = {}; - - if (!account.pleroma.settings_store) { - account.pleroma.settings_store = params.pleroma_settings_store || {}; + if (!account.settings_store) { + account.settings_store = params.pleroma_settings_store || {}; } KVStore.setItem(`authAccount:${account.url}`, account).catch(console.error); } @@ -84,7 +81,7 @@ const fetchMeRequest = () => ({ type: ME_FETCH_REQUEST, }); -const fetchMeSuccess = (account: Account) => { +const fetchMeSuccess = (account: CredentialAccount) => { setSentryAccount(account); return { @@ -93,7 +90,7 @@ const fetchMeSuccess = (account: Account) => { }; }; -const fetchMeFail = (error: APIEntity) => ({ +const fetchMeFail = (error: unknown) => ({ type: ME_FETCH_FAIL, error, skipAlert: true, @@ -105,10 +102,10 @@ const patchMeRequest = () => ({ interface MePatchSuccessAction { type: typeof ME_PATCH_SUCCESS; - me: APIEntity; + me: CredentialAccount; } -const patchMeSuccess = (me: APIEntity) => +const patchMeSuccess = (me: CredentialAccount) => (dispatch: AppDispatch) => { const action: MePatchSuccessAction = { type: ME_PATCH_SUCCESS, diff --git a/src/actions/media.ts b/src/actions/media.ts index cd62d3cc1..c7e53ffb1 100644 --- a/src/actions/media.ts +++ b/src/actions/media.ts @@ -7,9 +7,8 @@ import resizeImage from 'soapbox/utils/resize-image'; import { getClient } from '../api'; -import type { UploadMediaParams } from 'pl-api'; +import type { MediaAttachment, UploadMediaParams } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; const messages = defineMessages({ exceededImageSizeLimit: { id: 'upload_error.image_size_limit', defaultMessage: 'Image exceeds the current file size limit ({limit})' }, @@ -30,7 +29,7 @@ const uploadMedia = (body: UploadMediaParams, onUploadProgress: (e: ProgressEven const uploadFile = ( file: File, intl: IntlShape, - onSuccess: (data: APIEntity) => void = () => {}, + onSuccess: (data: MediaAttachment) => void = () => {}, onFail: (error: unknown) => void = () => {}, onProgress: (e: ProgressEvent) => void = () => {}, changeTotal: (value: number) => void = () => {}, diff --git a/src/actions/mfa.ts b/src/actions/mfa.ts index 198a9d746..77f23c083 100644 --- a/src/actions/mfa.ts +++ b/src/actions/mfa.ts @@ -2,30 +2,30 @@ import { getClient } from '../api'; import type { AppDispatch, RootState } from 'soapbox/store'; -const MFA_FETCH_REQUEST = 'MFA_FETCH_REQUEST'; -const MFA_FETCH_SUCCESS = 'MFA_FETCH_SUCCESS'; -const MFA_FETCH_FAIL = 'MFA_FETCH_FAIL'; +const MFA_FETCH_REQUEST = 'MFA_FETCH_REQUEST' as const; +const MFA_FETCH_SUCCESS = 'MFA_FETCH_SUCCESS' as const; +const MFA_FETCH_FAIL = 'MFA_FETCH_FAIL' as const; -const MFA_BACKUP_CODES_FETCH_REQUEST = 'MFA_BACKUP_CODES_FETCH_REQUEST'; -const MFA_BACKUP_CODES_FETCH_SUCCESS = 'MFA_BACKUP_CODES_FETCH_SUCCESS'; -const MFA_BACKUP_CODES_FETCH_FAIL = 'MFA_BACKUP_CODES_FETCH_FAIL'; +const MFA_BACKUP_CODES_FETCH_REQUEST = 'MFA_BACKUP_CODES_FETCH_REQUEST' as const; +const MFA_BACKUP_CODES_FETCH_SUCCESS = 'MFA_BACKUP_CODES_FETCH_SUCCESS' as const; +const MFA_BACKUP_CODES_FETCH_FAIL = 'MFA_BACKUP_CODES_FETCH_FAIL' as const; -const MFA_SETUP_REQUEST = 'MFA_SETUP_REQUEST'; -const MFA_SETUP_SUCCESS = 'MFA_SETUP_SUCCESS'; -const MFA_SETUP_FAIL = 'MFA_SETUP_FAIL'; +const MFA_SETUP_REQUEST = 'MFA_SETUP_REQUEST' as const; +const MFA_SETUP_SUCCESS = 'MFA_SETUP_SUCCESS' as const; +const MFA_SETUP_FAIL = 'MFA_SETUP_FAIL' as const; -const MFA_CONFIRM_REQUEST = 'MFA_CONFIRM_REQUEST'; -const MFA_CONFIRM_SUCCESS = 'MFA_CONFIRM_SUCCESS'; -const MFA_CONFIRM_FAIL = 'MFA_CONFIRM_FAIL'; +const MFA_CONFIRM_REQUEST = 'MFA_CONFIRM_REQUEST' as const; +const MFA_CONFIRM_SUCCESS = 'MFA_CONFIRM_SUCCESS' as const; +const MFA_CONFIRM_FAIL = 'MFA_CONFIRM_FAIL' as const; -const MFA_DISABLE_REQUEST = 'MFA_DISABLE_REQUEST'; -const MFA_DISABLE_SUCCESS = 'MFA_DISABLE_SUCCESS'; -const MFA_DISABLE_FAIL = 'MFA_DISABLE_FAIL'; +const MFA_DISABLE_REQUEST = 'MFA_DISABLE_REQUEST' as const; +const MFA_DISABLE_SUCCESS = 'MFA_DISABLE_SUCCESS' as const; +const MFA_DISABLE_FAIL = 'MFA_DISABLE_FAIL' as const; const fetchMfa = () => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: MFA_FETCH_REQUEST }); - return getClient(getState).request('/api/pleroma/accounts/mfa').then(({ json: data }) => { + return getClient(getState).settings.mfa.getMfaSettings().then((data) => { dispatch({ type: MFA_FETCH_SUCCESS, data }); }).catch(() => { dispatch({ type: MFA_FETCH_FAIL }); @@ -35,18 +35,19 @@ const fetchMfa = () => const fetchBackupCodes = () => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: MFA_BACKUP_CODES_FETCH_REQUEST }); - return getClient(getState).request('/api/pleroma/accounts/mfa/backup_codes').then(({ json: data }) => { + return getClient(getState).settings.mfa.getMfaBackupCodes().then((data) => { dispatch({ type: MFA_BACKUP_CODES_FETCH_SUCCESS, data }); return data; - }).catch(() => { + }).catch((error: unknown) => { dispatch({ type: MFA_BACKUP_CODES_FETCH_FAIL }); + throw error; }); }; -const setupMfa = (method: string) => +const setupMfa = (method: 'totp') => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: MFA_SETUP_REQUEST, method }); - return getClient(getState).request(`/api/pleroma/accounts/mfa/setup/${method}`).then(({ json: data }) => { + return getClient(getState).settings.mfa.getMfaSetup(method).then((data) => { dispatch({ type: MFA_SETUP_SUCCESS, data }); return data; }).catch((error: unknown) => { @@ -55,13 +56,10 @@ const setupMfa = (method: string) => }); }; -const confirmMfa = (method: string, code: string, password: string) => +const confirmMfa = (method: 'totp', code: string, password: string) => (dispatch: AppDispatch, getState: () => RootState) => { - const params = { code, password }; dispatch({ type: MFA_CONFIRM_REQUEST, method, code }); - return getClient(getState).request(`/api/pleroma/accounts/mfa/confirm/${method}`, { - method: 'POST', body: params, - }).then(({ json: data }) => { + return getClient(getState).settings.mfa.confirmMfaSetup(method, code, password).then((data) => { dispatch({ type: MFA_CONFIRM_SUCCESS, method, code }); return data; }).catch((error: unknown) => { @@ -70,12 +68,10 @@ const confirmMfa = (method: string, code: string, password: string) => }); }; -const disableMfa = (method: string, password: string) => +const disableMfa = (method: 'totp', password: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: MFA_DISABLE_REQUEST, method }); - return getClient(getState).request(`/api/pleroma/accounts/mfa/${method}`, { - method: 'DELETE', body: { password }, - }).then(({ json: data }) => { + return getClient(getState).settings.mfa.disableMfa(method, password).then((data) => { dispatch({ type: MFA_DISABLE_SUCCESS, method }); return data; }).catch((error: unknown) => { diff --git a/src/actions/modals.ts b/src/actions/modals.ts index ef3f55a2c..5bebc746e 100644 --- a/src/actions/modals.ts +++ b/src/actions/modals.ts @@ -2,7 +2,7 @@ import { AppDispatch } from 'soapbox/store'; import type { ModalType } from 'soapbox/features/ui/components/modal-root'; -const MODAL_OPEN = 'MODAL_OPEN'; +const MODAL_OPEN = 'MODAL_OPEN'; const MODAL_CLOSE = 'MODAL_CLOSE'; /** Open a modal of the given type */ diff --git a/src/actions/mutes.ts b/src/actions/mutes.ts index 205ee4677..c405a5661 100644 --- a/src/actions/mutes.ts +++ b/src/actions/mutes.ts @@ -1,14 +1,13 @@ import { openModal } from './modals'; -import type { Account } from 'soapbox/schemas'; +import type { Account } from 'soapbox/normalizers'; import type { AppDispatch } from 'soapbox/store'; -import type { Account as AccountEntity } from 'soapbox/types/entities'; const MUTES_INIT_MODAL = 'MUTES_INIT_MODAL'; const MUTES_TOGGLE_HIDE_NOTIFICATIONS = 'MUTES_TOGGLE_HIDE_NOTIFICATIONS'; const MUTES_CHANGE_DURATION = 'MUTES_CHANGE_DURATION'; -const initMuteModal = (account: AccountEntity | Account) => +const initMuteModal = (account: Account) => (dispatch: AppDispatch) => { dispatch({ type: MUTES_INIT_MODAL, diff --git a/src/actions/notifications.ts b/src/actions/notifications.ts index 53fc79fcb..199dfcb5b 100644 --- a/src/actions/notifications.ts +++ b/src/actions/notifications.ts @@ -1,12 +1,14 @@ import IntlMessageFormat from 'intl-messageformat'; import 'intl-pluralrules'; +import { type Account, type Notification as BaseNotification, type PaginatedResponse, type Status } from 'pl-api'; import { defineMessages } from 'react-intl'; import { getClient } from 'soapbox/api'; +import { getNotificationStatus } from 'soapbox/features/notifications/components/notification'; +import { normalizeNotifications, type Notification } from 'soapbox/normalizers'; import { getFilters, regexFromFilters } from 'soapbox/selectors'; import { isLoggedIn } from 'soapbox/utils/auth'; import { compareId } from 'soapbox/utils/comparators'; -import { getFeatures, parseVersion, PLEROMA } from 'soapbox/utils/features'; import { unescapeHTML } from 'soapbox/utils/html'; import { EXCLUDE_TYPES, NOTIFICATION_TYPES } from 'soapbox/utils/notification'; import { joinPublicPath } from 'soapbox/utils/static'; @@ -21,27 +23,25 @@ import { import { saveMarker } from './markers'; import { getSettings, saveSettings } from './settings'; -import type { Account, Notification, PaginatedResponse } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; -const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE'; -const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP'; -const NOTIFICATIONS_UPDATE_QUEUE = 'NOTIFICATIONS_UPDATE_QUEUE'; -const NOTIFICATIONS_DEQUEUE = 'NOTIFICATIONS_DEQUEUE'; +const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE' as const; +const NOTIFICATIONS_UPDATE_NOOP = 'NOTIFICATIONS_UPDATE_NOOP' as const; +const NOTIFICATIONS_UPDATE_QUEUE = 'NOTIFICATIONS_UPDATE_QUEUE' as const; +const NOTIFICATIONS_DEQUEUE = 'NOTIFICATIONS_DEQUEUE' as const; -const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST'; -const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS'; -const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL'; +const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST' as const; +const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS' as const; +const NOTIFICATIONS_EXPAND_FAIL = 'NOTIFICATIONS_EXPAND_FAIL' as const; -const NOTIFICATIONS_FILTER_SET = 'NOTIFICATIONS_FILTER_SET'; +const NOTIFICATIONS_FILTER_SET = 'NOTIFICATIONS_FILTER_SET' as const; -const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR'; -const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP'; +const NOTIFICATIONS_CLEAR = 'NOTIFICATIONS_CLEAR' as const; +const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP' as const; -const NOTIFICATIONS_MARK_READ_REQUEST = 'NOTIFICATIONS_MARK_READ_REQUEST'; -const NOTIFICATIONS_MARK_READ_SUCCESS = 'NOTIFICATIONS_MARK_READ_SUCCESS'; -const NOTIFICATIONS_MARK_READ_FAIL = 'NOTIFICATIONS_MARK_READ_FAIL'; +const NOTIFICATIONS_MARK_READ_REQUEST = 'NOTIFICATIONS_MARK_READ_REQUEST' as const; +const NOTIFICATIONS_MARK_READ_SUCCESS = 'NOTIFICATIONS_MARK_READ_SUCCESS' as const; +const NOTIFICATIONS_MARK_READ_FAIL = 'NOTIFICATIONS_MARK_READ_FAIL' as const; const MAX_QUEUED_NOTIFICATIONS = 40; @@ -62,7 +62,7 @@ defineMessages({ mention: { id: 'notification.mention', defaultMessage: '{name} mentioned you' }, }); -const fetchRelatedRelationships = (dispatch: AppDispatch, notifications: APIEntity[]) => { +const fetchRelatedRelationships = (dispatch: AppDispatch, notifications: Array) => { const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id); if (accountIds.length > 0) { @@ -70,7 +70,7 @@ const fetchRelatedRelationships = (dispatch: AppDispatch, notifications: APIEnti } }; -const updateNotifications = (notification: APIEntity) => +const updateNotifications = (notification: BaseNotification) => (dispatch: AppDispatch, getState: () => RootState) => { const showInColumn = getSettings(getState()).getIn(['notifications', 'shows', notification.type], true); @@ -79,12 +79,14 @@ const updateNotifications = (notification: APIEntity) => } // Used by Move notification - if (notification.target) { + if (notification.type === 'move' && notification.target) { dispatch(importFetchedAccount(notification.target)); } - if (notification.status) { - dispatch(importFetchedStatus(notification.status)); + const status = getNotificationStatus(notification); + + if (status) { + dispatch(importFetchedStatus(status)); } if (showInColumn) { @@ -97,19 +99,21 @@ const updateNotifications = (notification: APIEntity) => } }; -const updateNotificationsQueue = (notification: APIEntity, intlMessages: Record, intlLocale: string, curPath: string) => +const updateNotificationsQueue = (notification: BaseNotification, intlMessages: Record, intlLocale: string, curPath: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!notification.type) return; // drop invalid notifications - if (notification.type === 'pleroma:chat_mention') return; // Drop chat notifications, handle them per-chat + if (notification.type === 'chat_mention') return; // Drop chat notifications, handle them per-chat const filters = getFilters(getState(), { contextType: 'notifications' }); const playSound = getSettings(getState()).getIn(['notifications', 'sounds', notification.type]); + const status = getNotificationStatus(notification); + let filtered: boolean | null = false; const isOnNotificationsPage = curPath === '/notifications'; - if (['mention', 'status'].includes(notification.type)) { + if (notification.type === 'mention' || notification.type === 'status') { const regex = regexFromFilters(filters); const searchIndex = notification.status.spoiler_text + '\n' + unescapeHTML(notification.status.content); filtered = regex && regex.test(searchIndex); @@ -121,8 +125,8 @@ const updateNotificationsQueue = (notification: APIEntity, intlMessages: Record< const isNotificationsEnabled = window.Notification?.permission === 'granted'; if (!filtered && isNotificationsEnabled) { - const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username }); - const body = (notification.status && notification.status.spoiler_text.length > 0) ? notification.status.spoiler_text : unescapeHTML(notification.status ? notification.status.content : ''); + const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username }) as string; + const body = (status && status.spoiler_text.length > 0) ? status.spoiler_text : unescapeHTML(status ? status.content : ''); navigator.serviceWorker.ready.then(serviceWorkerRegistration => { serviceWorkerRegistration.showNotification(title, { @@ -185,51 +189,14 @@ const noOp = () => new Promise(f => f(undefined)); let abortExpandNotifications = new AbortController(); -const STATUS_NOTIFICATION_TYPES = [ - 'favourite', - 'reblog', - // WIP separate notifications for each reaction? - // 'emoji_reaction', - 'event_reminder', - 'participation_accepted', - 'participation_request', -]; - -const deduplicateNotifications = (notifications: any[]) => { - const deduplicatedNotifications: any[] = []; - - for (const notification of notifications) { - if (STATUS_NOTIFICATION_TYPES.includes(notification.type)) { - const existingNotification = deduplicatedNotifications - .find(deduplicatedNotification => deduplicatedNotification.type === notification.type && deduplicatedNotification.status?.id === notification.status?.id); - - if (existingNotification) { - if (existingNotification?.accounts) { - existingNotification.accounts.push(notification.account); - } else { - existingNotification.accounts = [existingNotification.account, notification.account]; - } - existingNotification.id += '+' + notification.id; - } else { - deduplicatedNotifications.push(notification); - } - } else { - deduplicatedNotifications.push(notification); - } - } - - return deduplicatedNotifications; -}; - const expandNotifications = ({ maxId }: Record = {}, done: () => any = noOp, abort?: boolean) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return dispatch(noOp); - const state = getState(); - const features = getFeatures(state.instance); + + const features = state.auth.client.features; const activeFilter = getSettings(state).getIn(['notifications', 'quickFilter', 'active']) as FilterType; const notifications = state.notifications; - const isLoadingMore = !!maxId; if (notifications.isLoading) { if (abort) { @@ -270,7 +237,7 @@ const expandNotifications = ({ maxId }: Record = {}, done: () => an params.since_id = notifications.getIn(['items', 0, 'id']); } - dispatch(expandNotificationsRequest(isLoadingMore)); + dispatch(expandNotificationsRequest()); return getClient(state).notifications.getNotifications(params, { signal: abortExpandNotifications.signal }).then(response => { const entries = (response.items).reduce((acc, item) => { @@ -291,38 +258,33 @@ const expandNotifications = ({ maxId }: Record = {}, done: () => an } return acc; - }, { accounts: {}, statuses: {} } as { accounts: Record; statuses: Record }); + }, { accounts: {}, statuses: {} } as { accounts: Record; statuses: Record }); dispatch(importFetchedAccounts(Object.values(entries.accounts))); dispatch(importFetchedStatuses(Object.values(entries.statuses))); - const deduplicatedNotifications = deduplicateNotifications(response.items); + const deduplicatedNotifications = normalizeNotifications(response.items); - dispatch(expandNotificationsSuccess(deduplicatedNotifications, response.next, isLoadingMore)); + dispatch(expandNotificationsSuccess(deduplicatedNotifications, response.next)); fetchRelatedRelationships(dispatch, response.items); done(); }).catch(error => { - dispatch(expandNotificationsFail(error, isLoadingMore)); + dispatch(expandNotificationsFail(error)); done(); }); }; -const expandNotificationsRequest = (isLoadingMore: boolean) => ({ - type: NOTIFICATIONS_EXPAND_REQUEST, - skipLoading: !isLoadingMore, -}); +const expandNotificationsRequest = () => ({ type: NOTIFICATIONS_EXPAND_REQUEST }); -const expandNotificationsSuccess = (notifications: APIEntity[], next: (() => Promise>) | null, isLoadingMore: boolean) => ({ +const expandNotificationsSuccess = (notifications: Array, next: (() => Promise>) | null) => ({ type: NOTIFICATIONS_EXPAND_SUCCESS, notifications, next, - skipLoading: !isLoadingMore, }); -const expandNotificationsFail = (error: unknown, isLoadingMore: boolean) => ({ +const expandNotificationsFail = (error: unknown) => ({ type: NOTIFICATIONS_EXPAND_FAIL, error, - skipLoading: !isLoadingMore, }); const scrollTopNotifications = (top: boolean) => @@ -345,15 +307,6 @@ const setFilter = (filterType: FilterType, abort?: boolean) => dispatch(saveSettings()); }; -// Of course Markers don't work properly in Pleroma. -// https://git.pleroma.social/pleroma/pleroma/-/issues/2769 -const markReadPleroma = (max_id: string | number) => - (dispatch: AppDispatch, getState: () => RootState) => - getClient(getState).request('/api/v1/pleroma/notifications/read', { - method: 'POST', - body: { max_id }, - }); - const markReadNotifications = () => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; @@ -361,7 +314,6 @@ const markReadNotifications = () => const state = getState(); let topNotificationId = state.notifications.items.first()?.id; const lastReadId = state.notifications.lastRead; - const v = parseVersion(state.instance.version); if (typeof topNotificationId === 'string' && topNotificationId?.includes('+')) { topNotificationId = topNotificationId.split('+')[0]; @@ -375,10 +327,6 @@ const markReadNotifications = () => }; dispatch(saveMarker(marker)); - - if (v.software === PLEROMA) { - dispatch(markReadPleroma(topNotificationId)); - } } }; @@ -407,6 +355,5 @@ export { expandNotificationsFail, scrollTopNotifications, setFilter, - markReadPleroma, markReadNotifications, }; diff --git a/src/actions/oauth.ts b/src/actions/oauth.ts index f7a82f5d0..097ebe3ab 100644 --- a/src/actions/oauth.ts +++ b/src/actions/oauth.ts @@ -13,13 +13,13 @@ import { getBaseURL } from 'soapbox/utils/state'; import type { AppDispatch, RootState } from 'soapbox/store'; -const OAUTH_TOKEN_CREATE_REQUEST = 'OAUTH_TOKEN_CREATE_REQUEST'; -const OAUTH_TOKEN_CREATE_SUCCESS = 'OAUTH_TOKEN_CREATE_SUCCESS'; -const OAUTH_TOKEN_CREATE_FAIL = 'OAUTH_TOKEN_CREATE_FAIL'; +const OAUTH_TOKEN_CREATE_REQUEST = 'OAUTH_TOKEN_CREATE_REQUEST' as const; +const OAUTH_TOKEN_CREATE_SUCCESS = 'OAUTH_TOKEN_CREATE_SUCCESS' as const; +const OAUTH_TOKEN_CREATE_FAIL = 'OAUTH_TOKEN_CREATE_FAIL' as const; -const OAUTH_TOKEN_REVOKE_REQUEST = 'OAUTH_TOKEN_REVOKE_REQUEST'; -const OAUTH_TOKEN_REVOKE_SUCCESS = 'OAUTH_TOKEN_REVOKE_SUCCESS'; -const OAUTH_TOKEN_REVOKE_FAIL = 'OAUTH_TOKEN_REVOKE_FAIL'; +const OAUTH_TOKEN_REVOKE_REQUEST = 'OAUTH_TOKEN_REVOKE_REQUEST' as const; +const OAUTH_TOKEN_REVOKE_SUCCESS = 'OAUTH_TOKEN_REVOKE_SUCCESS' as const; +const OAUTH_TOKEN_REVOKE_FAIL = 'OAUTH_TOKEN_REVOKE_FAIL' as const; const obtainOAuthToken = (params: GetTokenParams, baseURL?: string) => (dispatch: AppDispatch) => { diff --git a/src/actions/pin-statuses.ts b/src/actions/pin-statuses.ts index dec240f25..b9b880ad7 100644 --- a/src/actions/pin-statuses.ts +++ b/src/actions/pin-statuses.ts @@ -4,12 +4,12 @@ import { getClient } from '../api'; import { importFetchedStatuses } from './importer'; +import type { Status } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; -const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST'; -const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS'; -const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL'; +const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST' as const; +const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS' as const; +const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL' as const; const fetchPinnedStatuses = () => (dispatch: AppDispatch, getState: () => RootState) => { @@ -30,7 +30,7 @@ const fetchPinnedStatusesRequest = () => ({ type: PINNED_STATUSES_FETCH_REQUEST, }); -const fetchPinnedStatusesSuccess = (statuses: APIEntity[], next: string | null) => ({ +const fetchPinnedStatusesSuccess = (statuses: Array, next: string | null) => ({ type: PINNED_STATUSES_FETCH_SUCCESS, statuses, next, @@ -41,6 +41,11 @@ const fetchPinnedStatusesFail = (error: unknown) => ({ error, }); +type PinStatusesAction = + ReturnType + | ReturnType + | ReturnType; + export { PINNED_STATUSES_FETCH_REQUEST, PINNED_STATUSES_FETCH_SUCCESS, @@ -49,4 +54,5 @@ export { fetchPinnedStatusesRequest, fetchPinnedStatusesSuccess, fetchPinnedStatusesFail, + type PinStatusesAction, }; diff --git a/src/actions/polls.ts b/src/actions/polls.ts index 5f936dfdb..eb1113451 100644 --- a/src/actions/polls.ts +++ b/src/actions/polls.ts @@ -2,16 +2,16 @@ import { getClient } from '../api'; import { importFetchedPoll } from './importer'; +import type { Poll } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; -const POLL_VOTE_REQUEST = 'POLL_VOTE_REQUEST'; -const POLL_VOTE_SUCCESS = 'POLL_VOTE_SUCCESS'; -const POLL_VOTE_FAIL = 'POLL_VOTE_FAIL'; +const POLL_VOTE_REQUEST = 'POLL_VOTE_REQUEST' as const; +const POLL_VOTE_SUCCESS = 'POLL_VOTE_SUCCESS' as const; +const POLL_VOTE_FAIL = 'POLL_VOTE_FAIL' as const; -const POLL_FETCH_REQUEST = 'POLL_FETCH_REQUEST'; -const POLL_FETCH_SUCCESS = 'POLL_FETCH_SUCCESS'; -const POLL_FETCH_FAIL = 'POLL_FETCH_FAIL'; +const POLL_FETCH_REQUEST = 'POLL_FETCH_REQUEST' as const; +const POLL_FETCH_SUCCESS = 'POLL_FETCH_SUCCESS' as const; +const POLL_FETCH_FAIL = 'POLL_FETCH_FAIL' as const; const vote = (pollId: string, choices: number[]) => (dispatch: AppDispatch, getState: () => RootState) => { @@ -37,7 +37,7 @@ const voteRequest = () => ({ type: POLL_VOTE_REQUEST, }); -const voteSuccess = (poll: APIEntity) => ({ +const voteSuccess = (poll: Poll) => ({ type: POLL_VOTE_SUCCESS, poll, }); @@ -51,7 +51,7 @@ const fetchPollRequest = () => ({ type: POLL_FETCH_REQUEST, }); -const fetchPollSuccess = (poll: APIEntity) => ({ +const fetchPollSuccess = (poll: Poll) => ({ type: POLL_FETCH_SUCCESS, poll, }); diff --git a/src/actions/preload.ts b/src/actions/preload.ts index 1824afb8f..61f88fdd4 100644 --- a/src/actions/preload.ts +++ b/src/actions/preload.ts @@ -5,8 +5,8 @@ import { importFetchedAccounts } from './importer'; import type { AppDispatch } from 'soapbox/store'; -const PLEROMA_PRELOAD_IMPORT = 'PLEROMA_PRELOAD_IMPORT'; -const MASTODON_PRELOAD_IMPORT = 'MASTODON_PRELOAD_IMPORT'; +const PLEROMA_PRELOAD_IMPORT = 'PLEROMA_PRELOAD_IMPORT' as const; +const MASTODON_PRELOAD_IMPORT = 'MASTODON_PRELOAD_IMPORT' as const; // https://git.pleroma.social/pleroma/pleroma-fe/-/merge_requests/1176/diffs const decodeUTF8Base64 = (data: string) => { @@ -59,10 +59,16 @@ const preloadMastodon = (data: Record) => dispatch({ type: MASTODON_PRELOAD_IMPORT, data }); }; +interface PreloadAction { + type: typeof PLEROMA_PRELOAD_IMPORT | typeof MASTODON_PRELOAD_IMPORT; + data: Record; +} + export { PLEROMA_PRELOAD_IMPORT, MASTODON_PRELOAD_IMPORT, preload, preloadPleroma, preloadMastodon, + type PreloadAction, }; diff --git a/src/actions/push-subscriptions.ts b/src/actions/push-subscriptions.ts index 6162726f1..01a64d244 100644 --- a/src/actions/push-subscriptions.ts +++ b/src/actions/push-subscriptions.ts @@ -3,21 +3,21 @@ import { getClient } from '../api'; import type { CreatePushNotificationsSubscriptionParams } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -const PUSH_SUBSCRIPTION_CREATE_REQUEST = 'PUSH_SUBSCRIPTION_CREATE_REQUEST'; -const PUSH_SUBSCRIPTION_CREATE_SUCCESS = 'PUSH_SUBSCRIPTION_CREATE_SUCCESS'; -const PUSH_SUBSCRIPTION_CREATE_FAIL = 'PUSH_SUBSCRIPTION_CREATE_FAIL'; +const PUSH_SUBSCRIPTION_CREATE_REQUEST = 'PUSH_SUBSCRIPTION_CREATE_REQUEST' as const; +const PUSH_SUBSCRIPTION_CREATE_SUCCESS = 'PUSH_SUBSCRIPTION_CREATE_SUCCESS' as const; +const PUSH_SUBSCRIPTION_CREATE_FAIL = 'PUSH_SUBSCRIPTION_CREATE_FAIL' as const; -const PUSH_SUBSCRIPTION_FETCH_REQUEST = 'PUSH_SUBSCRIPTION_FETCH_REQUEST'; -const PUSH_SUBSCRIPTION_FETCH_SUCCESS = 'PUSH_SUBSCRIPTION_FETCH_SUCCESS'; -const PUSH_SUBSCRIPTION_FETCH_FAIL = 'PUSH_SUBSCRIPTION_FETCH_FAIL'; +const PUSH_SUBSCRIPTION_FETCH_REQUEST = 'PUSH_SUBSCRIPTION_FETCH_REQUEST' as const; +const PUSH_SUBSCRIPTION_FETCH_SUCCESS = 'PUSH_SUBSCRIPTION_FETCH_SUCCESS' as const; +const PUSH_SUBSCRIPTION_FETCH_FAIL = 'PUSH_SUBSCRIPTION_FETCH_FAIL' as const; -const PUSH_SUBSCRIPTION_UPDATE_REQUEST = 'PUSH_SUBSCRIPTION_UPDATE_REQUEST'; -const PUSH_SUBSCRIPTION_UPDATE_SUCCESS = 'PUSH_SUBSCRIPTION_UPDATE_SUCCESS'; -const PUSH_SUBSCRIPTION_UPDATE_FAIL = 'PUSH_SUBSCRIPTION_UPDATE_FAIL'; +const PUSH_SUBSCRIPTION_UPDATE_REQUEST = 'PUSH_SUBSCRIPTION_UPDATE_REQUEST' as const; +const PUSH_SUBSCRIPTION_UPDATE_SUCCESS = 'PUSH_SUBSCRIPTION_UPDATE_SUCCESS' as const; +const PUSH_SUBSCRIPTION_UPDATE_FAIL = 'PUSH_SUBSCRIPTION_UPDATE_FAIL' as const; -const PUSH_SUBSCRIPTION_DELETE_REQUEST = 'PUSH_SUBSCRIPTION_DELETE_REQUEST'; -const PUSH_SUBSCRIPTION_DELETE_SUCCESS = 'PUSH_SUBSCRIPTION_DELETE_SUCCESS'; -const PUSH_SUBSCRIPTION_DELETE_FAIL = 'PUSH_SUBSCRIPTION_DELETE_FAIL'; +const PUSH_SUBSCRIPTION_DELETE_REQUEST = 'PUSH_SUBSCRIPTION_DELETE_REQUEST' as const; +const PUSH_SUBSCRIPTION_DELETE_SUCCESS = 'PUSH_SUBSCRIPTION_DELETE_SUCCESS' as const; +const PUSH_SUBSCRIPTION_DELETE_FAIL = 'PUSH_SUBSCRIPTION_DELETE_FAIL' as const; const createPushSubscription = (params: CreatePushNotificationsSubscriptionParams) => (dispatch: AppDispatch, getState: () => RootState) => { diff --git a/src/actions/reports.ts b/src/actions/reports.ts index 511924f26..d3dfd8717 100644 --- a/src/actions/reports.ts +++ b/src/actions/reports.ts @@ -2,23 +2,22 @@ import { getClient } from '../api'; import { openModal } from './modals'; -import type { Account } from 'soapbox/schemas'; +import type { Account, Status } from 'soapbox/normalizers'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { ChatMessage, Group, Status } from 'soapbox/types/entities'; -const REPORT_INIT = 'REPORT_INIT'; -const REPORT_CANCEL = 'REPORT_CANCEL'; +const REPORT_INIT = 'REPORT_INIT' as const; +const REPORT_CANCEL = 'REPORT_CANCEL' as const; -const REPORT_SUBMIT_REQUEST = 'REPORT_SUBMIT_REQUEST'; -const REPORT_SUBMIT_SUCCESS = 'REPORT_SUBMIT_SUCCESS'; -const REPORT_SUBMIT_FAIL = 'REPORT_SUBMIT_FAIL'; +const REPORT_SUBMIT_REQUEST = 'REPORT_SUBMIT_REQUEST' as const; +const REPORT_SUBMIT_SUCCESS = 'REPORT_SUBMIT_SUCCESS' as const; +const REPORT_SUBMIT_FAIL = 'REPORT_SUBMIT_FAIL' as const; -const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE'; -const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE'; -const REPORT_FORWARD_CHANGE = 'REPORT_FORWARD_CHANGE'; -const REPORT_BLOCK_CHANGE = 'REPORT_BLOCK_CHANGE'; +const REPORT_STATUS_TOGGLE = 'REPORT_STATUS_TOGGLE' as const; +const REPORT_COMMENT_CHANGE = 'REPORT_COMMENT_CHANGE' as const; +const REPORT_FORWARD_CHANGE = 'REPORT_FORWARD_CHANGE' as const; +const REPORT_BLOCK_CHANGE = 'REPORT_BLOCK_CHANGE' as const; -const REPORT_RULE_CHANGE = 'REPORT_RULE_CHANGE'; +const REPORT_RULE_CHANGE = 'REPORT_RULE_CHANGE' as const; enum ReportableEntities { ACCOUNT = 'ACCOUNT', @@ -26,21 +25,17 @@ enum ReportableEntities { } type ReportedEntity = { - status?: Status; - chatMessage?: ChatMessage; - group?: Group; + status?: Pick; } -const initReport = (entityType: ReportableEntities, account: Account, entities?: ReportedEntity) => (dispatch: AppDispatch) => { - const { status, chatMessage, group } = entities || {}; +const initReport = (entityType: ReportableEntities, account: Pick, entities?: ReportedEntity) => (dispatch: AppDispatch) => { + const { status } = entities || {}; dispatch({ type: REPORT_INIT, entityType, account, status, - chatMessage, - group, }); return dispatch(openModal('REPORT')); @@ -63,7 +58,6 @@ const submitReport = () => return getClient(getState()).accounts.reportAccount(reports.new.account_id!, { status_ids: reports.new.status_ids.toArray(), - // group_id: reports.getIn(['new', 'group', 'id']), rule_ids: reports.new.rule_ids.toArray(), comment: reports.new.comment, forward: reports.new.forward, diff --git a/src/actions/scheduled-statuses.ts b/src/actions/scheduled-statuses.ts index 00908a645..6acc3b0d2 100644 --- a/src/actions/scheduled-statuses.ts +++ b/src/actions/scheduled-statuses.ts @@ -1,22 +1,19 @@ -import { getFeatures } from 'soapbox/utils/features'; - import { getClient } from '../api'; import type { PaginatedResponse, ScheduledStatus } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; -const SCHEDULED_STATUSES_FETCH_REQUEST = 'SCHEDULED_STATUSES_FETCH_REQUEST'; -const SCHEDULED_STATUSES_FETCH_SUCCESS = 'SCHEDULED_STATUSES_FETCH_SUCCESS'; -const SCHEDULED_STATUSES_FETCH_FAIL = 'SCHEDULED_STATUSES_FETCH_FAIL'; +const SCHEDULED_STATUSES_FETCH_REQUEST = 'SCHEDULED_STATUSES_FETCH_REQUEST' as const; +const SCHEDULED_STATUSES_FETCH_SUCCESS = 'SCHEDULED_STATUSES_FETCH_SUCCESS' as const; +const SCHEDULED_STATUSES_FETCH_FAIL = 'SCHEDULED_STATUSES_FETCH_FAIL' as const; -const SCHEDULED_STATUSES_EXPAND_REQUEST = 'SCHEDULED_STATUSES_EXPAND_REQUEST'; -const SCHEDULED_STATUSES_EXPAND_SUCCESS = 'SCHEDULED_STATUSES_EXPAND_SUCCESS'; -const SCHEDULED_STATUSES_EXPAND_FAIL = 'SCHEDULED_STATUSES_EXPAND_FAIL'; +const SCHEDULED_STATUSES_EXPAND_REQUEST = 'SCHEDULED_STATUSES_EXPAND_REQUEST' as const; +const SCHEDULED_STATUSES_EXPAND_SUCCESS = 'SCHEDULED_STATUSES_EXPAND_SUCCESS' as const; +const SCHEDULED_STATUSES_EXPAND_FAIL = 'SCHEDULED_STATUSES_EXPAND_FAIL' as const; -const SCHEDULED_STATUS_CANCEL_REQUEST = 'SCHEDULED_STATUS_CANCEL_REQUEST'; -const SCHEDULED_STATUS_CANCEL_SUCCESS = 'SCHEDULED_STATUS_CANCEL_SUCCESS'; -const SCHEDULED_STATUS_CANCEL_FAIL = 'SCHEDULED_STATUS_CANCEL_FAIL'; +const SCHEDULED_STATUS_CANCEL_REQUEST = 'SCHEDULED_STATUS_CANCEL_REQUEST' as const; +const SCHEDULED_STATUS_CANCEL_SUCCESS = 'SCHEDULED_STATUS_CANCEL_SUCCESS' as const; +const SCHEDULED_STATUS_CANCEL_FAIL = 'SCHEDULED_STATUS_CANCEL_FAIL' as const; const fetchScheduledStatuses = () => (dispatch: AppDispatch, getState: () => RootState) => { @@ -26,8 +23,7 @@ const fetchScheduledStatuses = () => return; } - const instance = state.instance; - const features = getFeatures(instance); + const features = state.auth.client.features; if (!features.scheduledStatuses) return; @@ -40,13 +36,13 @@ const fetchScheduledStatuses = () => }); }; -const cancelScheduledStatus = (id: string) => +const cancelScheduledStatus = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - dispatch({ type: SCHEDULED_STATUS_CANCEL_REQUEST, id }); - return getClient(getState()).scheduledStatuses.cancelScheduledStatus(id).then((data) => { - dispatch({ type: SCHEDULED_STATUS_CANCEL_SUCCESS, id, data }); + dispatch({ type: SCHEDULED_STATUS_CANCEL_REQUEST, statusId }); + return getClient(getState()).scheduledStatuses.cancelScheduledStatus(statusId).then(() => { + dispatch({ type: SCHEDULED_STATUS_CANCEL_SUCCESS, statusId }); }).catch(error => { - dispatch({ type: SCHEDULED_STATUS_CANCEL_FAIL, id, error }); + dispatch({ type: SCHEDULED_STATUS_CANCEL_FAIL, statusId, error }); }); }; @@ -54,7 +50,7 @@ const fetchScheduledStatusesRequest = () => ({ type: SCHEDULED_STATUSES_FETCH_REQUEST, }); -const fetchScheduledStatusesSuccess = (statuses: APIEntity[], next: (() => Promise>) | null) => ({ +const fetchScheduledStatusesSuccess = (statuses: Array, next: (() => Promise>) | null) => ({ type: SCHEDULED_STATUSES_FETCH_SUCCESS, statuses, next, @@ -86,7 +82,7 @@ const expandScheduledStatusesRequest = () => ({ type: SCHEDULED_STATUSES_EXPAND_REQUEST, }); -const expandScheduledStatusesSuccess = (statuses: APIEntity[], next: (() => Promise>) | null) => ({ +const expandScheduledStatusesSuccess = (statuses: Array, next: (() => Promise>) | null) => ({ type: SCHEDULED_STATUSES_EXPAND_SUCCESS, statuses, next, diff --git a/src/actions/search.ts b/src/actions/search.ts index f9a924bfb..4960cf49c 100644 --- a/src/actions/search.ts +++ b/src/actions/search.ts @@ -6,24 +6,23 @@ import { importFetchedAccounts, importFetchedStatuses } from './importer'; import type { Search } from 'pl-api'; import type { SearchFilter } from 'soapbox/reducers/search'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; -const SEARCH_CHANGE = 'SEARCH_CHANGE'; -const SEARCH_CLEAR = 'SEARCH_CLEAR'; -const SEARCH_SHOW = 'SEARCH_SHOW'; -const SEARCH_RESULTS_CLEAR = 'SEARCH_RESULTS_CLEAR'; +const SEARCH_CHANGE = 'SEARCH_CHANGE' as const; +const SEARCH_CLEAR = 'SEARCH_CLEAR' as const; +const SEARCH_SHOW = 'SEARCH_SHOW' as const; +const SEARCH_RESULTS_CLEAR = 'SEARCH_RESULTS_CLEAR' as const; -const SEARCH_FETCH_REQUEST = 'SEARCH_FETCH_REQUEST'; -const SEARCH_FETCH_SUCCESS = 'SEARCH_FETCH_SUCCESS'; -const SEARCH_FETCH_FAIL = 'SEARCH_FETCH_FAIL'; +const SEARCH_FETCH_REQUEST = 'SEARCH_FETCH_REQUEST' as const; +const SEARCH_FETCH_SUCCESS = 'SEARCH_FETCH_SUCCESS' as const; +const SEARCH_FETCH_FAIL = 'SEARCH_FETCH_FAIL' as const; -const SEARCH_FILTER_SET = 'SEARCH_FILTER_SET'; +const SEARCH_FILTER_SET = 'SEARCH_FILTER_SET' as const; -const SEARCH_EXPAND_REQUEST = 'SEARCH_EXPAND_REQUEST'; -const SEARCH_EXPAND_SUCCESS = 'SEARCH_EXPAND_SUCCESS'; -const SEARCH_EXPAND_FAIL = 'SEARCH_EXPAND_FAIL'; +const SEARCH_EXPAND_REQUEST = 'SEARCH_EXPAND_REQUEST' as const; +const SEARCH_EXPAND_SUCCESS = 'SEARCH_EXPAND_SUCCESS' as const; +const SEARCH_EXPAND_FAIL = 'SEARCH_EXPAND_FAIL' as const; -const SEARCH_ACCOUNT_SET = 'SEARCH_ACCOUNT_SET'; +const SEARCH_ACCOUNT_SET = 'SEARCH_ACCOUNT_SET' as const; const changeSearch = (value: string) => (dispatch: AppDispatch) => { @@ -81,7 +80,7 @@ const submitSearch = (filter?: SearchFilter) => } dispatch(fetchSearchSuccess(response, value, type)); - dispatch(fetchRelationships(response.accounts.map((item: APIEntity) => item.id))); + dispatch(fetchRelationships(response.accounts.map((item) => item.id))); }).catch(error => { dispatch(fetchSearchFail(error)); }); @@ -116,8 +115,8 @@ const setFilter = (filterType: SearchFilter) => }; const expandSearch = (type: SearchFilter) => (dispatch: AppDispatch, getState: () => RootState) => { - const value = getState().search.value; - const offset = getState().search.results[type].size; + const value = getState().search.value; + const offset = getState().search.results[type].size; const accountId = getState().search.accountId; dispatch(expandSearchRequest(type)); @@ -138,7 +137,7 @@ const expandSearch = (type: SearchFilter) => (dispatch: AppDispatch, getState: ( } dispatch(expandSearchSuccess(response, value, type)); - dispatch(fetchRelationships(response.accounts.map((item: APIEntity) => item.id))); + dispatch(fetchRelationships(response.accounts.map((item) => item.id))); }).catch(error => { dispatch(expandSearchFail(error)); }); diff --git a/src/actions/security.ts b/src/actions/security.ts index 9cbe6224b..96235d2bf 100644 --- a/src/actions/security.ts +++ b/src/actions/security.ts @@ -7,101 +7,74 @@ import { getClient } from 'soapbox/api'; import toast from 'soapbox/toast'; import { getLoggedInAccount } from 'soapbox/utils/auth'; -import { GOTOSOCIAL, parseVersion } from 'soapbox/utils/features'; import { normalizeUsername } from 'soapbox/utils/input'; import { AUTH_LOGGED_OUT, messages } from './auth'; import type { AppDispatch, RootState } from 'soapbox/store'; -const FETCH_TOKENS_REQUEST = 'FETCH_TOKENS_REQUEST'; -const FETCH_TOKENS_SUCCESS = 'FETCH_TOKENS_SUCCESS'; -const FETCH_TOKENS_FAIL = 'FETCH_TOKENS_FAIL'; +const FETCH_TOKENS_REQUEST = 'FETCH_TOKENS_REQUEST' as const; +const FETCH_TOKENS_SUCCESS = 'FETCH_TOKENS_SUCCESS' as const; +const FETCH_TOKENS_FAIL = 'FETCH_TOKENS_FAIL' as const; -const REVOKE_TOKEN_REQUEST = 'REVOKE_TOKEN_REQUEST'; -const REVOKE_TOKEN_SUCCESS = 'REVOKE_TOKEN_SUCCESS'; -const REVOKE_TOKEN_FAIL = 'REVOKE_TOKEN_FAIL'; +const REVOKE_TOKEN_REQUEST = 'REVOKE_TOKEN_REQUEST' as const; +const REVOKE_TOKEN_SUCCESS = 'REVOKE_TOKEN_SUCCESS' as const; +const REVOKE_TOKEN_FAIL = 'REVOKE_TOKEN_FAIL' as const; -const RESET_PASSWORD_REQUEST = 'RESET_PASSWORD_REQUEST'; -const RESET_PASSWORD_SUCCESS = 'RESET_PASSWORD_SUCCESS'; -const RESET_PASSWORD_FAIL = 'RESET_PASSWORD_FAIL'; +const RESET_PASSWORD_REQUEST = 'RESET_PASSWORD_REQUEST' as const; +const RESET_PASSWORD_SUCCESS = 'RESET_PASSWORD_SUCCESS' as const; +const RESET_PASSWORD_FAIL = 'RESET_PASSWORD_FAIL' as const; -const RESET_PASSWORD_CONFIRM_REQUEST = 'RESET_PASSWORD_CONFIRM_REQUEST'; -const RESET_PASSWORD_CONFIRM_SUCCESS = 'RESET_PASSWORD_CONFIRM_SUCCESS'; -const RESET_PASSWORD_CONFIRM_FAIL = 'RESET_PASSWORD_CONFIRM_FAIL'; +const RESET_PASSWORD_CONFIRM_REQUEST = 'RESET_PASSWORD_CONFIRM_REQUEST' as const; +const RESET_PASSWORD_CONFIRM_SUCCESS = 'RESET_PASSWORD_CONFIRM_SUCCESS' as const; +const RESET_PASSWORD_CONFIRM_FAIL = 'RESET_PASSWORD_CONFIRM_FAIL' as const; -const CHANGE_PASSWORD_REQUEST = 'CHANGE_PASSWORD_REQUEST'; -const CHANGE_PASSWORD_SUCCESS = 'CHANGE_PASSWORD_SUCCESS'; -const CHANGE_PASSWORD_FAIL = 'CHANGE_PASSWORD_FAIL'; +const CHANGE_PASSWORD_REQUEST = 'CHANGE_PASSWORD_REQUEST' as const; +const CHANGE_PASSWORD_SUCCESS = 'CHANGE_PASSWORD_SUCCESS' as const; +const CHANGE_PASSWORD_FAIL = 'CHANGE_PASSWORD_FAIL' as const; -const CHANGE_EMAIL_REQUEST = 'CHANGE_EMAIL_REQUEST'; -const CHANGE_EMAIL_SUCCESS = 'CHANGE_EMAIL_SUCCESS'; -const CHANGE_EMAIL_FAIL = 'CHANGE_EMAIL_FAIL'; +const CHANGE_EMAIL_REQUEST = 'CHANGE_EMAIL_REQUEST' as const; +const CHANGE_EMAIL_SUCCESS = 'CHANGE_EMAIL_SUCCESS' as const; +const CHANGE_EMAIL_FAIL = 'CHANGE_EMAIL_FAIL' as const; -const DELETE_ACCOUNT_REQUEST = 'DELETE_ACCOUNT_REQUEST'; -const DELETE_ACCOUNT_SUCCESS = 'DELETE_ACCOUNT_SUCCESS'; -const DELETE_ACCOUNT_FAIL = 'DELETE_ACCOUNT_FAIL'; +const DELETE_ACCOUNT_REQUEST = 'DELETE_ACCOUNT_REQUEST' as const; +const DELETE_ACCOUNT_SUCCESS = 'DELETE_ACCOUNT_SUCCESS' as const; +const DELETE_ACCOUNT_FAIL = 'DELETE_ACCOUNT_FAIL' as const; -const MOVE_ACCOUNT_REQUEST = 'MOVE_ACCOUNT_REQUEST'; -const MOVE_ACCOUNT_SUCCESS = 'MOVE_ACCOUNT_SUCCESS'; -const MOVE_ACCOUNT_FAIL = 'MOVE_ACCOUNT_FAIL'; +const MOVE_ACCOUNT_REQUEST = 'MOVE_ACCOUNT_REQUEST' as const; +const MOVE_ACCOUNT_SUCCESS = 'MOVE_ACCOUNT_SUCCESS' as const; +const MOVE_ACCOUNT_FAIL = 'MOVE_ACCOUNT_FAIL' as const; const fetchOAuthTokens = () => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: FETCH_TOKENS_REQUEST }); - return getClient(getState).request('/api/oauth_tokens').then(({ json: tokens }) => { + return getClient(getState).settings.getOauthTokens().then((tokens) => { dispatch({ type: FETCH_TOKENS_SUCCESS, tokens }); }).catch(() => { dispatch({ type: FETCH_TOKENS_FAIL }); }); }; -const revokeOAuthTokenById = (id: number) => +const revokeOAuthTokenById = (tokenId: number) => (dispatch: AppDispatch, getState: () => RootState) => { - dispatch({ type: REVOKE_TOKEN_REQUEST, id }); - return getClient(getState).request(`/api/oauth_tokens/${id}`, { method: 'DELETE' }).then(() => { - dispatch({ type: REVOKE_TOKEN_SUCCESS, id }); + dispatch({ type: REVOKE_TOKEN_REQUEST, tokenId }); + return getClient(getState).settings.deleteOauthToken(tokenId).then(() => { + dispatch({ type: REVOKE_TOKEN_SUCCESS, tokenId }); }).catch(() => { - dispatch({ type: REVOKE_TOKEN_FAIL, id }); + dispatch({ type: REVOKE_TOKEN_FAIL, tokenId }); }); }; -const changePassword = (oldPassword: string, newPassword: string, confirmation: string) => +const changePassword = (oldPassword: string, newPassword: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: CHANGE_PASSWORD_REQUEST }); - const state = getState(); - const instance = state.instance; - const v = parseVersion(instance.version); - if (v.software === GOTOSOCIAL) { - return getClient(getState).request('/api/v1/user/password_change', { - method: 'POST', - body: { - old_password: oldPassword, - new_password: newPassword, - }, - }).then(response => { - dispatch({ type: CHANGE_PASSWORD_SUCCESS, response }); - }).catch(error => { - dispatch({ type: CHANGE_PASSWORD_FAIL, error, skipAlert: true }); - throw error; - }); - } else { - return getClient(getState).request('/api/pleroma/change_password', { - method: 'POST', - body: { - password: oldPassword, - new_password: newPassword, - new_password_confirmation: confirmation, - }, - }).then(response => { - if (response.json.error) throw response.json.error; // This endpoint returns HTTP 200 even on failure - dispatch({ type: CHANGE_PASSWORD_SUCCESS, response }); - }).catch(error => { - dispatch({ type: CHANGE_PASSWORD_FAIL, error, skipAlert: true }); - throw error; - }); - } + return getClient(getState).settings.changePassword(oldPassword, newPassword).then(response => { + dispatch({ type: CHANGE_PASSWORD_SUCCESS, response }); + }).catch(error => { + dispatch({ type: CHANGE_PASSWORD_FAIL, error, skipAlert: true }); + throw error; + }); }; const resetPassword = (usernameOrEmail: string) => @@ -110,14 +83,10 @@ const resetPassword = (usernameOrEmail: string) => dispatch({ type: RESET_PASSWORD_REQUEST }); - const params = - input.includes('@') - ? { email: input } - : { nickname: input, username: input }; - - return getClient(getState).request('/auth/password', { - method: 'POST', body: params, - }).then(() => { + return getClient(getState).settings.resetPassword( + input.includes('@') ? input : undefined, + input.includes('@') ? undefined : input, + ).then(() => { dispatch({ type: RESET_PASSWORD_SUCCESS }); }).catch(error => { dispatch({ type: RESET_PASSWORD_FAIL, error }); @@ -128,18 +97,8 @@ const resetPassword = (usernameOrEmail: string) => const changeEmail = (email: string, password: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: CHANGE_EMAIL_REQUEST, email }); - const state = getState(); - const instance = state.instance; - const v = parseVersion(instance.version); - return getClient(getState).request(v.software === GOTOSOCIAL ? '/api/v1/user/email_change' : '/api/pleroma/change_email', { - method: 'POST', - body: { - [v.software === GOTOSOCIAL ? 'new_email' : 'email']: email, - password, - }, - }).then(response => { - if (response.json.error) throw response.json.error; // This endpoint returns HTTP 200 even on failure + return getClient(getState).settings.changeEmail(email, password).then(response => { dispatch({ type: CHANGE_EMAIL_SUCCESS, email, response }); }).catch(error => { dispatch({ type: CHANGE_EMAIL_FAIL, email, error, skipAlert: true }); @@ -151,15 +110,9 @@ const deleteAccount = (password: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: CHANGE_PASSWORD_REQUEST }); const account = getLoggedInAccount(getState()); - const state = getState(); - const instance = state.instance; - const v = parseVersion(instance.version); dispatch({ type: DELETE_ACCOUNT_REQUEST }); - return getClient(getState).request(v.software === GOTOSOCIAL ? '/api/v1/accounts/delete' : '/api/pleroma/delete_account', { - method: 'POST', body: { password }, - }).then(response => { - if (response.json.error) throw response.json.error; // This endpoint returns HTTP 200 even on failure + return getClient(getState).settings.deleteAccount(password).then(response => { dispatch({ type: DELETE_ACCOUNT_SUCCESS, response }); dispatch({ type: AUTH_LOGGED_OUT, account }); toast.success(messages.loggedOut); @@ -172,11 +125,7 @@ const deleteAccount = (password: string) => const moveAccount = (targetAccount: string, password: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: MOVE_ACCOUNT_REQUEST }); - return getClient(getState).request('/api/pleroma/move_account', { - method: 'POST', - body: { password, target_account: targetAccount }, - }).then(response => { - if (response.json.error) throw response.json.error; // This endpoint returns HTTP 200 even on failure + return getClient(getState).settings.moveAccount(targetAccount, password).then(response => { dispatch({ type: MOVE_ACCOUNT_SUCCESS, response }); }).catch(error => { dispatch({ type: MOVE_ACCOUNT_FAIL, error, skipAlert: true }); diff --git a/src/actions/settings.ts b/src/actions/settings.ts index d645f5f3b..7bda7c00c 100644 --- a/src/actions/settings.ts +++ b/src/actions/settings.ts @@ -10,7 +10,7 @@ import { isLoggedIn } from 'soapbox/utils/auth'; import type { AppDispatch, RootState } from 'soapbox/store'; const SETTING_CHANGE = 'SETTING_CHANGE' as const; -const SETTING_SAVE = 'SETTING_SAVE' as const; +const SETTING_SAVE = 'SETTING_SAVE' as const; const SETTINGS_UPDATE = 'SETTINGS_UPDATE' as const; const FE_NAME = 'pl_fe'; @@ -84,7 +84,7 @@ const defaultSettings = ImmutableMap({ }), }), - community: ImmutableMap({ + 'public:local': ImmutableMap({ shows: ImmutableMap({ reblog: false, reply: true, diff --git a/src/actions/sidebar.ts b/src/actions/sidebar.ts index 3c9d05cff..6d5110220 100644 --- a/src/actions/sidebar.ts +++ b/src/actions/sidebar.ts @@ -1,4 +1,4 @@ -const SIDEBAR_OPEN = 'SIDEBAR_OPEN'; +const SIDEBAR_OPEN = 'SIDEBAR_OPEN'; const SIDEBAR_CLOSE = 'SIDEBAR_CLOSE'; const openSidebar = () => ({ diff --git a/src/actions/soapbox.ts b/src/actions/soapbox.ts index fcdf19625..909f9af55 100644 --- a/src/actions/soapbox.ts +++ b/src/actions/soapbox.ts @@ -3,23 +3,22 @@ import { createSelector } from 'reselect'; import { getHost } from 'soapbox/actions/instance'; import { normalizeSoapboxConfig } from 'soapbox/normalizers'; import KVStore from 'soapbox/storage/kv-store'; -import { getFeatures } from 'soapbox/utils/features'; import { getClient, staticFetch } from '../api'; import type { AppDispatch, RootState } from 'soapbox/store'; import type { APIEntity } from 'soapbox/types/entities'; -const SOAPBOX_CONFIG_REQUEST_SUCCESS = 'SOAPBOX_CONFIG_REQUEST_SUCCESS'; -const SOAPBOX_CONFIG_REQUEST_FAIL = 'SOAPBOX_CONFIG_REQUEST_FAIL'; +const SOAPBOX_CONFIG_REQUEST_SUCCESS = 'SOAPBOX_CONFIG_REQUEST_SUCCESS' as const; +const SOAPBOX_CONFIG_REQUEST_FAIL = 'SOAPBOX_CONFIG_REQUEST_FAIL' as const; -const SOAPBOX_CONFIG_REMEMBER_REQUEST = 'SOAPBOX_CONFIG_REMEMBER_REQUEST'; -const SOAPBOX_CONFIG_REMEMBER_SUCCESS = 'SOAPBOX_CONFIG_REMEMBER_SUCCESS'; -const SOAPBOX_CONFIG_REMEMBER_FAIL = 'SOAPBOX_CONFIG_REMEMBER_FAIL'; +const SOAPBOX_CONFIG_REMEMBER_REQUEST = 'SOAPBOX_CONFIG_REMEMBER_REQUEST' as const; +const SOAPBOX_CONFIG_REMEMBER_SUCCESS = 'SOAPBOX_CONFIG_REMEMBER_SUCCESS' as const; +const SOAPBOX_CONFIG_REMEMBER_FAIL = 'SOAPBOX_CONFIG_REMEMBER_FAIL' as const; const getSoapboxConfig = createSelector([ (state: RootState) => state.soapbox, - (state: RootState) => getFeatures(state.instance), + (state: RootState) => state.auth.client.features, ], (soapbox, features) => { // Do some additional normalization with the state return normalizeSoapboxConfig(soapbox).withMutations(soapboxConfig => { @@ -44,13 +43,12 @@ const rememberSoapboxConfig = (host: string | null) => const fetchFrontendConfigurations = () => (dispatch: AppDispatch, getState: () => RootState) => - getClient(getState).request('/api/pleroma/frontend_configurations') - .then(({ json: data }) => data); + getClient(getState).instance.getFrontendConfigurations(); /** Conditionally fetches Soapbox config depending on backend features */ const fetchSoapboxConfig = (host: string | null) => (dispatch: AppDispatch, getState: () => RootState) => { - const features = getFeatures(getState().instance); + const features = getState().auth.client.features; if (features.frontendConfigurations) { return dispatch(fetchFrontendConfigurations()).then(data => { diff --git a/src/actions/status-quotes.ts b/src/actions/status-quotes.ts index 9b8ba6338..9d35ee5e0 100644 --- a/src/actions/status-quotes.ts +++ b/src/actions/status-quotes.ts @@ -4,13 +4,13 @@ import { importFetchedStatuses } from './importer'; import type { AppDispatch, RootState } from 'soapbox/store'; -const STATUS_QUOTES_FETCH_REQUEST = 'STATUS_QUOTES_FETCH_REQUEST'; -const STATUS_QUOTES_FETCH_SUCCESS = 'STATUS_QUOTES_FETCH_SUCCESS'; -const STATUS_QUOTES_FETCH_FAIL = 'STATUS_QUOTES_FETCH_FAIL'; +const STATUS_QUOTES_FETCH_REQUEST = 'STATUS_QUOTES_FETCH_REQUEST' as const; +const STATUS_QUOTES_FETCH_SUCCESS = 'STATUS_QUOTES_FETCH_SUCCESS' as const; +const STATUS_QUOTES_FETCH_FAIL = 'STATUS_QUOTES_FETCH_FAIL' as const; -const STATUS_QUOTES_EXPAND_REQUEST = 'STATUS_QUOTES_EXPAND_REQUEST'; -const STATUS_QUOTES_EXPAND_SUCCESS = 'STATUS_QUOTES_EXPAND_SUCCESS'; -const STATUS_QUOTES_EXPAND_FAIL = 'STATUS_QUOTES_EXPAND_FAIL'; +const STATUS_QUOTES_EXPAND_REQUEST = 'STATUS_QUOTES_EXPAND_REQUEST' as const; +const STATUS_QUOTES_EXPAND_SUCCESS = 'STATUS_QUOTES_EXPAND_SUCCESS' as const; +const STATUS_QUOTES_EXPAND_FAIL = 'STATUS_QUOTES_EXPAND_FAIL' as const; const noOp = () => new Promise(f => f(null)); diff --git a/src/actions/statuses.ts b/src/actions/statuses.ts index e7c1e65bb..73b5a09e3 100644 --- a/src/actions/statuses.ts +++ b/src/actions/statuses.ts @@ -1,5 +1,4 @@ import { isLoggedIn } from 'soapbox/utils/auth'; -import { getFeatures } from 'soapbox/utils/features'; import { shouldHaveCard } from 'soapbox/utils/status'; import { getClient } from '../api'; @@ -10,53 +9,51 @@ import { openModal } from './modals'; import { getSettings } from './settings'; import { deleteFromTimelines } from './timelines'; -import type { CreateStatusParams } from 'pl-api'; +import type { CreateStatusParams, Status as BaseStatus } from 'pl-api'; import type { IntlShape } from 'react-intl'; +import type { Status } from 'soapbox/normalizers'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity, Status } from 'soapbox/types/entities'; +import type { APIEntity } from 'soapbox/types/entities'; -const STATUS_CREATE_REQUEST = 'STATUS_CREATE_REQUEST'; -const STATUS_CREATE_SUCCESS = 'STATUS_CREATE_SUCCESS'; -const STATUS_CREATE_FAIL = 'STATUS_CREATE_FAIL'; +const STATUS_CREATE_REQUEST = 'STATUS_CREATE_REQUEST' as const; +const STATUS_CREATE_SUCCESS = 'STATUS_CREATE_SUCCESS' as const; +const STATUS_CREATE_FAIL = 'STATUS_CREATE_FAIL' as const; -const STATUS_FETCH_SOURCE_REQUEST = 'STATUS_FETCH_SOURCE_REQUEST'; -const STATUS_FETCH_SOURCE_SUCCESS = 'STATUS_FETCH_SOURCE_SUCCESS'; -const STATUS_FETCH_SOURCE_FAIL = 'STATUS_FETCH_SOURCE_FAIL'; +const STATUS_FETCH_SOURCE_REQUEST = 'STATUS_FETCH_SOURCE_REQUEST' as const; +const STATUS_FETCH_SOURCE_SUCCESS = 'STATUS_FETCH_SOURCE_SUCCESS' as const; +const STATUS_FETCH_SOURCE_FAIL = 'STATUS_FETCH_SOURCE_FAIL' as const; -const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST'; -const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS'; -const STATUS_FETCH_FAIL = 'STATUS_FETCH_FAIL'; +const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST' as const; +const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS' as const; +const STATUS_FETCH_FAIL = 'STATUS_FETCH_FAIL' as const; -const STATUS_DELETE_REQUEST = 'STATUS_DELETE_REQUEST'; -const STATUS_DELETE_SUCCESS = 'STATUS_DELETE_SUCCESS'; -const STATUS_DELETE_FAIL = 'STATUS_DELETE_FAIL'; +const STATUS_DELETE_REQUEST = 'STATUS_DELETE_REQUEST' as const; +const STATUS_DELETE_SUCCESS = 'STATUS_DELETE_SUCCESS' as const; +const STATUS_DELETE_FAIL = 'STATUS_DELETE_FAIL' as const; -const CONTEXT_FETCH_REQUEST = 'CONTEXT_FETCH_REQUEST'; -const CONTEXT_FETCH_SUCCESS = 'CONTEXT_FETCH_SUCCESS'; -const CONTEXT_FETCH_FAIL = 'CONTEXT_FETCH_FAIL'; +const CONTEXT_FETCH_REQUEST = 'CONTEXT_FETCH_REQUEST' as const; +const CONTEXT_FETCH_SUCCESS = 'CONTEXT_FETCH_SUCCESS' as const; +const CONTEXT_FETCH_FAIL = 'CONTEXT_FETCH_FAIL' as const; -const STATUS_MUTE_REQUEST = 'STATUS_MUTE_REQUEST'; -const STATUS_MUTE_SUCCESS = 'STATUS_MUTE_SUCCESS'; -const STATUS_MUTE_FAIL = 'STATUS_MUTE_FAIL'; +const STATUS_MUTE_REQUEST = 'STATUS_MUTE_REQUEST' as const; +const STATUS_MUTE_SUCCESS = 'STATUS_MUTE_SUCCESS' as const; +const STATUS_MUTE_FAIL = 'STATUS_MUTE_FAIL' as const; -const STATUS_UNMUTE_REQUEST = 'STATUS_UNMUTE_REQUEST'; -const STATUS_UNMUTE_SUCCESS = 'STATUS_UNMUTE_SUCCESS'; -const STATUS_UNMUTE_FAIL = 'STATUS_UNMUTE_FAIL'; +const STATUS_UNMUTE_REQUEST = 'STATUS_UNMUTE_REQUEST' as const; +const STATUS_UNMUTE_SUCCESS = 'STATUS_UNMUTE_SUCCESS' as const; +const STATUS_UNMUTE_FAIL = 'STATUS_UNMUTE_FAIL' as const; -const STATUS_REVEAL = 'STATUS_REVEAL'; -const STATUS_HIDE = 'STATUS_HIDE'; +const STATUS_REVEAL = 'STATUS_REVEAL' as const; +const STATUS_HIDE = 'STATUS_HIDE' as const; -const STATUS_TRANSLATE_REQUEST = 'STATUS_TRANSLATE_REQUEST'; -const STATUS_TRANSLATE_SUCCESS = 'STATUS_TRANSLATE_SUCCESS'; -const STATUS_TRANSLATE_FAIL = 'STATUS_TRANSLATE_FAIL'; -const STATUS_TRANSLATE_UNDO = 'STATUS_TRANSLATE_UNDO'; +const STATUS_TRANSLATE_REQUEST = 'STATUS_TRANSLATE_REQUEST' as const; +const STATUS_TRANSLATE_SUCCESS = 'STATUS_TRANSLATE_SUCCESS' as const; +const STATUS_TRANSLATE_FAIL = 'STATUS_TRANSLATE_FAIL' as const; +const STATUS_TRANSLATE_UNDO = 'STATUS_TRANSLATE_UNDO' as const; -const STATUS_UNFILTER = 'STATUS_UNFILTER'; +const STATUS_UNFILTER = 'STATUS_UNFILTER' as const; -const STATUS_LANGUAGE_CHANGE = 'STATUS_LANGUAGE_CHANGE'; - -const statusExists = (getState: () => RootState, statusId: string) => - (getState().statuses.get(statusId) || null) !== null; +const STATUS_LANGUAGE_CHANGE = 'STATUS_LANGUAGE_CHANGE' as const; const createStatus = (params: CreateStatusParams, idempotencyKey: string, statusId: string | null) => (dispatch: AppDispatch, getState: () => RootState) => { @@ -67,7 +64,7 @@ const createStatus = (params: CreateStatusParams, idempotencyKey: string, status // The backend might still be processing the rich media attachment const expectsCard = !status.card && shouldHaveCard(status); - dispatch(importFetchedStatus({ ...status, expectsCard }, idempotencyKey)); + dispatch(importFetchedStatus({ ...status, expectsCard } as BaseStatus, idempotencyKey)); dispatch({ type: STATUS_CREATE_SUCCESS, status, params, idempotencyKey, editing: !!statusId }); // Poll the backend for the updated card @@ -94,18 +91,17 @@ const createStatus = (params: CreateStatusParams, idempotencyKey: string, status }); }; -const editStatus = (id: string) => (dispatch: AppDispatch, getState: () => RootState) => { - let status = getState().statuses.get(id)!; +const editStatus = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { + const state = getState(); - if (status.poll) { - status = status.set('poll', getState().polls.get(status.poll) as any); - } + const status = state.statuses.get(statusId)!; + const poll = status.poll ? state.polls.get(status.poll) : undefined; dispatch({ type: STATUS_FETCH_SOURCE_REQUEST }); - return getClient(getState()).statuses.getStatusSource(id).then(response => { + return getClient(state).statuses.getStatusSource(statusId).then(response => { dispatch({ type: STATUS_FETCH_SOURCE_SUCCESS }); - dispatch(setComposeToStatus(status, response.text, response.spoiler_text, response.content_type, false)); + dispatch(setComposeToStatus(status, poll, response.text, response.spoiler_text, response.content_type, false)); dispatch(openModal('COMPOSE')); }).catch(error => { dispatch({ type: STATUS_FETCH_SOURCE_FAIL, error }); @@ -113,43 +109,40 @@ const editStatus = (id: string) => (dispatch: AppDispatch, getState: () => RootS }); }; -const fetchStatus = (id: string, intl?: IntlShape) => +const fetchStatus = (statusId: string, intl?: IntlShape) => (dispatch: AppDispatch, getState: () => RootState) => { - const skipLoading = statusExists(getState, id); - - dispatch({ type: STATUS_FETCH_REQUEST, id, skipLoading }); + dispatch({ type: STATUS_FETCH_REQUEST, statusId }); const params = intl && getSettings(getState()).get('autoTranslate') ? { language: intl.locale, } : undefined; - return getClient(getState()).statuses.getStatus(id, params).then(status => { + return getClient(getState()).statuses.getStatus(statusId, params).then(status => { dispatch(importFetchedStatus(status)); - dispatch({ type: STATUS_FETCH_SUCCESS, status, skipLoading }); + dispatch({ type: STATUS_FETCH_SUCCESS, status }); return status; }).catch(error => { - dispatch({ type: STATUS_FETCH_FAIL, id, error, skipLoading, skipAlert: true }); + dispatch({ type: STATUS_FETCH_FAIL, statusId, error, skipAlert: true }); }); }; -const deleteStatus = (id: string, withRedraft = false) => +const deleteStatus = (statusId: string, withRedraft = false) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return null; - let status = getState().statuses.get(id)!; + const state = getState(); - if (status.poll) { - status = status.set('poll', getState().polls.get(status.poll) as any); - } + const status = state.statuses.get(statusId)!; + const poll = status.poll ? state.polls.get(status.poll) : undefined; dispatch({ type: STATUS_DELETE_REQUEST, params: status }); - return getClient(getState()).statuses.deleteStatus(id).then(response => { - dispatch({ type: STATUS_DELETE_SUCCESS, id }); - dispatch(deleteFromTimelines(id)); + return getClient(state).statuses.deleteStatus(statusId).then(response => { + dispatch({ type: STATUS_DELETE_SUCCESS, statusId }); + dispatch(deleteFromTimelines(statusId)); if (withRedraft) { - dispatch(setComposeToStatus(status, response.text || '', response.spoiler_text, response.content_type, withRedraft)); + dispatch(setComposeToStatus(status, poll, response.text || '', response.spoiler_text, response.content_type, withRedraft)); dispatch(openModal('COMPOSE')); } }) @@ -158,18 +151,18 @@ const deleteStatus = (id: string, withRedraft = false) => }); }; -const updateStatus = (status: APIEntity) => (dispatch: AppDispatch) => +const updateStatus = (status: BaseStatus) => (dispatch: AppDispatch) => dispatch(importFetchedStatus(status)); -const fetchContext = (id: string, intl?: IntlShape) => +const fetchContext = (statusId: string, intl?: IntlShape) => (dispatch: AppDispatch, getState: () => RootState) => { - dispatch({ type: CONTEXT_FETCH_REQUEST, id }); + dispatch({ type: CONTEXT_FETCH_REQUEST, statusId }); const params = intl && getSettings(getState()).get('autoTranslate') ? { language: intl.locale, } : undefined; - return getClient(getState()).statuses.getContext(id, params).then(context => { + return getClient(getState()).statuses.getContext(statusId, params).then(context => { if (Array.isArray(context)) { // Mitra: returns a list of statuses dispatch(importFetchedStatuses(context)); @@ -178,51 +171,51 @@ const fetchContext = (id: string, intl?: IntlShape) => const { ancestors, descendants } = context; const statuses = ancestors.concat(descendants); dispatch(importFetchedStatuses(statuses)); - dispatch({ type: CONTEXT_FETCH_SUCCESS, id, ancestors, descendants }); + dispatch({ type: CONTEXT_FETCH_SUCCESS, statusId, ancestors, descendants }); } else { throw context; } return context; }).catch(error => { if (error.response?.status === 404) { - dispatch(deleteFromTimelines(id)); + dispatch(deleteFromTimelines(statusId)); } - dispatch({ type: CONTEXT_FETCH_FAIL, id, error, skipAlert: true }); + dispatch({ type: CONTEXT_FETCH_FAIL, statusId, error, skipAlert: true }); }); }; -const fetchStatusWithContext = (id: string, intl?: IntlShape) => +const fetchStatusWithContext = (statusId: string, intl?: IntlShape) => async (dispatch: AppDispatch) => Promise.all([ - dispatch(fetchContext(id, intl)), - dispatch(fetchStatus(id, intl)), + dispatch(fetchContext(statusId, intl)), + dispatch(fetchStatus(statusId, intl)), ]); -const muteStatus = (id: string) => +const muteStatus = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - dispatch({ type: STATUS_MUTE_REQUEST, id }); - return getClient(getState()).statuses.muteStatus(id).then(() => { - dispatch({ type: STATUS_MUTE_SUCCESS, id }); + dispatch({ type: STATUS_MUTE_REQUEST, statusId }); + return getClient(getState()).statuses.muteStatus(statusId).then((status) => { + dispatch({ type: STATUS_MUTE_SUCCESS, statusId }); }).catch(error => { - dispatch({ type: STATUS_MUTE_FAIL, id, error }); + dispatch({ type: STATUS_MUTE_FAIL, statusId, error }); }); }; -const unmuteStatus = (id: string) => +const unmuteStatus = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { if (!isLoggedIn(getState)) return; - dispatch({ type: STATUS_UNMUTE_REQUEST, id }); - return getClient(getState()).statuses.unmuteStatus(id).then(() => { - dispatch({ type: STATUS_UNMUTE_SUCCESS, id }); + dispatch({ type: STATUS_UNMUTE_REQUEST, statusId }); + return getClient(getState()).statuses.unmuteStatus(statusId).then(() => { + dispatch({ type: STATUS_UNMUTE_SUCCESS, statusId }); }).catch(error => { - dispatch({ type: STATUS_UNMUTE_FAIL, id, error }); + dispatch({ type: STATUS_UNMUTE_FAIL, statusId, error }); }); }; -const toggleMuteStatus = (status: Status) => +const toggleMuteStatus = (status: Pick) => (dispatch: AppDispatch) => { if (status.muted) { dispatch(unmuteStatus(status.id)); @@ -231,29 +224,29 @@ const toggleMuteStatus = (status: Status) => } }; -const hideStatus = (ids: string[] | string) => { - if (!Array.isArray(ids)) { - ids = [ids]; +const hideStatus = (statusIds: string[] | string) => { + if (!Array.isArray(statusIds)) { + statusIds = [statusIds]; } return { type: STATUS_HIDE, - ids, + statusIds, }; }; -const revealStatus = (ids: string[] | string) => { - if (!Array.isArray(ids)) { - ids = [ids]; +const revealStatus = (statusIds: string[] | string) => { + if (!Array.isArray(statusIds)) { + statusIds = [statusIds]; } return { type: STATUS_REVEAL, - ids, + statusIds, }; }; -const toggleStatusHidden = (status: Status) => { +const toggleStatusHidden = (status: Pick) => { if (status.hidden) { return revealStatus(status.id); } else { @@ -264,82 +257,83 @@ const toggleStatusHidden = (status: Status) => { let TRANSLATIONS_QUEUE: Set = new Set(); let TRANSLATIONS_TIMEOUT: NodeJS.Timeout | null = null; -const translateStatus = (id: string, targetLanguage?: string, lazy?: boolean) => +const translateStatus = (statusId: string, targetLanguage?: string, lazy?: boolean) => (dispatch: AppDispatch, getState: () => RootState) => { - const features = getFeatures(getState().instance); + const client = getClient(getState); + const features = client.features; - dispatch({ type: STATUS_TRANSLATE_REQUEST, id }); + dispatch({ type: STATUS_TRANSLATE_REQUEST, statusId }); const handleTranslateMany = () => { const copy = [...TRANSLATIONS_QUEUE]; TRANSLATIONS_QUEUE = new Set(); if (TRANSLATIONS_TIMEOUT) clearTimeout(TRANSLATIONS_TIMEOUT); - getClient(getState).request('/api/v1/pl/statuses/translate', { + return client.request('/api/v1/pl/statuses/translate', { method: 'POST', body: { ids: copy, lang: targetLanguage }, }).then((response) => { response.json.forEach((translation: APIEntity) => { dispatch({ type: STATUS_TRANSLATE_SUCCESS, - id: translation.id, + statusId: translation.id, translation: translation, }); copy .filter((statusId) => !response.json.some(({ id }: APIEntity) => id === statusId)) - .forEach((id) => dispatch({ + .forEach((statusId) => dispatch({ type: STATUS_TRANSLATE_FAIL, - id, + statusId, })); }); }).catch(error => { dispatch({ type: STATUS_TRANSLATE_FAIL, - id, + statusId, error, }); }); }; if (features.lazyTranslations && lazy) { - TRANSLATIONS_QUEUE.add(id); + TRANSLATIONS_QUEUE.add(statusId); if (TRANSLATIONS_TIMEOUT) clearTimeout(TRANSLATIONS_TIMEOUT); TRANSLATIONS_TIMEOUT = setTimeout(() => handleTranslateMany(), 3000); } else if (features.lazyTranslations && TRANSLATIONS_QUEUE.size) { - TRANSLATIONS_QUEUE.add(id); + TRANSLATIONS_QUEUE.add(statusId); handleTranslateMany(); } else { - return getClient(getState()).statuses.translateStatus(id, targetLanguage).then(response => { + return client.statuses.translateStatus(statusId, targetLanguage).then(response => { dispatch({ type: STATUS_TRANSLATE_SUCCESS, - id, + statusId, translation: response, }); }).catch(error => { dispatch({ type: STATUS_TRANSLATE_FAIL, - id, + statusId, error, }); }); } }; -const undoStatusTranslation = (id: string) => ({ +const undoStatusTranslation = (statusId: string) => ({ type: STATUS_TRANSLATE_UNDO, - id, + statusId, }); -const unfilterStatus = (id: string) => ({ +const unfilterStatus = (statusId: string) => ({ type: STATUS_UNFILTER, - id, + statusId, }); -const changeStatusLanguage = (id: string, language: string) => ({ +const changeStatusLanguage = (statusId: string, language: string) => ({ type: STATUS_LANGUAGE_CHANGE, - id, + statusId, language, }); diff --git a/src/actions/streaming.ts b/src/actions/streaming.ts deleted file mode 100644 index 2713381e0..000000000 --- a/src/actions/streaming.ts +++ /dev/null @@ -1,203 +0,0 @@ -import { getLocale, getSettings } from 'soapbox/actions/settings'; -import { updateReactions } from 'soapbox/api/hooks/announcements/useAnnouncements'; -import { importEntities } from 'soapbox/entity-store/actions'; -import { Entities } from 'soapbox/entity-store/entities'; -import { selectEntity } from 'soapbox/entity-store/selectors'; -import messages from 'soapbox/messages'; -import { queryClient } from 'soapbox/queries/client'; -import { announcementSchema, type Announcement, type Relationship } from 'soapbox/schemas'; -import { getUnreadChatsCount, updateChatListItem } from 'soapbox/utils/chats'; -import { play, soundCache } from 'soapbox/utils/sounds'; - -import { connectStream } from '../stream'; - -import { updateConversations } from './conversations'; -import { fetchFilters } from './filters'; -import { MARKER_FETCH_SUCCESS } from './markers'; -import { updateNotificationsQueue } from './notifications'; -import { updateStatus } from './statuses'; -import { - // deleteFromTimelines, - connectTimeline, - disconnectTimeline, - processTimelineUpdate, -} from './timelines'; - -import type { IntlShape } from 'react-intl'; -import type { IStatContext } from 'soapbox/contexts/stat-context'; -import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; - -const updateAnnouncementReactions = ({ announcement_id: id, name, count }: APIEntity) => { - queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) => - prevResult.map(value => { - if (value.id !== id) return value; - - return announcementSchema.parse({ - ...value, - reactions: updateReactions(value.reactions, name, -1, true), - }); - }), - ); -}; - -const updateAnnouncement = (announcement: APIEntity) => - queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) => { - let updated = false; - - const result = prevResult.map(value => value.id === announcement.id - ? (updated = true, announcementSchema.parse(announcement)) - : value); - - if (!updated) return [announcementSchema.parse(announcement), ...result]; - }); - -const deleteAnnouncement = (id: string) => - queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) => - prevResult.filter(value => value.id !== id), - ); - -interface TimelineStreamOpts { - statContext?: IStatContext; - enabled?: boolean; -} - -const connectTimelineStream = ( - timelineId: string, - path: string, - pollingRefresh: ((dispatch: AppDispatch, intl?: IntlShape, done?: () => void) => void) | null = null, - accept: ((status: APIEntity) => boolean) | null = null, - opts?: TimelineStreamOpts, -) => connectStream(path, pollingRefresh, (dispatch: AppDispatch, getState: () => RootState) => { - const locale = getLocale(getState()); - - return { - onConnect() { - dispatch(connectTimeline(timelineId)); - }, - - onDisconnect() { - dispatch(disconnectTimeline(timelineId)); - }, - - onReceive(websocket, data: any) { - switch (data.event) { - case 'update': - dispatch(processTimelineUpdate(timelineId, JSON.parse(data.payload), accept)); - break; - case 'status.update': - dispatch(updateStatus(JSON.parse(data.payload))); - break; - // FIXME: We think delete & redraft is causing jumpy timelines. - // Fix that in ScrollableList then re-enable this! - // - // case 'delete': - // dispatch(deleteFromTimelines(data.payload)); - // break; - case 'notification': - messages[locale]().then(messages => { - dispatch( - updateNotificationsQueue( - JSON.parse(data.payload), - messages, - locale, - window.location.pathname, - ), - ); - }).catch(error => { - console.error(error); - }); - break; - case 'conversation': - dispatch(updateConversations(JSON.parse(data.payload))); - break; - case 'filters_changed': - dispatch(fetchFilters()); - break; - case 'pleroma:chat_update': - dispatch((_dispatch: AppDispatch, getState: () => RootState) => { - const chat = JSON.parse(data.payload); - const me = getState().me; - const messageOwned = chat.last_message?.account_id === me; - const settings = getSettings(getState()); - - // Don't update own messages from streaming - if (!messageOwned) { - updateChatListItem(chat); - - if (settings.getIn(['chats', 'sound'])) { - play(soundCache.chat); - } - - // Increment unread counter - opts?.statContext?.setUnreadChatsCount(getUnreadChatsCount()); - } - }); - break; - case 'pleroma:follow_relationships_update': - dispatch(updateFollowRelationships(JSON.parse(data.payload))); - break; - case 'announcement': - updateAnnouncement(JSON.parse(data.payload)); - break; - case 'announcement.reaction': - updateAnnouncementReactions(JSON.parse(data.payload)); - break; - case 'announcement.delete': - deleteAnnouncement(data.payload); - break; - case 'marker': - dispatch({ type: MARKER_FETCH_SUCCESS, marker: JSON.parse(data.payload) }); - break; - } - }, - }; -}); - -const followStateToRelationship = (followState: string) => { - switch (followState) { - case 'follow_pending': - return { following: false, requested: true }; - case 'follow_accept': - return { following: true, requested: false }; - case 'follow_reject': - return { following: false, requested: false }; - default: - return {}; - } -}; - -interface FollowUpdate { - state: 'follow_pending' | 'follow_accept' | 'follow_reject'; - follower: { - id: string; - follower_count: number; - following_count: number; - }; - following: { - id: string; - follower_count: number; - following_count: number; - }; -} - -const updateFollowRelationships = (update: FollowUpdate) => - (dispatch: AppDispatch, getState: () => RootState) => { - const me = getState().me; - const relationship = selectEntity(getState(), Entities.RELATIONSHIPS, update.following.id); - - if (update.follower.id === me && relationship) { - const updated = { - ...relationship, - ...followStateToRelationship(update.state), - }; - - // Add a small delay to deal with API race conditions. - setTimeout(() => dispatch(importEntities([updated], Entities.RELATIONSHIPS)), 300); - } - }; - -export { - connectTimelineStream, - type TimelineStreamOpts, -}; diff --git a/src/actions/suggestions.ts b/src/actions/suggestions.ts index c297f2909..948af6494 100644 --- a/src/actions/suggestions.ts +++ b/src/actions/suggestions.ts @@ -8,11 +8,11 @@ import { insertSuggestionsIntoTimeline } from './timelines'; import type { AppDispatch, RootState } from 'soapbox/store'; -const SUGGESTIONS_FETCH_REQUEST = 'SUGGESTIONS_FETCH_REQUEST'; -const SUGGESTIONS_FETCH_SUCCESS = 'SUGGESTIONS_FETCH_SUCCESS'; -const SUGGESTIONS_FETCH_FAIL = 'SUGGESTIONS_FETCH_FAIL'; +const SUGGESTIONS_FETCH_REQUEST = 'SUGGESTIONS_FETCH_REQUEST' as const; +const SUGGESTIONS_FETCH_SUCCESS = 'SUGGESTIONS_FETCH_SUCCESS' as const; +const SUGGESTIONS_FETCH_FAIL = 'SUGGESTIONS_FETCH_FAIL' as const; -const SUGGESTIONS_DISMISS = 'SUGGESTIONS_DISMISS'; +const SUGGESTIONS_DISMISS = 'SUGGESTIONS_DISMISS' as const; const fetchSuggestions = (limit = 50) => (dispatch: AppDispatch, getState: () => RootState) => { @@ -23,18 +23,18 @@ const fetchSuggestions = (limit = 50) => if (!me) return null; if (client.features.suggestions) { - dispatch({ type: SUGGESTIONS_FETCH_REQUEST, skipLoading: true }); + dispatch({ type: SUGGESTIONS_FETCH_REQUEST }); return getClient(getState).myAccount.getSuggestions(limit).then((suggestions) => { const accounts = suggestions.map(({ account }) => account); dispatch(importFetchedAccounts(accounts)); - dispatch({ type: SUGGESTIONS_FETCH_SUCCESS, suggestions, skipLoading: true }); + dispatch({ type: SUGGESTIONS_FETCH_SUCCESS, suggestions }); dispatch(fetchRelationships(accounts.map(({ id }) => id))); return suggestions; }).catch(error => { - dispatch({ type: SUGGESTIONS_FETCH_FAIL, error, skipLoading: true, skipAlert: true }); + dispatch({ type: SUGGESTIONS_FETCH_FAIL, error, skipAlert: true }); throw error; }); } else { @@ -43,7 +43,7 @@ const fetchSuggestions = (limit = 50) => } }; -const fetchSuggestionsForTimeline = () => (dispatch: AppDispatch, _getState: () => RootState) => { +const fetchSuggestionsForTimeline = () => (dispatch: AppDispatch) => { dispatch(fetchSuggestions(20))?.then(() => dispatch(insertSuggestionsIntoTimeline())); }; @@ -53,7 +53,7 @@ const dismissSuggestion = (accountId: string) => dispatch({ type: SUGGESTIONS_DISMISS, - id: accountId, + accountId, }); return getClient(getState).myAccount.dismissSuggestions(accountId); diff --git a/src/actions/sw.ts b/src/actions/sw.ts index c12dc83e8..346864bda 100644 --- a/src/actions/sw.ts +++ b/src/actions/sw.ts @@ -1,15 +1,16 @@ -import type { AnyAction } from 'redux'; - /** Sets the ServiceWorker updating state. */ -const SW_UPDATING = 'SW_UPDATING'; +const SW_UPDATING = 'SW_UPDATING' as const; /** Dispatch when the ServiceWorker is being updated to display a loading screen. */ -const setSwUpdating = (isUpdating: boolean): AnyAction => ({ +const setSwUpdating = (isUpdating: boolean) => ({ type: SW_UPDATING, isUpdating, }); +type SwAction = ReturnType; + export { SW_UPDATING, setSwUpdating, + type SwAction, }; diff --git a/src/actions/tags.ts b/src/actions/tags.ts index 69e545c71..4b787cb04 100644 --- a/src/actions/tags.ts +++ b/src/actions/tags.ts @@ -2,27 +2,26 @@ import { getClient } from '../api'; import type { PaginatedResponse, Tag } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity } from 'soapbox/types/entities'; -const HASHTAG_FETCH_REQUEST = 'HASHTAG_FETCH_REQUEST'; -const HASHTAG_FETCH_SUCCESS = 'HASHTAG_FETCH_SUCCESS'; -const HASHTAG_FETCH_FAIL = 'HASHTAG_FETCH_FAIL'; +const HASHTAG_FETCH_REQUEST = 'HASHTAG_FETCH_REQUEST' as const; +const HASHTAG_FETCH_SUCCESS = 'HASHTAG_FETCH_SUCCESS' as const; +const HASHTAG_FETCH_FAIL = 'HASHTAG_FETCH_FAIL' as const; -const HASHTAG_FOLLOW_REQUEST = 'HASHTAG_FOLLOW_REQUEST'; -const HASHTAG_FOLLOW_SUCCESS = 'HASHTAG_FOLLOW_SUCCESS'; -const HASHTAG_FOLLOW_FAIL = 'HASHTAG_FOLLOW_FAIL'; +const HASHTAG_FOLLOW_REQUEST = 'HASHTAG_FOLLOW_REQUEST' as const; +const HASHTAG_FOLLOW_SUCCESS = 'HASHTAG_FOLLOW_SUCCESS' as const; +const HASHTAG_FOLLOW_FAIL = 'HASHTAG_FOLLOW_FAIL' as const; -const HASHTAG_UNFOLLOW_REQUEST = 'HASHTAG_UNFOLLOW_REQUEST'; -const HASHTAG_UNFOLLOW_SUCCESS = 'HASHTAG_UNFOLLOW_SUCCESS'; -const HASHTAG_UNFOLLOW_FAIL = 'HASHTAG_UNFOLLOW_FAIL'; +const HASHTAG_UNFOLLOW_REQUEST = 'HASHTAG_UNFOLLOW_REQUEST' as const; +const HASHTAG_UNFOLLOW_SUCCESS = 'HASHTAG_UNFOLLOW_SUCCESS' as const; +const HASHTAG_UNFOLLOW_FAIL = 'HASHTAG_UNFOLLOW_FAIL' as const; -const FOLLOWED_HASHTAGS_FETCH_REQUEST = 'FOLLOWED_HASHTAGS_FETCH_REQUEST'; -const FOLLOWED_HASHTAGS_FETCH_SUCCESS = 'FOLLOWED_HASHTAGS_FETCH_SUCCESS'; -const FOLLOWED_HASHTAGS_FETCH_FAIL = 'FOLLOWED_HASHTAGS_FETCH_FAIL'; +const FOLLOWED_HASHTAGS_FETCH_REQUEST = 'FOLLOWED_HASHTAGS_FETCH_REQUEST' as const; +const FOLLOWED_HASHTAGS_FETCH_SUCCESS = 'FOLLOWED_HASHTAGS_FETCH_SUCCESS' as const; +const FOLLOWED_HASHTAGS_FETCH_FAIL = 'FOLLOWED_HASHTAGS_FETCH_FAIL' as const; -const FOLLOWED_HASHTAGS_EXPAND_REQUEST = 'FOLLOWED_HASHTAGS_EXPAND_REQUEST'; -const FOLLOWED_HASHTAGS_EXPAND_SUCCESS = 'FOLLOWED_HASHTAGS_EXPAND_SUCCESS'; -const FOLLOWED_HASHTAGS_EXPAND_FAIL = 'FOLLOWED_HASHTAGS_EXPAND_FAIL'; +const FOLLOWED_HASHTAGS_EXPAND_REQUEST = 'FOLLOWED_HASHTAGS_EXPAND_REQUEST' as const; +const FOLLOWED_HASHTAGS_EXPAND_SUCCESS = 'FOLLOWED_HASHTAGS_EXPAND_SUCCESS' as const; +const FOLLOWED_HASHTAGS_EXPAND_FAIL = 'FOLLOWED_HASHTAGS_EXPAND_FAIL' as const; const fetchHashtag = (name: string) => (dispatch: AppDispatch, getState: () => RootState) => { dispatch(fetchHashtagRequest()); @@ -38,7 +37,7 @@ const fetchHashtagRequest = () => ({ type: HASHTAG_FETCH_REQUEST, }); -const fetchHashtagSuccess = (name: string, tag: APIEntity) => ({ +const fetchHashtagSuccess = (name: string, tag: Tag) => ({ type: HASHTAG_FETCH_SUCCESS, name, tag, @@ -64,7 +63,7 @@ const followHashtagRequest = (name: string) => ({ name, }); -const followHashtagSuccess = (name: string, tag: APIEntity) => ({ +const followHashtagSuccess = (name: string, tag: Tag) => ({ type: HASHTAG_FOLLOW_SUCCESS, name, tag, @@ -91,7 +90,7 @@ const unfollowHashtagRequest = (name: string) => ({ name, }); -const unfollowHashtagSuccess = (name: string, tag: APIEntity) => ({ +const unfollowHashtagSuccess = (name: string, tag: Tag) => ({ type: HASHTAG_UNFOLLOW_SUCCESS, name, tag, @@ -117,7 +116,7 @@ const fetchFollowedHashtagsRequest = () => ({ type: FOLLOWED_HASHTAGS_FETCH_REQUEST, }); -const fetchFollowedHashtagsSuccess = (followed_tags: APIEntity[], next: (() => Promise>) | null) => ({ +const fetchFollowedHashtagsSuccess = (followed_tags: Array, next: (() => Promise>) | null) => ({ type: FOLLOWED_HASHTAGS_FETCH_SUCCESS, followed_tags, next, @@ -146,7 +145,7 @@ const expandFollowedHashtagsRequest = () => ({ type: FOLLOWED_HASHTAGS_EXPAND_REQUEST, }); -const expandFollowedHashtagsSuccess = (followed_tags: APIEntity[], next: (() => Promise>) | null) => ({ +const expandFollowedHashtagsSuccess = (followed_tags: Array, next: (() => Promise>) | null) => ({ type: FOLLOWED_HASHTAGS_EXPAND_SUCCESS, followed_tags, next, diff --git a/src/actions/timelines.ts b/src/actions/timelines.ts index 286d8fc04..272f852ae 100644 --- a/src/actions/timelines.ts +++ b/src/actions/timelines.ts @@ -1,16 +1,14 @@ -import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable'; +import { Map as ImmutableMap } from 'immutable'; -import { getSettings } from 'soapbox/actions/settings'; -import { normalizeStatus } from 'soapbox/normalizers'; +import { getLocale, getSettings } from 'soapbox/actions/settings'; import { shouldFilter } from 'soapbox/utils/timelines'; -import { getClient, getNextLink, getPrevLink } from '../api'; +import { getClient } from '../api'; import { importFetchedStatus, importFetchedStatuses } from './importer'; -import type { IntlShape } from 'react-intl'; +import type { PaginatedResponse, Status as BaseStatus, PublicTimelineParams, HomeTimelineParams, ListTimelineParams, HashtagTimelineParams, GetAccountStatusesParams, GroupTimelineParams } from 'pl-api'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { APIEntity, Status } from 'soapbox/types/entities'; const TIMELINE_UPDATE = 'TIMELINE_UPDATE' as const; const TIMELINE_DELETE = 'TIMELINE_DELETE' as const; @@ -23,21 +21,18 @@ const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST' as const; const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS' as const; const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL' as const; -const TIMELINE_CONNECT = 'TIMELINE_CONNECT' as const; -const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT' as const; - const TIMELINE_INSERT = 'TIMELINE_INSERT' as const; const MAX_QUEUED_ITEMS = 40; -const processTimelineUpdate = (timeline: string, status: APIEntity, accept: ((status: APIEntity) => boolean) | null) => +const processTimelineUpdate = (timeline: string, status: BaseStatus, accept: ((status: BaseStatus) => boolean) | null = null) => (dispatch: AppDispatch, getState: () => RootState) => { const me = getState().me; const ownStatus = status.account?.id === me; const hasPendingStatuses = !getState().pending_statuses.isEmpty(); const columnSettings = getSettings(getState()).get(timeline, ImmutableMap()); - const shouldSkipQueue = shouldFilter(normalizeStatus(status) as Status, columnSettings as any); + const shouldSkipQueue = shouldFilter(status, columnSettings as any); if (ownStatus && hasPendingStatuses) { // WebSockets push statuses without the Idempotency-Key, @@ -55,7 +50,7 @@ const processTimelineUpdate = (timeline: string, status: APIEntity, accept: ((st } }; -const updateTimeline = (timeline: string, statusId: string, accept: ((status: APIEntity) => boolean) | null) => +const updateTimeline = (timeline: string, statusId: string, accept: ((status: BaseStatus) => boolean) | null) => (dispatch: AppDispatch) => { // if (typeof accept === 'function' && !accept(status)) { // return; @@ -68,7 +63,7 @@ const updateTimeline = (timeline: string, statusId: string, accept: ((status: AP }); }; -const updateTimelineQueue = (timeline: string, statusId: string, accept: ((status: APIEntity) => boolean) | null) => +const updateTimelineQueue = (timeline: string, statusId: string, accept: ((status: BaseStatus) => boolean) | null) => (dispatch: AppDispatch) => { // if (typeof accept === 'function' && !accept(status)) { // return; @@ -81,7 +76,7 @@ const updateTimelineQueue = (timeline: string, statusId: string, accept: ((statu }); }; -const dequeueTimeline = (timelineId: string, expandFunc?: (lastStatusId: string) => void, optionalExpandArgs?: any) => +const dequeueTimeline = (timelineId: string, expandFunc?: (lastStatusId: string) => void) => (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); const queuedCount = state.timelines.get(timelineId)?.totalQueuedItemsCount || 0; @@ -100,31 +95,31 @@ const dequeueTimeline = (timelineId: string, expandFunc?: (lastStatusId: string) } else { if (timelineId === 'home') { dispatch(clearTimeline(timelineId)); - dispatch(expandHomeTimeline(optionalExpandArgs)); - } else if (timelineId === 'community') { + dispatch(fetchHomeTimeline()); + } else if (timelineId === 'public:local') { dispatch(clearTimeline(timelineId)); - dispatch(expandCommunityTimeline(optionalExpandArgs)); + dispatch(fetchPublicTimeline({ local: true })); } } }; interface TimelineDeleteAction { type: typeof TIMELINE_DELETE; - id: string; + statusId: string; accountId: string; references: ImmutableMap; reblogOf: unknown; } -const deleteFromTimelines = (id: string) => +const deleteFromTimelines = (statusId: string) => (dispatch: AppDispatch, getState: () => RootState) => { - const accountId = getState().statuses.get(id)?.account?.id!; - const references = getState().statuses.filter(status => status.reblog === id).map(status => [status.id, status.account.id] as const); - const reblogOf = getState().statuses.getIn([id, 'reblog'], null); + const accountId = getState().statuses.get(statusId)?.account?.id!; + const references = getState().statuses.filter(status => status.reblog === statusId).map(status => [status.id, status.account.id] as const); + const reblogOf = getState().statuses.getIn([statusId, 'reblog'], null); const action: TimelineDeleteAction = { type: TIMELINE_DELETE, - id, + statusId, accountId, references, reblogOf, @@ -138,7 +133,6 @@ const clearTimeline = (timeline: string) => dispatch({ type: TIMELINE_CLEAR, timeline }); const noOp = () => { }; -const noOpAsync = () => () => new Promise(f => f(undefined)); const parseTags = (tags: Record = {}, mode: 'any' | 'all' | 'none') => (tags[mode] || []).map((tag) => tag.value); @@ -164,130 +158,137 @@ const deduplicateStatuses = (statuses: any[]) => { return deduplicatedStatuses; }; -const expandTimeline = (timelineId: string, path: string, params: Record = {}, intl?: IntlShape, done = noOp) => - (dispatch: AppDispatch, getState: () => RootState) => { - const timeline = getState().timelines.get(timelineId) || {} as Record; - const isLoadingMore = !!params.max_id; +const handleTimelineExpand = (timelineId: string, fn: Promise>, isLoadingRecent: boolean, done = noOp) => + (dispatch: AppDispatch) => + fn.then(response => { + dispatch(importFetchedStatuses(response.items)); - if (timeline.isLoading) { - done(); - return dispatch(noOpAsync()); - } - - if ( - !params.max_id && - !params.pinned && - (timeline.items || ImmutableOrderedSet()).size > 0 && - !path.includes('max_id=') - ) { - params.since_id = timeline.getIn(['items', 0]); - } - - if (intl && getSettings(getState()).get('autoTranslate')) params.language = intl.locale; - - const isLoadingRecent = !!params.since_id; - - dispatch(expandTimelineRequest(timelineId, isLoadingMore)); - - return getClient(getState).request(path, { params }).then(response => { - dispatch(importFetchedStatuses(response.json)); - - const statuses = deduplicateStatuses(response.json); + const statuses = deduplicateStatuses(response.items); dispatch(importFetchedStatuses(statuses.filter(status => status.accounts))); dispatch(expandTimelineSuccess( timelineId, statuses, - getNextLink(response), - getPrevLink(response), - response.status === 206, + response.next, + response.previous, + response.partial, isLoadingRecent, - isLoadingMore, )); done(); }).catch(error => { - dispatch(expandTimelineFail(timelineId, error, isLoadingMore)); + dispatch(expandTimelineFail(timelineId, error)); done(); }); + +const fetchHomeTimeline = (expand = false, done = noOp) => + (dispatch: AppDispatch, getState: () => RootState) => { + const state = getState(); + + const params: HomeTimelineParams = {}; + if (getSettings(state).get('autoTranslate')) params.language = getLocale(state); + + const fn = (expand && state.timelines.get('home')?.next?.()) || getClient(state).timelines.homeTimeline(params); + + return dispatch(handleTimelineExpand('home', fn, false, done)); }; -interface ExpandHomeTimelineOpts { - maxId?: string; - url?: string; -} +const fetchPublicTimeline = ({ onlyMedia, local, instance }: Record = {}, expand = false, done = noOp) => + (dispatch: AppDispatch, getState: () => RootState) => { + const state = getState(); + const timelineId = `${instance ? 'remote' : 'public'}${local ? ':local' : ''}${onlyMedia ? ':media' : ''}${instance ? `:${instance}` : ''}`; -interface HomeTimelineParams { - max_id?: string; - exclude_replies?: boolean; - with_muted?: boolean; -} + const params: PublicTimelineParams = { only_media: onlyMedia, local: instance ? false : local, instance }; + if (getSettings(state).get('autoTranslate')) params.language = getLocale(state); -const expandHomeTimeline = ({ url, maxId }: ExpandHomeTimelineOpts = {}, intl?: IntlShape, done = noOp) => { - const endpoint = url || '/api/v1/timelines/home'; - const params: HomeTimelineParams = {}; + const fn = (expand && state.timelines.get(timelineId)?.next?.()) || getClient(state).timelines.publicTimeline(params); - if (!url && maxId) { - params.max_id = maxId; - } + return dispatch(handleTimelineExpand(timelineId, fn, false, done)); + }; - return expandTimeline('home', endpoint, params, intl, done); -}; +const fetchBubbleTimeline = ({ onlyMedia }: Record = {}, expand = false, done = noOp) => + (dispatch: AppDispatch, getState: () => RootState) => { + const state = getState(); + const timelineId = `bubble${onlyMedia ? ':media' : ''}`; -const expandPublicTimeline = ({ url, maxId, onlyMedia }: Record = {}, intl?: IntlShape, done = noOp) => - expandTimeline(`public${onlyMedia ? ':media' : ''}`, url || '/api/v1/timelines/public', url ? {} : { max_id: maxId, only_media: !!onlyMedia }, intl, done); + const params: PublicTimelineParams = { only_media: onlyMedia }; + if (getSettings(state).get('autoTranslate')) params.language = getLocale(state); -const expandRemoteTimeline = (instance: string, { url, maxId, onlyMedia }: Record = {}, intl?: IntlShape, done = noOp) => - expandTimeline(`remote${onlyMedia ? ':media' : ''}:${instance}`, url || '/api/v1/timelines/public', url ? {} : { local: false, instance: instance, max_id: maxId, only_media: !!onlyMedia }, intl, done); + const fn = (expand && state.timelines.get(timelineId)?.next?.()) || getClient(state).timelines.bubbleTimeline(params); -const expandCommunityTimeline = ({ url, maxId, onlyMedia }: Record = {}, intl?: IntlShape, done = noOp) => - expandTimeline(`community${onlyMedia ? ':media' : ''}`, url || '/api/v1/timelines/public', url ? {} : { local: true, max_id: maxId, only_media: !!onlyMedia }, intl, done); + return dispatch(handleTimelineExpand(timelineId, fn, false, done)); + }; -const expandBubbleTimeline = ({ url, maxId, onlyMedia }: Record = {}, intl?: IntlShape, done = noOp) => - expandTimeline(`bubble${onlyMedia ? ':media' : ''}`, url || '/api/v1/timelines/bubble', url ? {} : { max_id: maxId, only_media: !!onlyMedia }, intl, done); +const fetchAccountTimeline = (accountId: string, { exclude_replies, pinned, only_media, limit }: Record = {}, expand = false, done = noOp) => + (dispatch: AppDispatch, getState: () => RootState) => { + const state = getState(); + const timelineId = `account:${accountId}${!exclude_replies ? ':with_replies' : ''}${pinned ? ':pinned' : only_media ? ':media' : ''}`; -const expandAccountTimeline = (accountId: string, { url, maxId, withReplies }: Record = {}, intl?: IntlShape) => - expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}`, url || `/api/v1/accounts/${accountId}/statuses`, url ? {} : { exclude_replies: !withReplies, max_id: maxId, with_muted: true }, intl); + const params: GetAccountStatusesParams = { exclude_replies, pinned, only_media, limit }; + if (pinned || only_media) params.with_muted = true; + if (getSettings(state).get('autoTranslate')) params.language = getLocale(state); -const expandAccountFeaturedTimeline = (accountId: string, intl?: IntlShape) => - expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true, with_muted: true }, intl); + const fn = (expand && state.timelines.get(timelineId)?.next?.()) || getClient(state).accounts.getAccountStatuses(accountId, params); -const expandAccountMediaTimeline = (accountId: string | number, { url, maxId }: Record = {}, intl?: IntlShape) => - expandTimeline(`account:${accountId}:media`, url || `/api/v1/accounts/${accountId}/statuses`, url ? {} : { max_id: maxId, only_media: true, limit: 40, with_muted: true }, intl); + return dispatch(handleTimelineExpand(timelineId, fn, false, done)); + }; -const expandListTimeline = (id: string, { url, maxId }: Record = {}, intl?: IntlShape, done = noOp) => - expandTimeline(`list:${id}`, url || `/api/v1/timelines/list/${id}`, url ? {} : { max_id: maxId }, intl, done); +const fetchListTimeline = (listId: string, expand = false, done = noOp) => + (dispatch: AppDispatch, getState: () => RootState) => { + const state = getState(); + const timelineId = `list:${listId}`; -const expandGroupTimeline = (id: string, { maxId }: Record = {}, intl?: IntlShape, done = noOp) => - expandTimeline(`group:${id}`, `/api/v1/timelines/group/${id}`, { max_id: maxId }, intl, done); + const params: ListTimelineParams = {}; + if (getSettings(state).get('autoTranslate')) params.language = getLocale(state); -const expandGroupFeaturedTimeline = (id: string, intl?: IntlShape) => - expandTimeline(`group:${id}:pinned`, `/api/v1/timelines/group/${id}`, { pinned: true }, intl); + const fn = (expand && state.timelines.get(timelineId)?.next?.()) || getClient(state).timelines.listTimeline(listId, params); -const expandGroupMediaTimeline = (id: string | number, { maxId }: Record = {}, intl?: IntlShape) => - expandTimeline(`group:${id}:media`, `/api/v1/timelines/group/${id}`, { max_id: maxId, only_media: true, limit: 40, with_muted: true }, intl); + return dispatch(handleTimelineExpand(timelineId, fn, false, done)); + }; -const expandHashtagTimeline = (hashtag: string, { url, maxId, tags }: Record = {}, intl?: IntlShape, done = noOp) => - expandTimeline(`hashtag:${hashtag}`, url || `/api/v1/timelines/tag/${hashtag}`, url ? {} : { - max_id: maxId, - any: parseTags(tags, 'any'), - all: parseTags(tags, 'all'), - none: parseTags(tags, 'none'), - }, intl, done); +const fetchGroupTimeline = (groupId: string, { only_media, limit }: Record = {}, expand = false, done = noOp) => + (dispatch: AppDispatch, getState: () => RootState) => { + const state = getState(); + const timelineId = `group:${groupId}${only_media ? ':media' : ''}`; -const expandTimelineRequest = (timeline: string, isLoadingMore: boolean) => ({ + const params: GroupTimelineParams = { only_media, limit }; + if (only_media) params.with_muted = true; + if (getSettings(state).get('autoTranslate')) params.language = getLocale(state); + + const fn = (expand && state.timelines.get(timelineId)?.next?.()) || getClient(state).timelines.groupTimeline(groupId, params); + + return dispatch(handleTimelineExpand(timelineId, fn, false, done)); + }; + +const fetchHashtagTimeline = (hashtag: string, { tags }: Record = {}, expand = false, done = noOp) => + (dispatch: AppDispatch, getState: () => RootState) => { + const state = getState(); + const timelineId = `hashtag:${hashtag}`; + + const params: HashtagTimelineParams = { + any: parseTags(tags, 'any'), + all: parseTags(tags, 'all'), + none: parseTags(tags, 'none'), + }; + + if (getSettings(state).get('autoTranslate')) params.language = getLocale(state); + + const fn = (expand && state.timelines.get(timelineId)?.next?.()) || getClient(state).timelines.hashtagTimeline(hashtag, params); + + return dispatch(handleTimelineExpand(timelineId, fn, false, done)); + }; + +const expandTimelineRequest = (timeline: string) => ({ type: TIMELINE_EXPAND_REQUEST, timeline, - skipLoading: !isLoadingMore, }); const expandTimelineSuccess = ( timeline: string, - statuses: APIEntity[], - next: string | undefined, - prev: string | undefined, + statuses: Array, + next: (() => Promise>) | null, + prev: (() => Promise>) | null, partial: boolean, isLoadingRecent: boolean, - isLoadingMore: boolean, ) => ({ type: TIMELINE_EXPAND_SUCCESS, timeline, @@ -296,24 +297,12 @@ const expandTimelineSuccess = ( prev, partial, isLoadingRecent, - skipLoading: !isLoadingMore, }); -const expandTimelineFail = (timeline: string, error: unknown, isLoadingMore: boolean) => ({ +const expandTimelineFail = (timeline: string, error: unknown) => ({ type: TIMELINE_EXPAND_FAIL, timeline, error, - skipLoading: !isLoadingMore, -}); - -const connectTimeline = (timeline: string) => ({ - type: TIMELINE_CONNECT, - timeline, -}); - -const disconnectTimeline = (timeline: string) => ({ - type: TIMELINE_DISCONNECT, - timeline, }); const scrollTopTimeline = (timeline: string, top: boolean) => ({ @@ -339,8 +328,6 @@ export { TIMELINE_EXPAND_REQUEST, TIMELINE_EXPAND_SUCCESS, TIMELINE_EXPAND_FAIL, - TIMELINE_CONNECT, - TIMELINE_DISCONNECT, TIMELINE_INSERT, MAX_QUEUED_ITEMS, processTimelineUpdate, @@ -349,25 +336,16 @@ export { dequeueTimeline, deleteFromTimelines, clearTimeline, - expandTimeline, - expandHomeTimeline, - expandPublicTimeline, - expandRemoteTimeline, - expandCommunityTimeline, - expandBubbleTimeline, - expandAccountTimeline, - expandAccountFeaturedTimeline, - expandAccountMediaTimeline, - expandListTimeline, - expandGroupTimeline, - expandGroupFeaturedTimeline, - expandGroupMediaTimeline, - expandHashtagTimeline, + fetchHomeTimeline, + fetchPublicTimeline, + fetchBubbleTimeline, + fetchAccountTimeline, + fetchListTimeline, + fetchGroupTimeline, + fetchHashtagTimeline, expandTimelineRequest, expandTimelineSuccess, expandTimelineFail, - connectTimeline, - disconnectTimeline, scrollTopTimeline, insertSuggestionsIntoTimeline, type TimelineAction, diff --git a/src/actions/trending-statuses.ts b/src/actions/trending-statuses.ts index 1e33e6575..fd1ac28f6 100644 --- a/src/actions/trending-statuses.ts +++ b/src/actions/trending-statuses.ts @@ -1,27 +1,23 @@ -import { getFeatures } from 'soapbox/utils/features'; - import { getClient } from '../api'; import { importFetchedStatuses } from './importer'; import type { AppDispatch, RootState } from 'soapbox/store'; -const TRENDING_STATUSES_FETCH_REQUEST = 'TRENDING_STATUSES_FETCH_REQUEST'; -const TRENDING_STATUSES_FETCH_SUCCESS = 'TRENDING_STATUSES_FETCH_SUCCESS'; -const TRENDING_STATUSES_FETCH_FAIL = 'TRENDING_STATUSES_FETCH_FAIL'; +const TRENDING_STATUSES_FETCH_REQUEST = 'TRENDING_STATUSES_FETCH_REQUEST' as const; +const TRENDING_STATUSES_FETCH_SUCCESS = 'TRENDING_STATUSES_FETCH_SUCCESS' as const; +const TRENDING_STATUSES_FETCH_FAIL = 'TRENDING_STATUSES_FETCH_FAIL' as const; const fetchTrendingStatuses = () => (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); + const client = getClient(state); - const instance = state.instance; - const features = getFeatures(instance); - - if (!features.trendingStatuses) return; + if (!client.features.trendingStatuses) return; dispatch({ type: TRENDING_STATUSES_FETCH_REQUEST }); - return getClient(getState()).trends.getTrendingStatuses().then((statuses) => { + return client.trends.getTrendingStatuses().then((statuses) => { dispatch(importFetchedStatuses(statuses)); dispatch({ type: TRENDING_STATUSES_FETCH_SUCCESS, statuses }); return statuses; diff --git a/src/actions/trends.ts b/src/actions/trends.ts index f0482a6bf..703b0cd43 100644 --- a/src/actions/trends.ts +++ b/src/actions/trends.ts @@ -1,11 +1,10 @@ -import type { APIEntity } from 'soapbox/types/entities'; +import type { Tag } from 'pl-api'; const TRENDS_FETCH_SUCCESS = 'TRENDS_FETCH_SUCCESS'; -const fetchTrendsSuccess = (tags: APIEntity[]) => ({ +const fetchTrendsSuccess = (tags: Array) => ({ type: TRENDS_FETCH_SUCCESS, tags, - skipLoading: true, }); export { diff --git a/src/api/hooks/accounts/useAccount.ts b/src/api/hooks/accounts/useAccount.ts index b9fbc3865..8195c15de 100644 --- a/src/api/hooks/accounts/useAccount.ts +++ b/src/api/hooks/accounts/useAccount.ts @@ -1,10 +1,11 @@ +import { type Account as BaseAccount, accountSchema } from 'pl-api'; import { useEffect, useMemo } from 'react'; import { useHistory } from 'react-router-dom'; import { Entities } from 'soapbox/entity-store/entities'; import { useEntity } from 'soapbox/entity-store/hooks'; -import { useClient, useFeatures, useLoggedIn } from 'soapbox/hooks'; -import { type Account, accountSchema } from 'soapbox/schemas'; +import { useAppSelector, useClient, useFeatures, useLoggedIn } from 'soapbox/hooks'; +import { type Account, normalizeAccount } from 'soapbox/normalizers'; import { useRelationship } from './useRelationship'; @@ -19,12 +20,14 @@ const useAccount = (accountId?: string, opts: UseAccountOpts = {}) => { const { me } = useLoggedIn(); const { withRelationship } = opts; - const { entity, isUnauthorized, ...result } = useEntity( + const { entity, isUnauthorized, ...result } = useEntity( [Entities.ACCOUNTS, accountId!], () => client.accounts.getAccount(accountId!), - { schema: accountSchema, enabled: !!accountId }, + { schema: accountSchema, enabled: !!accountId, transform: normalizeAccount }, ); + const meta = useAppSelector((state) => accountId && state.accounts_meta[accountId] || {}); + const { relationship, isLoading: isRelationshipLoading, @@ -34,7 +37,7 @@ const useAccount = (accountId?: string, opts: UseAccountOpts = {}) => { const isUnavailable = (me === entity?.id) ? false : (isBlocked && !features.blockersVisible); const account = useMemo( - () => entity ? { ...entity, relationship } : undefined, + () => entity ? { ...entity, relationship, __meta: { meta, ...entity.__meta } } : undefined, [entity, relationship], ); diff --git a/src/api/hooks/accounts/useAccountList.ts b/src/api/hooks/accounts/useAccountList.ts index 4fd4e35b9..da0eab19b 100644 --- a/src/api/hooks/accounts/useAccountList.ts +++ b/src/api/hooks/accounts/useAccountList.ts @@ -1,7 +1,9 @@ +import { accountSchema, type Account as BaseAccount } from 'pl-api'; + import { Entities } from 'soapbox/entity-store/entities'; import { useEntities } from 'soapbox/entity-store/hooks'; import { useClient } from 'soapbox/hooks'; -import { Account, accountSchema } from 'soapbox/schemas'; +import { normalizeAccount, type Account } from 'soapbox/normalizers'; import { useRelationships } from './useRelationships'; @@ -12,10 +14,10 @@ interface useAccountListOpts { } const useAccountList = (listKey: string[], entityFn: EntityFn, opts: useAccountListOpts = {}) => { - const { entities, ...rest } = useEntities( + const { entities, ...rest } = useEntities( [Entities.ACCOUNTS, ...listKey], entityFn, - { schema: accountSchema, enabled: opts.enabled }, + { schema: accountSchema, enabled: opts.enabled, transform: normalizeAccount }, ); const { relationships } = useRelationships( diff --git a/src/api/hooks/accounts/useAccountLookup.ts b/src/api/hooks/accounts/useAccountLookup.ts index b9adda5c3..1b372fc71 100644 --- a/src/api/hooks/accounts/useAccountLookup.ts +++ b/src/api/hooks/accounts/useAccountLookup.ts @@ -1,10 +1,11 @@ +import { accountSchema, type Account as BaseAccount } from 'pl-api'; import { useEffect } from 'react'; import { useHistory } from 'react-router-dom'; import { Entities } from 'soapbox/entity-store/entities'; import { useEntityLookup } from 'soapbox/entity-store/hooks'; import { useClient, useFeatures, useLoggedIn } from 'soapbox/hooks'; -import { type Account, accountSchema } from 'soapbox/schemas'; +import { type Account, normalizeAccount } from 'soapbox/normalizers'; import { useRelationship } from './useRelationship'; @@ -19,11 +20,11 @@ const useAccountLookup = (acct: string | undefined, opts: UseAccountLookupOpts = const { me } = useLoggedIn(); const { withRelationship } = opts; - const { entity: account, isUnauthorized, ...result } = useEntityLookup( + const { entity: account, isUnauthorized, ...result } = useEntityLookup( Entities.ACCOUNTS, (account) => account.acct.toLowerCase() === acct?.toLowerCase(), () => client.accounts.lookupAccount(acct!), - { schema: accountSchema, enabled: !!acct }, + { schema: accountSchema, enabled: !!acct, transform: normalizeAccount }, ); const { diff --git a/src/api/hooks/accounts/useRelationship.ts b/src/api/hooks/accounts/useRelationship.ts index c470f2f28..a909745b5 100644 --- a/src/api/hooks/accounts/useRelationship.ts +++ b/src/api/hooks/accounts/useRelationship.ts @@ -1,9 +1,9 @@ +import { type Relationship, relationshipSchema } from 'pl-api'; import { z } from 'zod'; import { Entities } from 'soapbox/entity-store/entities'; import { useEntity } from 'soapbox/entity-store/hooks'; import { useClient } from 'soapbox/hooks'; -import { type Relationship, relationshipSchema } from 'soapbox/schemas'; interface UseRelationshipOpts { enabled?: boolean; diff --git a/src/api/hooks/accounts/useRelationships.ts b/src/api/hooks/accounts/useRelationships.ts index 7777b32a0..69f630e0f 100644 --- a/src/api/hooks/accounts/useRelationships.ts +++ b/src/api/hooks/accounts/useRelationships.ts @@ -1,17 +1,18 @@ +import { type Relationship, relationshipSchema } from 'pl-api'; + import { Entities } from 'soapbox/entity-store/entities'; import { useBatchedEntities } from 'soapbox/entity-store/hooks/useBatchedEntities'; import { useClient, useLoggedIn } from 'soapbox/hooks'; -import { type Relationship, relationshipSchema } from 'soapbox/schemas'; -const useRelationships = (listKey: string[], ids: string[]) => { +const useRelationships = (listKey: string[], accountIds: string[]) => { const client = useClient(); const { isLoggedIn } = useLoggedIn(); - const fetchRelationships = (ids: string[]) => client.accounts.getRelationships(ids); + const fetchRelationships = (accountIds: string[]) => client.accounts.getRelationships(accountIds); const { entityMap: relationships, ...result } = useBatchedEntities( [Entities.RELATIONSHIPS, ...listKey], - ids, + accountIds, fetchRelationships, { schema: relationshipSchema, enabled: isLoggedIn }, ); diff --git a/src/api/hooks/admin/useSuggest.ts b/src/api/hooks/admin/useSuggest.ts index a34345ee1..fe4e56e17 100644 --- a/src/api/hooks/admin/useSuggest.ts +++ b/src/api/hooks/admin/useSuggest.ts @@ -3,7 +3,7 @@ import { EntityCallbacks } from 'soapbox/entity-store/hooks/types'; import { useClient, useGetState } from 'soapbox/hooks'; import { accountIdsToAccts } from 'soapbox/selectors'; -import type { Account } from 'soapbox/schemas'; +import type { Account } from 'soapbox/normalizers'; const useSuggest = () => { const client = useClient(); @@ -12,9 +12,7 @@ const useSuggest = () => { const suggestEffect = (accountIds: string[], suggested: boolean) => { const updater = (account: Account): Account => { - if (account.pleroma) { - account.pleroma.is_suggested = suggested; - } + account.is_suggested = suggested; return account; }; diff --git a/src/api/hooks/admin/useVerify.ts b/src/api/hooks/admin/useVerify.ts index 1641a7525..56f4a3bdb 100644 --- a/src/api/hooks/admin/useVerify.ts +++ b/src/api/hooks/admin/useVerify.ts @@ -3,7 +3,7 @@ import { EntityCallbacks } from 'soapbox/entity-store/hooks/types'; import { useClient, useGetState } from 'soapbox/hooks'; import { accountIdsToAccts } from 'soapbox/selectors'; -import type { Account } from 'soapbox/schemas'; +import type { Account } from 'soapbox/normalizers'; const useVerify = () => { const client = useClient(); @@ -12,12 +12,12 @@ const useVerify = () => { const verifyEffect = (accountIds: string[], verified: boolean) => { const updater = (account: Account): Account => { - if (account.pleroma) { - const tags = account.pleroma.tags.filter((tag) => tag !== 'verified'); + if (account.__meta.pleroma) { + const tags = account.__meta.pleroma.tags.filter((tag: string) => tag !== 'verified'); if (verified) { tags.push('verified'); } - account.pleroma.tags = tags; + account.__meta.pleroma.tags = tags; } account.verified = verified; return account; diff --git a/src/api/hooks/announcements/useAnnouncements.ts b/src/api/hooks/announcements/useAnnouncements.ts index 06cf52d90..cd6c2091d 100644 --- a/src/api/hooks/announcements/useAnnouncements.ts +++ b/src/api/hooks/announcements/useAnnouncements.ts @@ -1,25 +1,9 @@ import { useMutation, useQuery } from '@tanstack/react-query'; -import { announcementReactionSchema, type Announcement as BaseAnnouncement, type AnnouncementReaction } from 'pl-api'; +import { announcementReactionSchema, type AnnouncementReaction } from 'pl-api'; -import emojify from 'soapbox/features/emoji'; import { useClient } from 'soapbox/hooks'; +import { type Announcement, normalizeAnnouncement } from 'soapbox/normalizers'; import { queryClient } from 'soapbox/queries/client'; -import { makeCustomEmojiMap } from 'soapbox/schemas/utils'; - -interface Announcement extends BaseAnnouncement { - contentHtml: string; -} - -const transformAnnouncement = (announcement: BaseAnnouncement) => { - const emojiMap = makeCustomEmojiMap(announcement.emojis); - - const contentHtml = emojify(announcement.content, emojiMap); - - return { - ...announcement, - contentHtml, - }; -}; const updateReaction = (reaction: AnnouncementReaction, count: number, me?: boolean, overwrite?: boolean) => announcementReactionSchema.parse({ ...reaction, @@ -42,7 +26,7 @@ const useAnnouncements = () => { const getAnnouncements = async () => { const data = await client.announcements.getAnnouncements(); - return data.map(transformAnnouncement); + return data.map(normalizeAnnouncement); }; const { data, ...result } = useQuery>({ @@ -110,4 +94,4 @@ const useAnnouncements = () => { const compareAnnouncements = (a: Announcement, b: Announcement): number => new Date(a.starts_at || a.published_at).getDate() - new Date(b.starts_at || b.published_at).getDate(); -export { updateReactions, useAnnouncements, type Announcement }; +export { updateReactions, useAnnouncements }; diff --git a/src/api/hooks/groups/useBlockGroupMember.ts b/src/api/hooks/groups/useBlockGroupMember.ts index 0372b0dec..37af79b7d 100644 --- a/src/api/hooks/groups/useBlockGroupMember.ts +++ b/src/api/hooks/groups/useBlockGroupMember.ts @@ -2,9 +2,10 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useCreateEntity } from 'soapbox/entity-store/hooks'; import { useClient } from 'soapbox/hooks'; -import type { Account, Group } from 'soapbox/schemas'; +import type { Group } from 'pl-api'; +import type { Account } from 'soapbox/normalizers'; -const useBlockGroupMember = (group: Group, account: Account) => { +const useBlockGroupMember = (group: Pick, account: Pick) => { const client = useClient(); const { createEntity } = useCreateEntity( diff --git a/src/api/hooks/groups/useCreateGroup.ts b/src/api/hooks/groups/useCreateGroup.ts index 981df176e..863276d35 100644 --- a/src/api/hooks/groups/useCreateGroup.ts +++ b/src/api/hooks/groups/useCreateGroup.ts @@ -1,7 +1,9 @@ +import { groupSchema, type Group as BaseGroup } from 'pl-api'; + import { Entities } from 'soapbox/entity-store/entities'; import { useCreateEntity } from 'soapbox/entity-store/hooks'; import { useClient } from 'soapbox/hooks'; -import { groupSchema } from 'soapbox/schemas'; +import { normalizeGroup, type Group } from 'soapbox/normalizers'; interface CreateGroupParams { display_name: string; @@ -16,10 +18,10 @@ interface CreateGroupParams { const useCreateGroup = () => { const client = useClient(); - const { createEntity, ...rest } = useCreateEntity( + const { createEntity, ...rest } = useCreateEntity( [Entities.GROUPS, 'search', ''], (params: CreateGroupParams) => client.experimental.groups.createGroup(params), - { schema: groupSchema }, + { schema: groupSchema, transform: normalizeGroup }, ); return { diff --git a/src/api/hooks/groups/useDeleteGroupStatus.ts b/src/api/hooks/groups/useDeleteGroupStatus.ts index d826ff5fc..6cd95154e 100644 --- a/src/api/hooks/groups/useDeleteGroupStatus.ts +++ b/src/api/hooks/groups/useDeleteGroupStatus.ts @@ -2,9 +2,9 @@ import { Entities } from 'soapbox/entity-store/entities'; import { useDeleteEntity } from 'soapbox/entity-store/hooks'; import { useClient } from 'soapbox/hooks'; -import type { Group } from 'soapbox/schemas'; +import type { Group } from 'pl-api'; -const useDeleteGroupStatus = (group: Group, statusId: string) => { +const useDeleteGroupStatus = (group: Pick, statusId: string) => { const client = useClient(); const { deleteEntity, isSubmitting } = useDeleteEntity( Entities.STATUSES, diff --git a/src/api/hooks/groups/useDemoteGroupMember.ts b/src/api/hooks/groups/useDemoteGroupMember.ts index bdf1976ff..ee6aa9eb4 100644 --- a/src/api/hooks/groups/useDemoteGroupMember.ts +++ b/src/api/hooks/groups/useDemoteGroupMember.ts @@ -1,14 +1,11 @@ +import { groupMemberSchema, type Group, type GroupMember, type GroupRole } from 'pl-api'; import { z } from 'zod'; import { Entities } from 'soapbox/entity-store/entities'; import { useCreateEntity } from 'soapbox/entity-store/hooks'; import { useClient } from 'soapbox/hooks'; -import { groupMemberSchema } from 'soapbox/schemas'; -import type { GroupRole } from 'pl-api'; -import type { Group, GroupMember } from 'soapbox/schemas'; - -const useDemoteGroupMember = (group: Group, groupMember: GroupMember) => { +const useDemoteGroupMember = (group: Pick, groupMember: Pick) => { const client = useClient(); const { createEntity } = useCreateEntity( diff --git a/src/api/hooks/groups/useGroup.ts b/src/api/hooks/groups/useGroup.ts index 2466b1a7e..d7bfabc44 100644 --- a/src/api/hooks/groups/useGroup.ts +++ b/src/api/hooks/groups/useGroup.ts @@ -1,10 +1,11 @@ +import { type Group as BaseGroup, groupSchema } from 'pl-api'; import { useEffect } from 'react'; import { useHistory } from 'react-router-dom'; import { Entities } from 'soapbox/entity-store/entities'; import { useEntity } from 'soapbox/entity-store/hooks'; import { useClient } from 'soapbox/hooks'; -import { type Group, groupSchema } from 'soapbox/schemas'; +import { normalizeGroup, type Group } from 'soapbox/normalizers'; import { useGroupRelationship } from './useGroupRelationship'; @@ -12,11 +13,12 @@ const useGroup = (groupId: string, refetch = true) => { const client = useClient(); const history = useHistory(); - const { entity: group, isUnauthorized, ...result } = useEntity( + const { entity: group, isUnauthorized, ...result } = useEntity( [Entities.GROUPS, groupId], () => client.experimental.groups.getGroup(groupId), { schema: groupSchema, + transform: normalizeGroup, refetch, enabled: !!groupId, }, diff --git a/src/api/hooks/groups/useGroupMedia.ts b/src/api/hooks/groups/useGroupMedia.ts index a9301ed5a..faf802e27 100644 --- a/src/api/hooks/groups/useGroupMedia.ts +++ b/src/api/hooks/groups/useGroupMedia.ts @@ -1,10 +1,9 @@ +import { statusSchema } from 'pl-api'; + import { Entities } from 'soapbox/entity-store/entities'; import { useEntities } from 'soapbox/entity-store/hooks'; import { useClient } from 'soapbox/hooks'; import { normalizeStatus } from 'soapbox/normalizers'; -import { toSchema } from 'soapbox/utils/normalizers'; - -const statusSchema = toSchema(normalizeStatus); const useGroupMedia = (groupId: string) => { const client = useClient(); @@ -12,7 +11,7 @@ const useGroupMedia = (groupId: string) => { return useEntities( [Entities.STATUSES, 'groupMedia', groupId], () => client.timelines.groupTimeline(groupId, { only_media: true }), - { schema: statusSchema }) + { schema: statusSchema, transform: normalizeStatus }) ; }; diff --git a/src/api/hooks/groups/useGroupMembers.test.ts b/src/api/hooks/groups/useGroupMembers.test.ts index d82133423..ccad586d3 100644 --- a/src/api/hooks/groups/useGroupMembers.test.ts +++ b/src/api/hooks/groups/useGroupMembers.test.ts @@ -1,7 +1,8 @@ +import { GroupRoles } from 'pl-api'; + import { __stub } from 'soapbox/api'; import { buildGroupMember } from 'soapbox/jest/factory'; import { renderHook, waitFor } from 'soapbox/jest/test-helpers'; -import { GroupRoles } from 'soapbox/schemas/group-member'; import { useGroupMembers } from './useGroupMembers'; diff --git a/src/api/hooks/groups/useGroupMembers.ts b/src/api/hooks/groups/useGroupMembers.ts index b1fdbef01..d95c870f9 100644 --- a/src/api/hooks/groups/useGroupMembers.ts +++ b/src/api/hooks/groups/useGroupMembers.ts @@ -1,8 +1,8 @@ +import { groupMemberSchema, type GroupMember, type GroupRoles } from 'pl-api'; + import { Entities } from 'soapbox/entity-store/entities'; import { useEntities } from 'soapbox/entity-store/hooks'; import { useClient } from 'soapbox/hooks'; -import { GroupMember, groupMemberSchema } from 'soapbox/schemas'; -import { GroupRoles } from 'soapbox/schemas/group-member'; const useGroupMembers = (groupId: string, role: GroupRoles) => { const client = useClient(); diff --git a/src/api/hooks/groups/useGroupMembershipRequests.ts b/src/api/hooks/groups/useGroupMembershipRequests.ts index 68baf9a3c..42dc41bb8 100644 --- a/src/api/hooks/groups/useGroupMembershipRequests.ts +++ b/src/api/hooks/groups/useGroupMembershipRequests.ts @@ -1,8 +1,9 @@ +import { accountSchema, GroupRoles } from 'pl-api'; + import { Entities } from 'soapbox/entity-store/entities'; import { useDismissEntity, useEntities } from 'soapbox/entity-store/hooks'; import { useClient } from 'soapbox/hooks'; -import { accountSchema } from 'soapbox/schemas'; -import { GroupRoles } from 'soapbox/schemas/group-member'; +import { normalizeAccount } from 'soapbox/normalizers'; import { useGroupRelationship } from './useGroupRelationship'; @@ -19,6 +20,7 @@ const useGroupMembershipRequests = (groupId: string) => { () => client.experimental.groups.getGroupMembershipRequests(groupId), { schema: accountSchema, + transform: normalizeAccount, enabled: relationship?.role === GroupRoles.OWNER || relationship?.role === GroupRoles.ADMIN, }, ); diff --git a/src/api/hooks/groups/useGroupRelationship.ts b/src/api/hooks/groups/useGroupRelationship.ts index 666197c56..3dae15133 100644 --- a/src/api/hooks/groups/useGroupRelationship.ts +++ b/src/api/hooks/groups/useGroupRelationship.ts @@ -1,9 +1,9 @@ +import { type GroupRelationship, groupRelationshipSchema } from 'pl-api'; import { z } from 'zod'; import { Entities } from 'soapbox/entity-store/entities'; import { useEntity } from 'soapbox/entity-store/hooks'; import { useClient } from 'soapbox/hooks'; -import { type GroupRelationship, groupRelationshipSchema } from 'soapbox/schemas'; const useGroupRelationship = (groupId: string | undefined) => { const client = useClient(); diff --git a/src/api/hooks/groups/useGroupRelationships.ts b/src/api/hooks/groups/useGroupRelationships.ts index 9a9b51339..4fb497dbf 100644 --- a/src/api/hooks/groups/useGroupRelationships.ts +++ b/src/api/hooks/groups/useGroupRelationships.ts @@ -1,18 +1,19 @@ +import { type GroupRelationship, groupRelationshipSchema } from 'pl-api'; + import { Entities } from 'soapbox/entity-store/entities'; import { useBatchedEntities } from 'soapbox/entity-store/hooks/useBatchedEntities'; import { useClient, useLoggedIn } from 'soapbox/hooks'; -import { type GroupRelationship, groupRelationshipSchema } from 'soapbox/schemas'; -const useGroupRelationships = (listKey: string[], ids: string[]) => { +const useGroupRelationships = (listKey: string[], groupIds: string[]) => { const client = useClient(); const { isLoggedIn } = useLoggedIn(); - const fetchGroupRelationships = (ids: string[]) => - client.experimental.groups.getGroupRelationships(ids); + const fetchGroupRelationships = (groupIds: string[]) => + client.experimental.groups.getGroupRelationships(groupIds); const { entityMap: relationships, ...result } = useBatchedEntities( [Entities.RELATIONSHIPS, ...listKey], - ids, + groupIds, fetchGroupRelationships, { schema: groupRelationshipSchema, enabled: isLoggedIn }, ); diff --git a/src/api/hooks/groups/useGroups.ts b/src/api/hooks/groups/useGroups.ts index 1debce6ab..0ff89bcdb 100644 --- a/src/api/hooks/groups/useGroups.ts +++ b/src/api/hooks/groups/useGroups.ts @@ -1,8 +1,10 @@ +import { groupSchema, type Group } from 'pl-api'; + import { Entities } from 'soapbox/entity-store/entities'; import { useEntities } from 'soapbox/entity-store/hooks'; import { useClient } from 'soapbox/hooks'; import { useFeatures } from 'soapbox/hooks/useFeatures'; -import { groupSchema, type Group } from 'soapbox/schemas/group'; +import { normalizeGroup } from 'soapbox/normalizers'; import { useGroupRelationships } from './useGroupRelationships'; @@ -13,7 +15,7 @@ const useGroups = () => { const { entities, ...result } = useEntities( [Entities.GROUPS, 'search', ''], () => client.experimental.groups.getGroups(), - { enabled: features.groups, schema: groupSchema }, + { enabled: features.groups, schema: groupSchema, transform: normalizeGroup }, ); const { relationships } = useGroupRelationships( ['search', ''], diff --git a/src/api/hooks/groups/useJoinGroup.ts b/src/api/hooks/groups/useJoinGroup.ts index 6e31c4433..681113e8c 100644 --- a/src/api/hooks/groups/useJoinGroup.ts +++ b/src/api/hooks/groups/useJoinGroup.ts @@ -4,9 +4,9 @@ import { useClient } from 'soapbox/hooks'; import { useGroups } from './useGroups'; -import type { Group } from 'soapbox/schemas'; +import type { Group } from 'pl-api'; -const useJoinGroup = (group: Group) => { +const useJoinGroup = (group: Pick) => { const client = useClient(); const { invalidate } = useGroups(); diff --git a/src/api/hooks/groups/useLeaveGroup.ts b/src/api/hooks/groups/useLeaveGroup.ts index 26bca3849..587badcff 100644 --- a/src/api/hooks/groups/useLeaveGroup.ts +++ b/src/api/hooks/groups/useLeaveGroup.ts @@ -4,9 +4,9 @@ import { useClient } from 'soapbox/hooks'; import { useGroups } from './useGroups'; -import type { Group } from 'soapbox/schemas'; +import type { Group } from 'pl-api'; -const useLeaveGroup = (group: Group) => { +const useLeaveGroup = (group: Pick) => { const client = useClient(); const { invalidate } = useGroups(); diff --git a/src/api/hooks/groups/usePromoteGroupMember.ts b/src/api/hooks/groups/usePromoteGroupMember.ts index 8c057eda3..a9cdcac6d 100644 --- a/src/api/hooks/groups/usePromoteGroupMember.ts +++ b/src/api/hooks/groups/usePromoteGroupMember.ts @@ -1,14 +1,13 @@ +import { groupMemberSchema } from 'pl-api'; import { z } from 'zod'; import { Entities } from 'soapbox/entity-store/entities'; import { useCreateEntity } from 'soapbox/entity-store/hooks'; import { useClient } from 'soapbox/hooks'; -import { groupMemberSchema } from 'soapbox/schemas'; -import type { GroupRole } from 'pl-api'; -import type { Group, GroupMember } from 'soapbox/schemas'; +import type { Group, GroupMember, GroupRole } from 'pl-api'; -const usePromoteGroupMember = (group: Group, groupMember: GroupMember) => { +const usePromoteGroupMember = (group: Pick, groupMember: Pick) => { const client = useClient(); const { createEntity } = useCreateEntity( diff --git a/src/api/hooks/groups/useUpdateGroup.ts b/src/api/hooks/groups/useUpdateGroup.ts index eb81ba1d4..4fe2d1bb5 100644 --- a/src/api/hooks/groups/useUpdateGroup.ts +++ b/src/api/hooks/groups/useUpdateGroup.ts @@ -1,7 +1,9 @@ +import { groupSchema } from 'pl-api'; + import { Entities } from 'soapbox/entity-store/entities'; import { useCreateEntity } from 'soapbox/entity-store/hooks'; import { useClient } from 'soapbox/hooks'; -import { groupSchema } from 'soapbox/schemas'; +import { normalizeGroup } from 'soapbox/normalizers'; interface UpdateGroupParams { display_name?: string; @@ -18,7 +20,7 @@ const useUpdateGroup = (groupId: string) => { const { createEntity, ...rest } = useCreateEntity( [Entities.GROUPS], (params: UpdateGroupParams) => client.experimental.groups.updateGroup(groupId, params), - { schema: groupSchema }, + { schema: groupSchema, transform: normalizeGroup }, ); return { diff --git a/src/api/hooks/statuses/useBookmarkFolder.ts b/src/api/hooks/statuses/useBookmarkFolder.ts index f152827c0..c87a395ba 100644 --- a/src/api/hooks/statuses/useBookmarkFolder.ts +++ b/src/api/hooks/statuses/useBookmarkFolder.ts @@ -1,7 +1,8 @@ +import { type BookmarkFolder } from 'pl-api'; + import { Entities } from 'soapbox/entity-store/entities'; import { selectEntity } from 'soapbox/entity-store/selectors'; import { useAppSelector } from 'soapbox/hooks'; -import { type BookmarkFolder } from 'soapbox/schemas/bookmark-folder'; import { useBookmarkFolders } from './useBookmarkFolders'; diff --git a/src/api/hooks/statuses/useBookmarkFolders.ts b/src/api/hooks/statuses/useBookmarkFolders.ts index 32ebc49c2..75597a4bb 100644 --- a/src/api/hooks/statuses/useBookmarkFolders.ts +++ b/src/api/hooks/statuses/useBookmarkFolders.ts @@ -1,8 +1,9 @@ +import { bookmarkFolderSchema, type BookmarkFolder } from 'pl-api'; + import { Entities } from 'soapbox/entity-store/entities'; import { useEntities } from 'soapbox/entity-store/hooks'; import { useClient } from 'soapbox/hooks'; import { useFeatures } from 'soapbox/hooks/useFeatures'; -import { bookmarkFolderSchema, type BookmarkFolder } from 'soapbox/schemas/bookmark-folder'; const useBookmarkFolders = () => { const client = useClient(); diff --git a/src/api/hooks/statuses/useCreateBookmarkFolder.ts b/src/api/hooks/statuses/useCreateBookmarkFolder.ts index f13f55424..5490fe312 100644 --- a/src/api/hooks/statuses/useCreateBookmarkFolder.ts +++ b/src/api/hooks/statuses/useCreateBookmarkFolder.ts @@ -1,7 +1,8 @@ +import { bookmarkFolderSchema } from 'pl-api'; + import { Entities } from 'soapbox/entity-store/entities'; import { useCreateEntity } from 'soapbox/entity-store/hooks'; import { useClient } from 'soapbox/hooks'; -import { bookmarkFolderSchema } from 'soapbox/schemas/bookmark-folder'; interface CreateBookmarkFolderParams { name: string; diff --git a/src/api/hooks/statuses/useUpdateBookmarkFolder.ts b/src/api/hooks/statuses/useUpdateBookmarkFolder.ts index 2c852a729..4173195ca 100644 --- a/src/api/hooks/statuses/useUpdateBookmarkFolder.ts +++ b/src/api/hooks/statuses/useUpdateBookmarkFolder.ts @@ -1,7 +1,8 @@ +import { bookmarkFolderSchema } from 'pl-api'; + import { Entities } from 'soapbox/entity-store/entities'; import { useCreateEntity } from 'soapbox/entity-store/hooks'; import { useClient } from 'soapbox/hooks'; -import { bookmarkFolderSchema } from 'soapbox/schemas/bookmark-folder'; interface UpdateBookmarkFolderParams { name: string; diff --git a/src/api/hooks/streaming/useCommunityStream.ts b/src/api/hooks/streaming/useCommunityStream.ts index 73cc0d5c1..8c7341ffe 100644 --- a/src/api/hooks/streaming/useCommunityStream.ts +++ b/src/api/hooks/streaming/useCommunityStream.ts @@ -6,12 +6,6 @@ interface UseCommunityStreamOpts { } const useCommunityStream = ({ onlyMedia, enabled }: UseCommunityStreamOpts = {}) => - useTimelineStream( - `community${onlyMedia ? ':media' : ''}`, - `public:local${onlyMedia ? ':media' : ''}`, - undefined, - undefined, - { enabled }, - ); + useTimelineStream(`public:local${onlyMedia ? ':media' : ''}`, {}, enabled); export { useCommunityStream }; \ No newline at end of file diff --git a/src/api/hooks/streaming/useDirectStream.ts b/src/api/hooks/streaming/useDirectStream.ts index e12815fbb..014283300 100644 --- a/src/api/hooks/streaming/useDirectStream.ts +++ b/src/api/hooks/streaming/useDirectStream.ts @@ -1,17 +1,5 @@ -import { useLoggedIn } from 'soapbox/hooks/useLoggedIn'; - import { useTimelineStream } from './useTimelineStream'; -const useDirectStream = () => { - const { isLoggedIn } = useLoggedIn(); - - return useTimelineStream( - 'direct', - 'direct', - null, - null, - { enabled: isLoggedIn }, - ); -}; +const useDirectStream = () => useTimelineStream('direct'); export { useDirectStream }; \ No newline at end of file diff --git a/src/api/hooks/streaming/useGroupStream.ts b/src/api/hooks/streaming/useGroupStream.ts index 5c68bb76e..a5eb2a69d 100644 --- a/src/api/hooks/streaming/useGroupStream.ts +++ b/src/api/hooks/streaming/useGroupStream.ts @@ -1,5 +1,5 @@ import { useTimelineStream } from './useTimelineStream'; -const useGroupStream = (groupId: string) => useTimelineStream(`group:${groupId}`, `group&group=${groupId}`); +const useGroupStream = (groupId: string) => useTimelineStream('group', { group: groupId } as any); export { useGroupStream }; \ No newline at end of file diff --git a/src/api/hooks/streaming/useHashtagStream.ts b/src/api/hooks/streaming/useHashtagStream.ts index 3a2935dd9..620d082bc 100644 --- a/src/api/hooks/streaming/useHashtagStream.ts +++ b/src/api/hooks/streaming/useHashtagStream.ts @@ -1,5 +1,5 @@ import { useTimelineStream } from './useTimelineStream'; -const useHashtagStream = (tag: string) => useTimelineStream(`hashtag:${tag}`, `hashtag&tag=${tag}`); +const useHashtagStream = (tag: string) => useTimelineStream('hashtag', { tag }); export { useHashtagStream }; \ No newline at end of file diff --git a/src/api/hooks/streaming/useListStream.ts b/src/api/hooks/streaming/useListStream.ts index dc0561767..b6e4476fa 100644 --- a/src/api/hooks/streaming/useListStream.ts +++ b/src/api/hooks/streaming/useListStream.ts @@ -5,13 +5,7 @@ import { useTimelineStream } from './useTimelineStream'; const useListStream = (listId: string) => { const { isLoggedIn } = useLoggedIn(); - return useTimelineStream( - `list:${listId}`, - `list&list=${listId}`, - null, - null, - { enabled: isLoggedIn }, - ); + return useTimelineStream('list', { list: listId }, isLoggedIn); }; export { useListStream }; \ No newline at end of file diff --git a/src/api/hooks/streaming/usePublicStream.ts b/src/api/hooks/streaming/usePublicStream.ts index 3807c72a0..cdea0add7 100644 --- a/src/api/hooks/streaming/usePublicStream.ts +++ b/src/api/hooks/streaming/usePublicStream.ts @@ -5,6 +5,6 @@ interface UsePublicStreamOpts { } const usePublicStream = ({ onlyMedia }: UsePublicStreamOpts = {}) => - useTimelineStream(`public${onlyMedia ? ':media' : ''}`, `public${onlyMedia ? ':media' : ''}`); + useTimelineStream(`public${onlyMedia ? ':media' : ''}`); export { usePublicStream }; \ No newline at end of file diff --git a/src/api/hooks/streaming/useRemoteStream.ts b/src/api/hooks/streaming/useRemoteStream.ts index cc7497cef..7e97c0dba 100644 --- a/src/api/hooks/streaming/useRemoteStream.ts +++ b/src/api/hooks/streaming/useRemoteStream.ts @@ -6,9 +6,6 @@ interface UseRemoteStreamOpts { } const useRemoteStream = ({ instance, onlyMedia }: UseRemoteStreamOpts) => - useTimelineStream( - `remote${onlyMedia ? ':media' : ''}:${instance}`, - `public:remote${onlyMedia ? ':media' : ''}&instance=${instance}`, - ); + useTimelineStream(`public:remote${onlyMedia ? ':media' : ''}`, { instance } as any); export { useRemoteStream }; \ No newline at end of file diff --git a/src/api/hooks/streaming/useTimelineStream.ts b/src/api/hooks/streaming/useTimelineStream.ts index 53df1ce9f..5788fcca6 100644 --- a/src/api/hooks/streaming/useTimelineStream.ts +++ b/src/api/hooks/streaming/useTimelineStream.ts @@ -1,38 +1,83 @@ import { useEffect, useRef } from 'react'; -import { connectTimelineStream } from 'soapbox/actions/streaming'; -import { useAppDispatch, useAppSelector, useInstance } from 'soapbox/hooks'; +import { useAppSelector, useClient, useInstance } from 'soapbox/hooks'; import { getAccessToken } from 'soapbox/utils/auth'; -const useTimelineStream = (...args: Parameters) => { - // TODO: get rid of streaming.ts and move the actual opts here. - const [timelineId, path] = args; - const { enabled = true } = args[4] ?? {}; +import type { StreamingEvent } from 'pl-api'; + +const useTimelineStream = (stream: string, params: { list?: string; tag?: string } = {}, enabled = true, listener?: (event: StreamingEvent) => any) => { + const firstUpdate = useRef(true); + + const client = useClient(); - const dispatch = useAppDispatch(); const instance = useInstance(); - const stream = useRef<(() => void) | null>(null); + const socket = useRef<({ + listen: (listener: any, stream?: string) => number; + unlisten: (listener: any) => void; + subscribe: (stream: string, params?: { + list?: string; + tag?: string; + }) => void; + unsubscribe: (stream: string, params?: { + list?: string; + tag?: string; + }) => void; + close: () => void; + }) | null>(null); const accessToken = useAppSelector(getAccessToken); const streamingUrl = instance.configuration.urls.streaming; - const connect = () => { - if (enabled && streamingUrl && !stream.current) { - stream.current = dispatch(connectTimelineStream(...args)); + const connect = async () => { + if (!socket.current) { + socket.current = client.streaming.connect(); + + socket.current.subscribe(stream, params); + if (listener) socket.current.listen(listener); } }; const disconnect = () => { - if (stream.current) { - stream.current(); - stream.current = null; + if (socket.current) { + socket.current.close(); + socket.current = null; } }; useEffect(() => { - connect(); - return disconnect; - }, [accessToken, streamingUrl, timelineId, path, enabled]); + socket.current?.subscribe(stream, params); + + return () => socket.current?.unsubscribe(stream, params); + }, [stream, params.list, params.tag, enabled]); + + useEffect(() => { + if (enabled) { + connect(); + + return () => { + if (listener) socket.current?.unlisten(listener); + }; + } + }, [enabled]); + + useEffect(() => { + if (firstUpdate.current) { + firstUpdate.current = false; + } else { + disconnect(); + connect(); + + return () => { + if (listener) socket.current?.unlisten(listener); + }; + } + }, [accessToken, streamingUrl]); + + useEffect(() => { + if (!enabled) { + disconnect(); + } + }, [enabled]); return { disconnect, diff --git a/src/api/hooks/streaming/useUserStream.ts b/src/api/hooks/streaming/useUserStream.ts index dfad289cd..607a44222 100644 --- a/src/api/hooks/streaming/useUserStream.ts +++ b/src/api/hooks/streaming/useUserStream.ts @@ -1,28 +1,180 @@ -import { expandNotifications } from 'soapbox/actions/notifications'; -import { expandHomeTimeline } from 'soapbox/actions/timelines'; + +import { announcementSchema, type Announcement, type AnnouncementReaction, type FollowRelationshipUpdate, type Relationship, type StreamingEvent } from 'pl-api'; +import { useCallback } from 'react'; + +import { updateConversations } from 'soapbox/actions/conversations'; +import { fetchFilters } from 'soapbox/actions/filters'; +import { updateNotificationsQueue } from 'soapbox/actions/notifications'; +import { getLocale, getSettings } from 'soapbox/actions/settings'; +import { updateStatus } from 'soapbox/actions/statuses'; +import { deleteFromTimelines, processTimelineUpdate } from 'soapbox/actions/timelines'; import { useStatContext } from 'soapbox/contexts/stat-context'; -import { useLoggedIn } from 'soapbox/hooks'; +import { importEntities } from 'soapbox/entity-store/actions'; +import { Entities } from 'soapbox/entity-store/entities'; +import { selectEntity } from 'soapbox/entity-store/selectors'; +import { useAppDispatch, useLoggedIn } from 'soapbox/hooks'; +import messages from 'soapbox/messages'; +import { queryClient } from 'soapbox/queries/client'; +import { getUnreadChatsCount, updateChatListItem } from 'soapbox/utils/chats'; +import { play, soundCache } from 'soapbox/utils/sounds'; + +import { updateReactions } from '../announcements/useAnnouncements'; import { useTimelineStream } from './useTimelineStream'; -import type { IntlShape } from 'react-intl'; -import type { AppDispatch } from 'soapbox/store'; +import type { AppDispatch, RootState } from 'soapbox/store'; -const useUserStream = () => { - const { isLoggedIn } = useLoggedIn(); - const statContext = useStatContext(); +const updateAnnouncementReactions = ({ announcement_id: id, name }: AnnouncementReaction) => { + queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) => + prevResult.map(value => { + if (value.id !== id) return value; - return useTimelineStream( - 'home', - 'user', - refresh, - null, - { statContext, enabled: isLoggedIn }, + return { + ...value, + reactions: updateReactions(value.reactions, name, -1, true), + }; + }), ); }; -/** Refresh home timeline and notifications. */ -const refresh = (dispatch: AppDispatch, intl?: IntlShape, done?: () => void) => - dispatch(expandHomeTimeline({}, intl, () => dispatch(expandNotifications({}, done)))); +const updateAnnouncement = (announcement: Announcement) => + queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) => { + let updated = false; + + const result = prevResult.map(value => value.id === announcement.id + ? (updated = true, announcementSchema.parse(announcement)) + : value); + + if (!updated) return [announcementSchema.parse(announcement), ...result]; + }); + +const deleteAnnouncement = (announcementId: string) => + queryClient.setQueryData(['announcements'], (prevResult: Announcement[]) => + prevResult.filter(value => value.id !== announcementId), + ); + +const followStateToRelationship = (followState: FollowRelationshipUpdate['state']) => { + switch (followState) { + case 'follow_pending': + return { following: false, requested: true }; + case 'follow_accept': + return { following: true, requested: false }; + case 'follow_reject': + return { following: false, requested: false }; + default: + return {}; + } +}; + +const updateFollowRelationships = (update: FollowRelationshipUpdate) => + (dispatch: AppDispatch, getState: () => RootState) => { + const state = getState(); + + const me = state.me; + const relationship = selectEntity(state, Entities.RELATIONSHIPS, update.following.id); + + if (update.follower.id === me && relationship) { + const updated = { + ...relationship, + ...followStateToRelationship(update.state), + }; + + // Add a small delay to deal with API race conditions. + setTimeout(() => dispatch(importEntities([updated], Entities.RELATIONSHIPS)), 300); + } + }; + +const getTimelineFromStream = (stream: Array) => { + switch (stream[0]) { + case 'user': + return 'home'; + case 'hashtag': + case 'hashtag:local': + case 'list': + return `${stream[0]}:${stream[1]}`; + default: + return stream[0]; + } +}; + +const useUserStream = () => { + const { isLoggedIn } = useLoggedIn(); + const dispatch = useAppDispatch(); + const statContext = useStatContext(); + + const listener = useCallback((event: StreamingEvent) => { + switch (event.event) { + case 'update': + dispatch(processTimelineUpdate(getTimelineFromStream(event.stream), event.payload)); + break; + case 'status.update': + dispatch(updateStatus(event.payload)); + break; + case 'delete': + dispatch(deleteFromTimelines(event.payload)); + break; + case 'notification': + dispatch((dispatch, getState) => { + const locale = getLocale(getState()); + messages[locale]().then(messages => { + dispatch( + updateNotificationsQueue( + event.payload, + messages, + locale, + window.location.pathname, + ), + ); + }).catch(error => { + console.error(error); + }); + }); + break; + case 'conversation': + dispatch(updateConversations(event.payload)); + break; + case 'filters_changed': + dispatch(fetchFilters()); + break; + case 'chat_update': + dispatch((_dispatch, getState) => { + const chat = event.payload; + const me = getState().me; + const messageOwned = chat.last_message?.account_id === me; + const settings = getSettings(getState()); + + // Don't update own messages from streaming + if (!messageOwned) { + updateChatListItem(chat); + + if (settings.getIn(['chats', 'sound'])) { + play(soundCache.chat); + } + + // Increment unread counter + statContext?.setUnreadChatsCount(getUnreadChatsCount()); + } + }); + break; + case 'follow_relationships_update': + dispatch(updateFollowRelationships(event.payload)); + break; + case 'announcement': + updateAnnouncement(event.payload); + break; + case 'announcement.reaction': + updateAnnouncementReactions(event.payload); + break; + case 'announcement.delete': + deleteAnnouncement(event.payload); + break; + // case 'marker': + // dispatch({ type: MARKER_FETCH_SUCCESS, marker: JSON.parse(data.payload) }); + // break; + } + }, []); + + return useTimelineStream('user', {}, isLoggedIn, listener); +}; export { useUserStream }; \ No newline at end of file diff --git a/src/components/account.tsx b/src/components/account.tsx index b408c7b42..8c461dd62 100644 --- a/src/components/account.tsx +++ b/src/components/account.tsx @@ -13,11 +13,11 @@ import Badge from './badge'; import RelativeTimestamp from './relative-timestamp'; import { Avatar, Emoji, HStack, Icon, IconButton, Stack, Text } from './ui'; +import type { Account as AccountSchema } from 'soapbox/normalizers'; import type { StatusApprovalStatus } from 'soapbox/normalizers/status'; -import type { Account as AccountSchema } from 'soapbox/schemas'; interface IInstanceFavicon { - account: AccountSchema; + account: Pick; disabled?: boolean; } @@ -41,7 +41,7 @@ const InstanceFavicon: React.FC = ({ account, disabled }) => { } }; - if (!account.pleroma?.favicon) { + if (!account.favicon) { return null; } @@ -51,7 +51,7 @@ const InstanceFavicon: React.FC = ({ account, disabled }) => { onClick={handleClick} disabled={disabled} > - + ); }; @@ -91,7 +91,7 @@ interface IAccount { withLinkToProfile?: boolean; withRelationship?: boolean; showEdit?: boolean; - approvalStatus?: StatusApprovalStatus; + approvalStatus?: StatusApprovalStatus | null; emoji?: string; emojiUrl?: string; note?: string; @@ -224,7 +224,7 @@ const Account = ({ @{username} - {account.pleroma?.favicon && ( + {account.favicon && ( )} @@ -288,7 +288,7 @@ const Account = ({ @{username} - {account.pleroma?.favicon && ( + {account.favicon && ( )} diff --git a/src/components/announcements/announcement-content.tsx b/src/components/announcements/announcement-content.tsx index c65325d2f..035d03e83 100644 --- a/src/components/announcements/announcement-content.tsx +++ b/src/components/announcements/announcement-content.tsx @@ -3,8 +3,8 @@ import { useHistory } from 'react-router-dom'; import { getTextDirection } from 'soapbox/utils/rtl'; -import type { Announcement } from 'soapbox/api/hooks/announcements/useAnnouncements'; -import type { Mention as MentionEntity } from 'soapbox/schemas'; +import type { Mention as MentionEntity } from 'pl-api'; +import type { Announcement } from 'soapbox/normalizers'; interface IAnnouncementContent { announcement: Announcement; diff --git a/src/components/announcements/announcement.tsx b/src/components/announcements/announcement.tsx index 759c0bffd..06b28543b 100644 --- a/src/components/announcements/announcement.tsx +++ b/src/components/announcements/announcement.tsx @@ -9,11 +9,12 @@ import AnnouncementContent from './announcement-content'; import ReactionsBar from './reactions-bar'; import type { Map as ImmutableMap } from 'immutable'; -import type { Announcement as AnnouncementEntity } from 'soapbox/api/hooks/announcements/useAnnouncements'; +import type { CustomEmoji } from 'pl-api'; +import type { Announcement as AnnouncementEntity } from 'soapbox/normalizers'; interface IAnnouncement { announcement: AnnouncementEntity; - emojiMap: ImmutableMap>; + emojiMap: ImmutableMap; } const Announcement: React.FC = ({ announcement, emojiMap }) => { diff --git a/src/components/announcements/announcements-panel.tsx b/src/components/announcements/announcements-panel.tsx index 2e3619e62..c98119aa5 100644 --- a/src/components/announcements/announcements-panel.tsx +++ b/src/components/announcements/announcements-panel.tsx @@ -1,5 +1,5 @@ import clsx from 'clsx'; -import { List as ImmutableList, Map as ImmutableMap } from 'immutable'; +import { Map as ImmutableMap } from 'immutable'; import React, { useState } from 'react'; import { FormattedMessage } from 'react-intl'; import ReactSwipeableViews from 'react-swipeable-views'; @@ -11,9 +11,10 @@ import { useAppSelector } from 'soapbox/hooks'; import Announcement from './announcement'; +import type { CustomEmoji } from 'pl-api'; import type { RootState } from 'soapbox/store'; -const customEmojiMap = createSelector([(state: RootState) => state.custom_emojis], items => (items as ImmutableList>).reduce((map, emoji) => map.set(emoji.get('shortcode')!, emoji), ImmutableMap>())); +const customEmojiMap = createSelector([(state: RootState) => state.custom_emojis], items => items.reduce((map, emoji) => map.set(emoji.shortcode, emoji), ImmutableMap())); const AnnouncementsPanel = () => { const emojiMap = useAppSelector(state => customEmojiMap(state)); @@ -37,7 +38,7 @@ const AnnouncementsPanel = () => { announcement={announcement} emojiMap={emojiMap} /> - )).reverse()} + )).toReversed()} {announcements.length > 1 && ( diff --git a/src/components/announcements/emoji.tsx b/src/components/announcements/emoji.tsx index f835a21b6..601363548 100644 --- a/src/components/announcements/emoji.tsx +++ b/src/components/announcements/emoji.tsx @@ -5,10 +5,11 @@ import { useSettings } from 'soapbox/hooks'; import { joinPublicPath } from 'soapbox/utils/static'; import type { Map as ImmutableMap } from 'immutable'; +import type { CustomEmoji } from 'pl-api'; interface IEmoji { emoji: string; - emojiMap: ImmutableMap>; + emojiMap: ImmutableMap; hovered: boolean; } @@ -31,7 +32,7 @@ const Emoji: React.FC = ({ emoji, emojiMap, hovered }) => { /> ); } else if (emojiMap.get(emoji as any)) { - const filename = (autoPlayGif || hovered) ? emojiMap.getIn([emoji, 'url']) : emojiMap.getIn([emoji, 'static_url']); + const filename = (autoPlayGif || hovered) ? emojiMap.getIn([emoji, 'url']) : emojiMap.getIn([emoji, 'static_url']); const shortCode = `:${emoji}:`; return ( diff --git a/src/components/announcements/reaction.tsx b/src/components/announcements/reaction.tsx index 9d7c05762..bd9a7c008 100644 --- a/src/components/announcements/reaction.tsx +++ b/src/components/announcements/reaction.tsx @@ -8,12 +8,12 @@ import unicodeMapping from 'soapbox/features/emoji/mapping'; import Emoji from './emoji'; import type { Map as ImmutableMap } from 'immutable'; -import type { AnnouncementReaction } from 'soapbox/schemas'; +import type { AnnouncementReaction, CustomEmoji } from 'pl-api'; interface IReaction { announcementId: string; reaction: AnnouncementReaction; - emojiMap: ImmutableMap>; + emojiMap: ImmutableMap; style: React.CSSProperties; } diff --git a/src/components/announcements/reactions-bar.tsx b/src/components/announcements/reactions-bar.tsx index 5488641ba..03cf6e77b 100644 --- a/src/components/announcements/reactions-bar.tsx +++ b/src/components/announcements/reactions-bar.tsx @@ -9,13 +9,13 @@ import { useSettings } from 'soapbox/hooks'; import Reaction from './reaction'; import type { Map as ImmutableMap } from 'immutable'; +import type { AnnouncementReaction, CustomEmoji } from 'pl-api'; import type { Emoji, NativeEmoji } from 'soapbox/features/emoji'; -import type { AnnouncementReaction } from 'soapbox/schemas'; interface IReactionsBar { announcementId: string; - reactions: AnnouncementReaction[]; - emojiMap: ImmutableMap>; + reactions: Array; + emojiMap: ImmutableMap; } const ReactionsBar: React.FC = ({ announcementId, reactions, emojiMap }) => { diff --git a/src/components/attachment-thumbs.tsx b/src/components/attachment-thumbs.tsx index 1fc8743ae..4a8d2d36d 100644 --- a/src/components/attachment-thumbs.tsx +++ b/src/components/attachment-thumbs.tsx @@ -4,11 +4,10 @@ import { openModal } from 'soapbox/actions/modals'; import { MediaGallery } from 'soapbox/features/ui/util/async-components'; import { useAppDispatch } from 'soapbox/hooks'; -import type { List as ImmutableList } from 'immutable'; -import type { Attachment } from 'soapbox/types/entities'; +import type { MediaAttachment } from 'pl-api'; interface IAttachmentThumbs { - media: ImmutableList; + media: Array; onClick?(): void; sensitive?: boolean; } @@ -18,7 +17,7 @@ const AttachmentThumbs = (props: IAttachmentThumbs) => { const dispatch = useAppDispatch(); const fallback =
; - const onOpenMedia = (media: ImmutableList, index: number) => dispatch(openModal('MEDIA', { media, index })); + const onOpenMedia = (media: Array, index: number) => dispatch(openModal('MEDIA', { media, index })); return (
diff --git a/src/components/avatar-stack.tsx b/src/components/avatar-stack.tsx index 53fa79606..b7c89eb91 100644 --- a/src/components/avatar-stack.tsx +++ b/src/components/avatar-stack.tsx @@ -6,7 +6,7 @@ import { Avatar, HStack } from 'soapbox/components/ui'; import { useAppSelector } from 'soapbox/hooks'; import { makeGetAccount } from 'soapbox/selectors'; -import type { Account } from 'soapbox/types/entities'; +import type { Account } from 'soapbox/normalizers'; const getAccount = makeGetAccount(); diff --git a/src/components/badge.tsx b/src/components/badge.tsx index 2f7fad676..74bb87973 100644 --- a/src/components/badge.tsx +++ b/src/components/badge.tsx @@ -10,7 +10,7 @@ interface IBadge { } /** Badge to display on a user's profile. */ const Badge: React.FC = ({ title, slug, color }) => { - const fallback = !['patron', 'admin', 'moderator', 'opaque', 'badge:donor'].includes(slug); + const fallback = !['patron', 'admin', 'moderator', 'opaque'].includes(slug); const isDark = useMemo(() => { if (!color) return false; @@ -30,7 +30,6 @@ const Badge: React.FC = ({ title, slug, color }) => { 'bg-gray-800 text-gray-900': !isDark, } : { 'bg-fuchsia-700 text-white': slug === 'patron', - 'bg-emerald-800 text-white': slug === 'badge:donor', 'bg-black text-white': slug === 'admin', 'bg-cyan-600 text-white': slug === 'moderator', 'bg-gray-100 dark:bg-gray-800 text-gray-900 dark:text-gray-100': fallback, diff --git a/src/components/event-preview.tsx b/src/components/event-preview.tsx index d2e3de706..09ab8e849 100644 --- a/src/components/event-preview.tsx +++ b/src/components/event-preview.tsx @@ -19,7 +19,7 @@ const messages = defineMessages({ }); interface IEventPreview { - status: StatusEntity; + status: Pick; className?: string; hideAction?: boolean; floatingAction?: boolean; @@ -80,7 +80,7 @@ const EventPreview: React.FC = ({ status, className, hideAction, - {event.location.get('name')} + {event.location.name} )} diff --git a/src/components/group-card.tsx b/src/components/group-card.tsx index 71811bfe5..df92fc31a 100644 --- a/src/components/group-card.tsx +++ b/src/components/group-card.tsx @@ -8,7 +8,7 @@ import GroupRelationship from 'soapbox/features/group/components/group-relations import GroupAvatar from './groups/group-avatar'; import { HStack, Stack, Text } from './ui'; -import type { Group as GroupEntity } from 'soapbox/types/entities'; +import type { Group as GroupEntity } from 'soapbox/normalizers'; interface IGroupCard { group: GroupEntity; diff --git a/src/components/groups/group-avatar.tsx b/src/components/groups/group-avatar.tsx index 6e444e181..64708a57d 100644 --- a/src/components/groups/group-avatar.tsx +++ b/src/components/groups/group-avatar.tsx @@ -1,14 +1,11 @@ import clsx from 'clsx'; +import { GroupRoles, type Group } from 'pl-api'; import React from 'react'; -import { GroupRoles } from 'soapbox/schemas/group-member'; - import { Avatar } from '../ui'; -import type { Group } from 'soapbox/schemas'; - interface IGroupAvatar { - group: Group; + group: Pick; size: number; withRing?: boolean; } diff --git a/src/components/groups/popover/group-popover.tsx b/src/components/groups/popover/group-popover.tsx index 17c03c30b..1e1b40daf 100644 --- a/src/components/groups/popover/group-popover.tsx +++ b/src/components/groups/popover/group-popover.tsx @@ -8,7 +8,7 @@ import GroupPrivacy from 'soapbox/features/group/components/group-privacy'; import GroupAvatar from '../group-avatar'; -import type { Group } from 'soapbox/schemas'; +import type { Group } from 'soapbox/normalizers'; interface IGroupPopoverContainer { children: React.ReactElement>; @@ -54,7 +54,7 @@ const GroupPopover = (props: IGroupPopoverContainer) => { )} diff --git a/src/components/hashtag.tsx b/src/components/hashtag.tsx index 37d2424b3..4b18f42c8 100644 --- a/src/components/hashtag.tsx +++ b/src/components/hashtag.tsx @@ -42,7 +42,7 @@ const Hashtag: React.FC = ({ hashtag }) => { +day.uses)} + data={hashtag.history.toReversed().map((day) => +day.uses)} > diff --git a/src/components/media-gallery.tsx b/src/components/media-gallery.tsx index dddc3e9bc..3c124c17f 100644 --- a/src/components/media-gallery.tsx +++ b/src/components/media-gallery.tsx @@ -1,4 +1,5 @@ import clsx from 'clsx'; +import { MediaAttachment } from 'pl-api'; import React, { useState, useRef, useLayoutEffect } from 'react'; import Blurhash from 'soapbox/components/blurhash'; @@ -6,14 +7,12 @@ import Icon from 'soapbox/components/icon'; import StillImage from 'soapbox/components/still-image'; import { MIMETYPE_ICONS } from 'soapbox/components/upload'; import { useSettings, useSoapboxConfig } from 'soapbox/hooks'; -import { Attachment } from 'soapbox/types/entities'; import { truncateFilename } from 'soapbox/utils/media'; import { isIOS } from '../is-mobile'; import { isPanoramic, isPortrait, isNonConformingRatio, minimumAspectRatio, maximumAspectRatio } from '../utils/media-aspect-ratio'; import type { Property } from 'csstype'; -import type { List as ImmutableList } from 'immutable'; const ATTACHMENT_LIMIT = 4; const MAX_FILENAME_LENGTH = 45; @@ -36,18 +35,21 @@ interface SizeData { width: number; } +const getAspectRatio = (attachment: MediaAttachment) => + (attachment.type === 'gifv' || attachment.type === 'image' || attachment.type === 'video') && attachment.meta.original?.aspect || null; + const withinLimits = (aspectRatio: number) => aspectRatio >= minimumAspectRatio && aspectRatio <= maximumAspectRatio; -const shouldLetterbox = (attachment: Attachment): boolean => { - const aspectRatio = attachment.getIn(['meta', 'original', 'aspect']) as number | undefined; +const shouldLetterbox = (attachment: MediaAttachment): boolean => { + const aspectRatio = getAspectRatio(attachment); if (!aspectRatio) return true; return !withinLimits(aspectRatio); }; interface IItem { - attachment: Attachment; + attachment: MediaAttachment; standalone?: boolean; index: number; size: number; @@ -144,7 +146,7 @@ const Item: React.FC = ({ const attachmentIcon = ( ); @@ -271,9 +273,9 @@ const Item: React.FC = ({ interface IMediaGallery { sensitive?: boolean; - media: ImmutableList; + media: Array; height?: number; - onOpenMedia: (media: ImmutableList, index: number) => void; + onOpenMedia: (media: Array, index: number) => void; defaultWidth?: number; cacheWidth?: (width: number) => void; visible?: boolean; @@ -302,7 +304,7 @@ const MediaGallery: React.FC = (props) => { const getSizeDataSingle = (): SizeData => { const w = width || defaultWidth; - const aspectRatio = media.getIn([0, 'meta', 'original', 'aspect']) as number | undefined; + const aspectRatio = getAspectRatio(media[0]); const getHeight = () => { if (!aspectRatio) return w * 9 / 16; @@ -328,7 +330,7 @@ const MediaGallery: React.FC = (props) => { let itemsDimensions: Dimensions[] = []; const ratios = Array(size).fill(null).map((_, i) => - media.getIn([i, 'meta', 'original', 'aspect']) as number, + getAspectRatio(media[i]) as number, ); const [ar1, ar2, ar3, ar4] = ratios; @@ -526,9 +528,9 @@ const MediaGallery: React.FC = (props) => { }; }; - const sizeData: SizeData = getSizeData(media.size); + const sizeData: SizeData = getSizeData(media.length); - const children = media.take(ATTACHMENT_LIMIT).map((attachment, i) => ( + const children = media.slice(0, ATTACHMENT_LIMIT).map((attachment, i) => ( = (props) => { visible={!!props.visible} dimensions={sizeData.itemsDimensions[i]} last={i === ATTACHMENT_LIMIT - 1} - total={media.size} + total={media.length} /> )); diff --git a/src/components/mention.tsx b/src/components/mention.tsx index e33be36e1..ae5c1c317 100644 --- a/src/components/mention.tsx +++ b/src/components/mention.tsx @@ -3,7 +3,7 @@ import { Link } from 'react-router-dom'; import { Tooltip } from './ui'; -import type { Mention as MentionEntity } from 'soapbox/schemas'; +import type { Mention as MentionEntity } from 'pl-api'; interface IMention { mention: Pick; diff --git a/src/components/polls/poll-footer.tsx b/src/components/polls/poll-footer.tsx index c61369dbb..85a6905b0 100644 --- a/src/components/polls/poll-footer.tsx +++ b/src/components/polls/poll-footer.tsx @@ -8,7 +8,7 @@ import RelativeTimestamp from '../relative-timestamp'; import { Button, HStack, Stack, Text, Tooltip } from '../ui'; import type { Selected } from './poll'; -import type { Poll as PollEntity } from 'soapbox/types/entities'; +import type { Poll } from 'soapbox/normalizers'; const messages = defineMessages({ closed: { id: 'poll.closed', defaultMessage: 'Closed' }, @@ -16,7 +16,7 @@ const messages = defineMessages({ }); interface IPollFooter { - poll: PollEntity; + poll: Poll; showResults: boolean; selected: Selected; } @@ -56,7 +56,7 @@ const PollFooter: React.FC = ({ poll, showResults, selected }): JSX )} - {poll.pleroma?.non_anonymous && ( + {poll.non_anonymous && ( <> diff --git a/src/components/polls/poll-option.tsx b/src/components/polls/poll-option.tsx index b16cbede1..b391b858c 100644 --- a/src/components/polls/poll-option.tsx +++ b/src/components/polls/poll-option.tsx @@ -5,10 +5,7 @@ import { Motion, presets, spring } from 'react-motion'; import { HStack, Icon, Text } from '../ui'; -import type { - Poll as PollEntity, - PollOption as PollOptionEntity, -} from 'soapbox/types/entities'; +import type { Poll } from 'soapbox/normalizers/poll'; const messages = defineMessages({ voted: { id: 'poll.voted', defaultMessage: 'You voted for this answer' }, @@ -94,8 +91,8 @@ const PollOptionText: React.FC = ({ poll, option, index, active }; interface IPollOption { - poll: PollEntity; - option: PollOptionEntity; + poll: Poll; + option: Poll['options'][number]; index: number; showResults?: boolean; active: boolean; diff --git a/src/components/polls/poll.tsx b/src/components/polls/poll.tsx index 6e455c59e..88ce0924c 100644 --- a/src/components/polls/poll.tsx +++ b/src/components/polls/poll.tsx @@ -10,13 +10,13 @@ import { Stack, Text } from '../ui'; import PollFooter from './poll-footer'; import PollOption from './poll-option'; -import type { Status } from 'soapbox/types/entities'; +import type { Status } from 'soapbox/normalizers'; type Selected = Record; interface IPoll { id: string; - status?: Status; + status?: Pick; } const messages = defineMessages({ diff --git a/src/components/preview-card.tsx b/src/components/preview-card.tsx index 1d2525a10..d25507901 100644 --- a/src/components/preview-card.tsx +++ b/src/components/preview-card.tsx @@ -1,21 +1,20 @@ import clsx from 'clsx'; -import { List as ImmutableList } from 'immutable'; +import { type MediaAttachment, mediaAttachmentSchema } from 'pl-api'; import React, { useState, useEffect } from 'react'; import Blurhash from 'soapbox/components/blurhash'; import { HStack, Stack, Text, Icon } from 'soapbox/components/ui'; -import { normalizeAttachment } from 'soapbox/normalizers'; import { addAutoPlay } from 'soapbox/utils/media'; import { getTextDirection } from 'soapbox/utils/rtl'; -import type { Card as CardEntity, Attachment } from 'soapbox/types/entities'; +import type { Card as CardEntity } from 'soapbox/types/entities'; /** Props for `PreviewCard`. */ interface IPreviewCard { card: CardEntity; maxTitle?: number; maxDescription?: number; - onOpenMedia: (attachments: ImmutableList, index: number) => void; + onOpenMedia: (attachments: Array, index: number) => void; compact?: boolean; defaultWidth?: number; cacheWidth?: (width: number) => void; @@ -46,7 +45,8 @@ const PreviewCard: React.FC = ({ const trimmedDescription = trim(card.description, maxDescription); const handlePhotoClick = () => { - const attachment = normalizeAttachment({ + const attachment = mediaAttachmentSchema.parse({ + id: '', type: 'image', url: card.embed_url, description: trimmedTitle, @@ -58,7 +58,7 @@ const PreviewCard: React.FC = ({ }, }); - onOpenMedia(ImmutableList([attachment]), 0); + onOpenMedia([attachment], 0); }; const handleEmbedClick: React.MouseEventHandler = (e) => { diff --git a/src/components/profile-hover-card.tsx b/src/components/profile-hover-card.tsx index 1872ef73a..6aefcefec 100644 --- a/src/components/profile-hover-card.tsx +++ b/src/components/profile-hover-card.tsx @@ -19,17 +19,17 @@ import { showProfileHoverCard } from './hover-ref-wrapper'; import { dateFormatOptions } from './relative-timestamp'; import { Card, CardBody, HStack, Icon, Stack, Text } from './ui'; -import type { Account } from 'soapbox/schemas'; +import type { Account } from 'soapbox/normalizers'; import type { AppDispatch } from 'soapbox/store'; const getBadges = ( - account?: Pick, + account?: Pick, ): JSX.Element[] => { const badges = []; - if (account?.admin) { + if (account?.is_admin) { badges.push(} />); - } else if (account?.moderator) { + } else if (account?.is_moderator) { badges.push(} />); } diff --git a/src/components/quoted-status.tsx b/src/components/quoted-status.tsx index f2f243f64..d537cae0c 100644 --- a/src/components/quoted-status.tsx +++ b/src/components/quoted-status.tsx @@ -122,7 +122,7 @@ const QuotedStatus: React.FC = ({ status, onCancel, compose }) => {status.quote && } - {status.media_attachments.size > 0 && ( + {status.media_attachments.length > 0 && ( )} diff --git a/src/components/sidebar-navigation.tsx b/src/components/sidebar-navigation.tsx index 6cede6cb1..b4f6ae45b 100644 --- a/src/components/sidebar-navigation.tsx +++ b/src/components/sidebar-navigation.tsx @@ -133,7 +133,7 @@ const SidebarNavigation = () => { if (features.conversations) { return ( } /> @@ -212,7 +212,7 @@ const SidebarNavigation = () => { text={} /> - {account.staff && ( + {account.is_admin || account.is_moderator && ( = ({ const { allowedEmoji } = soapboxConfig; const { account } = useOwnAccount(); - const isStaff = account ? account.staff : false; - const isAdmin = account ? account.admin : false; + const isStaff = account ? account.is_admin || account.is_moderator : false; + const isAdmin = account ? account.is_admin : false; if (!status) { return null; @@ -499,7 +500,7 @@ const StatusActionBar: React.FC = ({ icon: require('@tabler/icons/outline/at.svg'), }); - if (status.account.pleroma?.accepts_chat_messages === true) { + if (status.account.accepts_chat_messages === true) { menu.push({ text: intl.formatMessage(messages.chat, { name: username }), action: handleChatClick, @@ -627,8 +628,8 @@ const StatusActionBar: React.FC = ({ const reblogCount = status.reblogs_count; const favouriteCount = status.favourites_count; - const emojiReactCount = status.reactions ? reduceEmoji( - status.reactions, + const emojiReactCount = status.emoji_reactions ? reduceEmoji( + status.emoji_reactions, favouriteCount, status.favourited, allowedEmoji, @@ -758,7 +759,7 @@ const StatusActionBar: React.FC = ({ ) : ( = ({ /> )} - {features.dislikes && ( + {features.statusDislikes && ( = React.memo(({ const parsedHtml = useMemo( (): string => translatable && status.translation - ? status.translation.get('content')! + ? status.translation.content! : (status.contentMapHtml && status.currentLanguage) - ? status.contentMapHtml.get(status.currentLanguage, status.contentHtml) + ? (status.contentMapHtml[status.currentLanguage] || status.contentHtml) : status.contentHtml, [status.contentHtml, status.translation, status.currentLanguage], ); @@ -174,8 +174,10 @@ const StatusContent: React.FC = React.memo(({ output.push(); } - const hasPoll = status.poll && typeof status.poll === 'string'; - if (hasPoll) { + let hasPoll = false; + + if (status.poll && typeof status.poll === 'string') { + hasPoll = true; output.push(); } diff --git a/src/components/status-language-picker.tsx b/src/components/status-language-picker.tsx index 16cd1bd85..c2be068bf 100644 --- a/src/components/status-language-picker.tsx +++ b/src/components/status-language-picker.tsx @@ -15,7 +15,7 @@ const messages = defineMessages({ }); interface IStatusLanguagePicker { - status: Status; + status: Pick; showLabel?: boolean; } @@ -23,7 +23,7 @@ const StatusLanguagePicker: React.FC = ({ status, showLab const intl = useIntl(); const dispatch = useAppDispatch(); - if (!status.contentMapHtml || status.contentMapHtml.isEmpty()) return null; + if (!status.contentMapHtml || !Object.keys(status.contentMapHtml).length) return null; const icon = ; @@ -32,7 +32,7 @@ const StatusLanguagePicker: React.FC = ({ status, showLab · ({ + items={Object.keys(status.contentMapHtml).map((language) => ({ text: languages[language as Language] || language, action: () => dispatch(changeStatusLanguage(status.id, language)), active: language === status.currentLanguage, diff --git a/src/components/status-media.tsx b/src/components/status-media.tsx index b26df0c60..bac5d2310 100644 --- a/src/components/status-media.tsx +++ b/src/components/status-media.tsx @@ -8,8 +8,8 @@ import { MediaGallery, Video, Audio } from 'soapbox/features/ui/util/async-compo import { useAppDispatch, useSettings } from 'soapbox/hooks'; import { defaultMediaVisibility } from 'soapbox/utils/status'; -import type { List as ImmutableList } from 'immutable'; -import type { Status, Attachment } from 'soapbox/types/entities'; +import type { MediaAttachment } from 'pl-api'; +import type { Status } from 'soapbox/types/entities'; interface IStatusMedia { /** Status entity to render media for. */ @@ -32,10 +32,10 @@ const StatusMedia: React.FC = ({ const dispatch = useAppDispatch(); const { displayMedia } = useSettings(); - const visible = showMedia || (status.hidden === null ? defaultMediaVisibility(status, displayMedia) : status.hidden); + const visible = showMedia || (status.hidden === null ? defaultMediaVisibility(status, displayMedia) : !status.hidden); - const size = status.media_attachments.size; - const firstAttachment = status.media_attachments.first(); + const size = status.media_attachments.length; + const firstAttachment = status.media_attachments[0]; let media: JSX.Element | null = null; @@ -49,7 +49,7 @@ const StatusMedia: React.FC = ({
); - const openMedia = (media: ImmutableList, index: number) => { + const openMedia = (media: Array, index: number) => { dispatch(openModal('MEDIA', { media, status, index })); }; @@ -72,7 +72,7 @@ const StatusMedia: React.FC = ({ blurhash={video.blurhash} src={video.url} alt={video.description} - aspectRatio={Number(video.meta.getIn(['original', 'aspect']))} + aspectRatio={Number(video.meta.original?.aspect)} height={285} visible={visible} inline @@ -87,11 +87,11 @@ const StatusMedia: React.FC = ({