diff --git a/app/soapbox/actions/__tests__/account-notes.test.ts b/app/soapbox/actions/__tests__/account-notes.test.ts index dc4eac6f3..fdae30838 100644 --- a/app/soapbox/actions/__tests__/account-notes.test.ts +++ b/app/soapbox/actions/__tests__/account-notes.test.ts @@ -1,15 +1,12 @@ import { Map as ImmutableMap } from 'immutable'; import { __stub } from 'soapbox/api'; -import { buildRelationship } from 'soapbox/jest/factory'; +import { buildAccount, buildRelationship } from 'soapbox/jest/factory'; import { mockStore, rootState } from 'soapbox/jest/test-helpers'; import { ReducerRecord, EditRecord } from 'soapbox/reducers/account-notes'; -import { normalizeAccount } from '../../normalizers'; import { changeAccountNoteComment, initAccountNoteModal, submitAccountNote } from '../account-notes'; -import type { Account } from 'soapbox/types/entities'; - describe('submitAccountNote()', () => { let store: ReturnType; @@ -72,13 +69,13 @@ describe('initAccountNoteModal()', () => { }); it('dispatches the proper actions', async() => { - const account = normalizeAccount({ + const account = buildAccount({ id: '1', acct: 'justin-username', display_name: 'Justin L', avatar: 'test.jpg', verified: true, - }) as Account; + }); const expectedActions = [ { type: 'ACCOUNT_NOTE_INIT_MODAL', account, comment: 'hello' }, { type: 'MODAL_CLOSE', modalType: 'ACCOUNT_NOTE' }, diff --git a/app/soapbox/actions/__tests__/accounts.test.ts b/app/soapbox/actions/__tests__/accounts.test.ts index c13f8ef90..7157fef1d 100644 --- a/app/soapbox/actions/__tests__/accounts.test.ts +++ b/app/soapbox/actions/__tests__/accounts.test.ts @@ -76,9 +76,14 @@ describe('fetchAccount()', () => { }); const state = rootState - .set('accounts', ImmutableMap({ - [id]: account, - }) as any); + .set('entities', { + 'ACCOUNTS': { + store: { + [id]: account, + }, + lists: {}, + }, + }); store = mockStore(state); @@ -168,9 +173,14 @@ describe('fetchAccountByUsername()', () => { }); state = rootState - .set('accounts', ImmutableMap({ - [id]: account, - })); + .set('entities', { + 'ACCOUNTS': { + store: { + [id]: account, + }, + lists: {}, + }, + }); store = mockStore(state); diff --git a/app/soapbox/actions/__tests__/me.test.ts b/app/soapbox/actions/__tests__/me.test.ts index d4dc1d31f..91fba12fa 100644 --- a/app/soapbox/actions/__tests__/me.test.ts +++ b/app/soapbox/actions/__tests__/me.test.ts @@ -1,13 +1,11 @@ import { Map as ImmutableMap } from 'immutable'; import { __stub } from 'soapbox/api'; +import { buildAccount } from 'soapbox/jest/factory'; import { mockStore, rootState } from 'soapbox/jest/test-helpers'; -import { AccountRecord } from 'soapbox/normalizers'; +import { AuthUserRecord, ReducerRecord } from 'soapbox/reducers/auth'; -import { AuthUserRecord, ReducerRecord } from '../../reducers/auth'; -import { - fetchMe, patchMe, -} from '../me'; +import { fetchMe, patchMe } from '../me'; jest.mock('../../storage/kv-store', () => ({ __esModule: true, @@ -48,11 +46,15 @@ describe('fetchMe()', () => { }), }), })) - .set('accounts', ImmutableMap({ - [accountUrl]: AccountRecord({ - url: accountUrl, - }), - }) as any); + .set('entities', { + 'ACCOUNTS': { + store: { + [accountUrl]: buildAccount({ url: accountUrl }), + }, + lists: {}, + }, + }); + store = mockStore(state); }); diff --git a/app/soapbox/actions/__tests__/soapbox.test.ts b/app/soapbox/actions/__tests__/soapbox.test.ts index e3dcf9a85..6247ab256 100644 --- a/app/soapbox/actions/__tests__/soapbox.test.ts +++ b/app/soapbox/actions/__tests__/soapbox.test.ts @@ -1,4 +1,6 @@ -import { rootState } from '../../jest/test-helpers'; +import { rootState } from 'soapbox/jest/test-helpers'; +import { RootState } from 'soapbox/store'; + import { getSoapboxConfig } from '../soapbox'; const ASCII_HEART = '❤'; // '\u2764\uFE0F' @@ -6,13 +8,13 @@ const RED_HEART_RGI = '❤️'; // '\u2764' describe('getSoapboxConfig()', () => { it('returns RGI heart on Pleroma > 2.3', () => { - const state = rootState.setIn(['instance', 'version'], '2.7.2 (compatible; Pleroma 2.3.0)'); + const state = rootState.setIn(['instance', 'version'], '2.7.2 (compatible; Pleroma 2.3.0)') as RootState; expect(getSoapboxConfig(state).allowedEmoji.includes(RED_HEART_RGI)).toBe(true); expect(getSoapboxConfig(state).allowedEmoji.includes(ASCII_HEART)).toBe(false); }); it('returns an ASCII heart on Pleroma < 2.3', () => { - const state = rootState.setIn(['instance', 'version'], '2.7.2 (compatible; Pleroma 2.0.0)'); + const state = rootState.setIn(['instance', 'version'], '2.7.2 (compatible; Pleroma 2.0.0)') as RootState; expect(getSoapboxConfig(state).allowedEmoji.includes(ASCII_HEART)).toBe(true); expect(getSoapboxConfig(state).allowedEmoji.includes(RED_HEART_RGI)).toBe(false); }); diff --git a/app/soapbox/actions/account-notes.ts b/app/soapbox/actions/account-notes.ts index 2d0c0cb13..691f63fc3 100644 --- a/app/soapbox/actions/account-notes.ts +++ b/app/soapbox/actions/account-notes.ts @@ -4,8 +4,8 @@ import { openModal, closeModal } from './modals'; import type { AxiosError } from 'axios'; import type { AnyAction } from 'redux'; +import type { Account } from 'soapbox/schemas'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { Account } from 'soapbox/types/entities'; const ACCOUNT_NOTE_SUBMIT_REQUEST = 'ACCOUNT_NOTE_SUBMIT_REQUEST'; const ACCOUNT_NOTE_SUBMIT_SUCCESS = 'ACCOUNT_NOTE_SUBMIT_SUCCESS'; diff --git a/app/soapbox/actions/aliases.ts b/app/soapbox/actions/aliases.ts index 3a5b61163..b7856cbe0 100644 --- a/app/soapbox/actions/aliases.ts +++ b/app/soapbox/actions/aliases.ts @@ -111,7 +111,7 @@ const addToAliases = (account: Account) => dispatch(addToAliasesRequest()); - api(getState).patch('/api/v1/accounts/update_credentials', { also_known_as: [...alsoKnownAs, account.pleroma.get('ap_id')] }) + api(getState).patch('/api/v1/accounts/update_credentials', { also_known_as: [...alsoKnownAs, account.pleroma?.ap_id] }) .then((response => { toast.success(messages.createSuccess); dispatch(addToAliasesSuccess); diff --git a/app/soapbox/actions/domain-blocks.ts b/app/soapbox/actions/domain-blocks.ts index 4308edec7..86be9744a 100644 --- a/app/soapbox/actions/domain-blocks.ts +++ b/app/soapbox/actions/domain-blocks.ts @@ -3,7 +3,6 @@ import { isLoggedIn } from 'soapbox/utils/auth'; import api, { getLinks } from '../api'; import type { AxiosError } from 'axios'; -import type { List as ImmutableList } from 'immutable'; import type { AppDispatch, RootState } from 'soapbox/store'; const DOMAIN_BLOCK_REQUEST = 'DOMAIN_BLOCK_REQUEST'; @@ -30,8 +29,11 @@ const blockDomain = (domain: string) => api(getState).post('/api/v1/domain_blocks', { domain }).then(() => { const at_domain = '@' + domain; - const accounts = getState().accounts.filter(item => item.acct.endsWith(at_domain)).valueSeq().map(item => item.id); - dispatch(blockDomainSuccess(domain, accounts.toList())); + const accounts = getState().accounts + .filter(item => item.acct.endsWith(at_domain)) + .map(item => item.id); + + dispatch(blockDomainSuccess(domain, accounts)); }).catch(err => { dispatch(blockDomainFail(domain, err)); }); @@ -42,7 +44,7 @@ const blockDomainRequest = (domain: string) => ({ domain, }); -const blockDomainSuccess = (domain: string, accounts: ImmutableList) => ({ +const blockDomainSuccess = (domain: string, accounts: string[]) => ({ type: DOMAIN_BLOCK_SUCCESS, domain, accounts, @@ -68,8 +70,8 @@ const unblockDomain = (domain: string) => api(getState).delete('/api/v1/domain_blocks', params).then(() => { const at_domain = '@' + domain; - const accounts = getState().accounts.filter(item => item.get('acct').endsWith(at_domain)).valueSeq().map(item => item.get('id')); - dispatch(unblockDomainSuccess(domain, accounts.toList())); + const accounts = getState().accounts.filter(item => item.acct.endsWith(at_domain)).map(item => item.id); + dispatch(unblockDomainSuccess(domain, accounts)); }).catch(err => { dispatch(unblockDomainFail(domain, err)); }); @@ -80,7 +82,7 @@ const unblockDomainRequest = (domain: string) => ({ domain, }); -const unblockDomainSuccess = (domain: string, accounts: ImmutableList) => ({ +const unblockDomainSuccess = (domain: string, accounts: string[]) => ({ type: DOMAIN_UNBLOCK_SUCCESS, domain, accounts, diff --git a/app/soapbox/actions/reports.ts b/app/soapbox/actions/reports.ts index f51ef1f0a..be6a60ed8 100644 --- a/app/soapbox/actions/reports.ts +++ b/app/soapbox/actions/reports.ts @@ -3,8 +3,9 @@ import api from '../api'; import { openModal } from './modals'; import type { AxiosError } from 'axios'; +import type { Account } from 'soapbox/schemas'; import type { AppDispatch, RootState } from 'soapbox/store'; -import type { Account, ChatMessage, Group, Status } from 'soapbox/types/entities'; +import type { ChatMessage, Group, Status } from 'soapbox/types/entities'; const REPORT_INIT = 'REPORT_INIT'; const REPORT_CANCEL = 'REPORT_CANCEL'; diff --git a/app/soapbox/actions/timelines.ts b/app/soapbox/actions/timelines.ts index af52c1ce2..21e36798f 100644 --- a/app/soapbox/actions/timelines.ts +++ b/app/soapbox/actions/timelines.ts @@ -40,7 +40,7 @@ const processTimelineUpdate = (timeline: string, status: APIEntity, accept: ((st const hasPendingStatuses = !getState().pending_statuses.isEmpty(); const columnSettings = getSettings(getState()).get(timeline, ImmutableMap()); - const shouldSkipQueue = shouldFilter(normalizeStatus(status) as Status, columnSettings); + const shouldSkipQueue = shouldFilter(normalizeStatus(status) as Status, columnSettings as any); if (ownStatus && hasPendingStatuses) { // WebSockets push statuses without the Idempotency-Key, diff --git a/app/soapbox/components/__tests__/account.test.tsx b/app/soapbox/components/__tests__/account.test.tsx index 7f1458349..e46b1a3af 100644 --- a/app/soapbox/components/__tests__/account.test.tsx +++ b/app/soapbox/components/__tests__/account.test.tsx @@ -1,20 +1,19 @@ import { Map as ImmutableMap } from 'immutable'; import React from 'react'; -import { render, screen } from '../../jest/test-helpers'; -import { normalizeAccount } from '../../normalizers'; -import Account from '../account'; +import { buildAccount } from 'soapbox/jest/factory'; -import type { ReducerAccount } from 'soapbox/reducers/accounts'; +import { render, screen } from '../../jest/test-helpers'; +import Account from '../account'; describe('', () => { it('renders account name and username', () => { - const account = normalizeAccount({ + const account = buildAccount({ id: '1', acct: 'justin-username', display_name: 'Justin L', avatar: 'test.jpg', - }) as ReducerAccount; + }); const store = { accounts: ImmutableMap({ @@ -29,13 +28,13 @@ describe('', () => { describe('verification badge', () => { it('renders verification badge', () => { - const account = normalizeAccount({ + const account = buildAccount({ id: '1', acct: 'justin-username', display_name: 'Justin L', avatar: 'test.jpg', verified: true, - }) as ReducerAccount; + }); const store = { accounts: ImmutableMap({ @@ -48,13 +47,13 @@ describe('', () => { }); it('does not render verification badge', () => { - const account = normalizeAccount({ + const account = buildAccount({ id: '1', acct: 'justin-username', display_name: 'Justin L', avatar: 'test.jpg', verified: false, - }) as ReducerAccount; + }); const store = { accounts: ImmutableMap({ diff --git a/app/soapbox/components/__tests__/display-name.test.tsx b/app/soapbox/components/__tests__/display-name.test.tsx index 4c1c1bd23..59ba65f19 100644 --- a/app/soapbox/components/__tests__/display-name.test.tsx +++ b/app/soapbox/components/__tests__/display-name.test.tsx @@ -1,15 +1,13 @@ import React from 'react'; -import { normalizeAccount } from 'soapbox/normalizers'; +import { buildAccount } from 'soapbox/jest/factory'; import { render, screen } from '../../jest/test-helpers'; import DisplayName from '../display-name'; -import type { ReducerAccount } from 'soapbox/reducers/accounts'; - describe('', () => { it('renders display name + account name', () => { - const account = normalizeAccount({ acct: 'bar@baz' }) as ReducerAccount; + const account = buildAccount({ acct: 'bar@baz' }); render(); expect(screen.getByTestId('display-name')).toHaveTextContent('bar@baz'); diff --git a/app/soapbox/components/display-name.tsx b/app/soapbox/components/display-name.tsx index 0902b5ac2..9610ae8b6 100644 --- a/app/soapbox/components/display-name.tsx +++ b/app/soapbox/components/display-name.tsx @@ -8,10 +8,10 @@ import { getAcct } from '../utils/accounts'; import { HStack, Text } from './ui'; import VerificationBadge from './verification-badge'; -import type { Account } from 'soapbox/types/entities'; +import type { Account } from 'soapbox/schemas'; interface IDisplayName { - account: Account + account: Pick withSuffix?: boolean children?: React.ReactNode } @@ -37,7 +37,7 @@ const DisplayName: React.FC = ({ account, children, withSuffix = t return ( - + {displayName} {withSuffix && suffix} diff --git a/app/soapbox/containers/soapbox.tsx b/app/soapbox/containers/soapbox.tsx index 75134b00d..6048be71d 100644 --- a/app/soapbox/containers/soapbox.tsx +++ b/app/soapbox/containers/soapbox.tsx @@ -96,7 +96,7 @@ const SoapboxMount = () => { const features = useFeatures(); const { pepeEnabled } = useRegistrationStatus(); - const waitlisted = account && !account.source.get('approved', true); + const waitlisted = account && account.source?.approved === false; const needsOnboarding = useAppSelector(state => state.onboarding.needsOnboarding); const showOnboarding = account && !waitlisted && needsOnboarding; const { redirectRootNoLogin } = soapboxConfig; diff --git a/app/soapbox/entity-store/hooks/useEntityActions.ts b/app/soapbox/entity-store/hooks/useEntityActions.ts index c7e2e431d..449817e32 100644 --- a/app/soapbox/entity-store/hooks/useEntityActions.ts +++ b/app/soapbox/entity-store/hooks/useEntityActions.ts @@ -26,7 +26,7 @@ function useEntityActions( const { entityType, path } = parseEntitiesPath(expandedPath); const { deleteEntity, isSubmitting: deleteSubmitting } = - useDeleteEntity(entityType, (entityId) => api.delete(endpoints.delete!.replaceAll(':id', entityId))); + useDeleteEntity(entityType, (entityId) => api.delete(endpoints.delete!.replace(/:id/g, entityId))); const { createEntity, isSubmitting: createSubmitting } = useCreateEntity(path, (data) => api.post(endpoints.post!, data), opts); diff --git a/app/soapbox/features/account/components/header.tsx b/app/soapbox/features/account/components/header.tsx index 81743c2ac..f96b9b415 100644 --- a/app/soapbox/features/account/components/header.tsx +++ b/app/soapbox/features/account/components/header.tsx @@ -615,7 +615,7 @@ const Header: React.FC = ({ account }) => { return (
{(account.moved && typeof account.moved === 'object') && ( - + )}
diff --git a/app/soapbox/features/admin/components/unapproved-account.tsx b/app/soapbox/features/admin/components/unapproved-account.tsx index cf99baa6e..9aa1ba4fe 100644 --- a/app/soapbox/features/admin/components/unapproved-account.tsx +++ b/app/soapbox/features/admin/components/unapproved-account.tsx @@ -27,7 +27,7 @@ const UnapprovedAccount: React.FC = ({ accountId }) => { - @{account.get('acct')} + @{account.acct} {adminAccount?.invite_request || ''} diff --git a/app/soapbox/features/aliases/components/account.tsx b/app/soapbox/features/aliases/components/account.tsx index 5abc0a66c..f0aa77e8c 100644 --- a/app/soapbox/features/aliases/components/account.tsx +++ b/app/soapbox/features/aliases/components/account.tsx @@ -8,15 +8,13 @@ import { HStack } from 'soapbox/components/ui'; import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; import { makeGetAccount } from 'soapbox/selectors'; -import type { List as ImmutableList } from 'immutable'; - const messages = defineMessages({ add: { id: 'aliases.account.add', defaultMessage: 'Create alias' }, }); interface IAccount { accountId: string - aliases: ImmutableList + aliases: string[] } const Account: React.FC = ({ accountId, aliases }) => { @@ -30,8 +28,9 @@ const Account: React.FC = ({ accountId, aliases }) => { const added = useAppSelector((state) => { const account = getAccount(state, accountId); - const apId = account?.pleroma.get('ap_id'); + const apId = account?.pleroma?.ap_id; const name = features.accountMoving ? account?.acct : apId; + if (!name) return false; return aliases.includes(name); }); diff --git a/app/soapbox/features/aliases/index.tsx b/app/soapbox/features/aliases/index.tsx index 268ca8cde..d3ad6b48e 100644 --- a/app/soapbox/features/aliases/index.tsx +++ b/app/soapbox/features/aliases/index.tsx @@ -1,4 +1,3 @@ -import { List as ImmutableList } from 'immutable'; import React, { useEffect } from 'react'; import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; @@ -28,11 +27,11 @@ const Aliases = () => { const aliases = useAppSelector((state) => { if (features.accountMoving) { - return state.aliases.aliases.items; + return state.aliases.aliases.items.toArray(); } else { - return account!.pleroma.get('also_known_as'); + return account?.pleroma?.also_known_as ?? []; } - }) as ImmutableList; + }); const searchAccountIds = useAppSelector((state) => state.aliases.suggestions.items); const loaded = useAppSelector((state) => state.aliases.suggestions.loaded); diff --git a/app/soapbox/features/birthdays/account.tsx b/app/soapbox/features/birthdays/account.tsx index 99260e1dc..21aec8cd2 100644 --- a/app/soapbox/features/birthdays/account.tsx +++ b/app/soapbox/features/birthdays/account.tsx @@ -23,7 +23,7 @@ const Account: React.FC = ({ accountId }) => { if (!account) return null; - const birthday = account.birthday; + const birthday = account.pleroma?.birthday; if (!birthday) return null; const formattedBirthday = intl.formatDate(birthday, { day: 'numeric', month: 'short', year: 'numeric' }); diff --git a/app/soapbox/features/chats/components/__tests__/chat-message-list.test.tsx b/app/soapbox/features/chats/components/__tests__/chat-message-list.test.tsx index d538bee34..b0870c867 100644 --- a/app/soapbox/features/chats/components/__tests__/chat-message-list.test.tsx +++ b/app/soapbox/features/chats/components/__tests__/chat-message-list.test.tsx @@ -4,8 +4,8 @@ import { VirtuosoMockContext } from 'react-virtuoso'; import { ChatContext } from 'soapbox/contexts/chat-context'; +import { buildAccount } from 'soapbox/jest/factory'; import { normalizeChatMessage, normalizeInstance } from 'soapbox/normalizers'; -import { IAccount } from 'soapbox/queries/accounts'; import { ChatMessage } from 'soapbox/types/entities'; import { __stub } from '../../../../api'; @@ -15,7 +15,7 @@ import ChatMessageList from '../chat-message-list'; const chat: IChat = { accepted: true, - account: { + account: buildAccount({ username: 'username', verified: true, id: '1', @@ -23,7 +23,7 @@ const chat: IChat = { avatar: 'avatar', avatar_static: 'avatar', display_name: 'my name', - } as IAccount, + }), chat_type: 'direct', created_at: '2020-06-10T02:05:06.000Z', created_by_account: '2', diff --git a/app/soapbox/features/chats/components/__tests__/chat-widget.test.tsx b/app/soapbox/features/chats/components/__tests__/chat-widget.test.tsx index c191d9b75..197c98366 100644 --- a/app/soapbox/features/chats/components/__tests__/chat-widget.test.tsx +++ b/app/soapbox/features/chats/components/__tests__/chat-widget.test.tsx @@ -1,26 +1,32 @@ -import { Map as ImmutableMap } from 'immutable'; import React from 'react'; import { Route, Switch } from 'react-router-dom'; -import { normalizeAccount } from 'soapbox/normalizers'; +import { buildAccount } from 'soapbox/jest/factory'; import { render, rootState } from '../../../../jest/test-helpers'; import ChatWidget from '../chat-widget/chat-widget'; const id = '1'; -const account = normalizeAccount({ +const account = buildAccount({ id, acct: 'justin-username', display_name: 'Justin L', avatar: 'test.jpg', - chats_onboarded: true, + source: { + chats_onboarded: true, + }, }); const store = rootState .set('me', id) - .set('accounts', ImmutableMap({ - [id]: account, - }) as any); + .set('entities', { + 'ACCOUNTS': { + store: { + [id]: account, + }, + lists: {}, + }, + }); describe('', () => { describe('when on the /chats endpoint', () => { @@ -45,16 +51,23 @@ describe('', () => { describe('when the user has not onboarded chats', () => { it('hides the widget', async () => { - const accountWithoutChats = normalizeAccount({ + const accountWithoutChats = buildAccount({ id, acct: 'justin-username', display_name: 'Justin L', avatar: 'test.jpg', - chats_onboarded: false, + source: { + chats_onboarded: false, + }, + }); + const newStore = store.set('entities', { + 'ACCOUNTS': { + store: { + [id]: accountWithoutChats, + }, + lists: {}, + }, }); - const newStore = store.set('accounts', ImmutableMap({ - [id]: accountWithoutChats, - }) as any); const screen = render( , diff --git a/app/soapbox/features/chats/components/chat-message.tsx b/app/soapbox/features/chats/components/chat-message.tsx index f8c7898ed..16e33a918 100644 --- a/app/soapbox/features/chats/components/chat-message.tsx +++ b/app/soapbox/features/chats/components/chat-message.tsx @@ -13,7 +13,6 @@ import emojify from 'soapbox/features/emoji'; import Bundle from 'soapbox/features/ui/components/bundle'; import { MediaGallery } from 'soapbox/features/ui/util/async-components'; import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; -import { normalizeAccount } from 'soapbox/normalizers'; import { ChatKeys, IChat, useChatActions } from 'soapbox/queries/chats'; import { queryClient } from 'soapbox/queries/client'; import { stripHTML } from 'soapbox/utils/html'; @@ -24,7 +23,7 @@ import ChatMessageReactionWrapper from './chat-message-reaction-wrapper/chat-mes import type { Menu as IMenu } from 'soapbox/components/dropdown-menu'; import type { IMediaGallery } from 'soapbox/components/media-gallery'; -import type { Account, ChatMessage as ChatMessageEntity } from 'soapbox/types/entities'; +import type { ChatMessage as ChatMessageEntity } from 'soapbox/types/entities'; const messages = defineMessages({ copy: { id: 'chats.actions.copy', defaultMessage: 'Copy' }, @@ -178,7 +177,7 @@ const ChatMessage = (props: IChatMessage) => { if (features.reportChats) { menu.push({ text: intl.formatMessage(messages.report), - action: () => dispatch(initReport(ReportableEntities.CHAT_MESSAGE, normalizeAccount(chat.account) as Account, { chatMessage })), + action: () => dispatch(initReport(ReportableEntities.CHAT_MESSAGE, chat.account, { chatMessage })), icon: require('@tabler/icons/flag.svg'), }); } diff --git a/app/soapbox/features/chats/components/chat-page/chat-page.tsx b/app/soapbox/features/chats/components/chat-page/chat-page.tsx index 09c5057fa..746494623 100644 --- a/app/soapbox/features/chats/components/chat-page/chat-page.tsx +++ b/app/soapbox/features/chats/components/chat-page/chat-page.tsx @@ -19,7 +19,7 @@ const ChatPage: React.FC = ({ chatId }) => { const account = useOwnAccount(); const history = useHistory(); - const isOnboarded = account?.chats_onboarded; + const isOnboarded = account?.source?.chats_onboarded ?? true; const path = history.location.pathname; const isSidebarHidden = matchPath(path, { diff --git a/app/soapbox/features/chats/components/chat-page/components/chat-page-settings.tsx b/app/soapbox/features/chats/components/chat-page/components/chat-page-settings.tsx index 3d4d4de65..097120f52 100644 --- a/app/soapbox/features/chats/components/chat-page/components/chat-page-settings.tsx +++ b/app/soapbox/features/chats/components/chat-page/components/chat-page-settings.tsx @@ -33,7 +33,7 @@ const ChatPageSettings = () => { const [data, setData] = useState({ chats_onboarded: true, - accepts_chat_messages: account?.accepts_chat_messages, + accepts_chat_messages: account?.pleroma?.accepts_chat_messages === true, }); const onToggleChange = (key: string[], checked: boolean) => { diff --git a/app/soapbox/features/chats/components/chat-page/components/welcome.tsx b/app/soapbox/features/chats/components/chat-page/components/welcome.tsx index 0a269f6a8..187039eb8 100644 --- a/app/soapbox/features/chats/components/chat-page/components/welcome.tsx +++ b/app/soapbox/features/chats/components/chat-page/components/welcome.tsx @@ -26,7 +26,7 @@ const Welcome = () => { const [data, setData] = useState({ chats_onboarded: true, - accepts_chat_messages: account?.accepts_chat_messages, + accepts_chat_messages: account?.pleroma?.accepts_chat_messages === true, }); const handleSubmit = (event: React.FormEvent) => { diff --git a/app/soapbox/features/chats/components/chat-widget/chat-widget.tsx b/app/soapbox/features/chats/components/chat-widget/chat-widget.tsx index a155abbaa..038bab90e 100644 --- a/app/soapbox/features/chats/components/chat-widget/chat-widget.tsx +++ b/app/soapbox/features/chats/components/chat-widget/chat-widget.tsx @@ -11,9 +11,10 @@ const ChatWidget = () => { const history = useHistory(); const path = history.location.pathname; - const shouldHideWidget = Boolean(path.match(/^\/chats/)); + const isChatsPath = Boolean(path.match(/^\/chats/)); + const isOnboarded = account?.source?.chats_onboarded ?? true; - if (!account?.chats_onboarded || shouldHideWidget) { + if (!isOnboarded || isChatsPath) { return null; } diff --git a/app/soapbox/features/conversations/components/conversation.tsx b/app/soapbox/features/conversations/components/conversation.tsx index 442b10dd5..47cedc500 100644 --- a/app/soapbox/features/conversations/components/conversation.tsx +++ b/app/soapbox/features/conversations/components/conversation.tsx @@ -19,7 +19,7 @@ const Conversation: React.FC = ({ conversationId, onMoveUp, onMov const conversation = state.conversations.items.find(x => x.id === conversationId)!; return { - accounts: conversation.accounts.map((accountId: string) => state.accounts.get(accountId, null)!), + accounts: conversation.accounts.map((accountId: string) => state.accounts.get(accountId)!), unread: conversation.unread, lastStatusId: conversation.last_status || null, }; diff --git a/app/soapbox/features/directory/components/account-card.tsx b/app/soapbox/features/directory/components/account-card.tsx index 407c4a45c..0a5707c4c 100644 --- a/app/soapbox/features/directory/components/account-card.tsx +++ b/app/soapbox/features/directory/components/account-card.tsx @@ -87,10 +87,10 @@ const AccountCard: React.FC = ({ id }) => { - {account.last_status_at === null ? ( - - ) : ( + {account.last_status_at ? ( + ) : ( + )} diff --git a/app/soapbox/features/edit-profile/components/profile-preview.tsx b/app/soapbox/features/edit-profile/components/profile-preview.tsx index e4aa73b18..1edead4dc 100644 --- a/app/soapbox/features/edit-profile/components/profile-preview.tsx +++ b/app/soapbox/features/edit-profile/components/profile-preview.tsx @@ -8,7 +8,7 @@ import { useSoapboxConfig } from 'soapbox/hooks'; import type { Account } from 'soapbox/types/entities'; interface IProfilePreview { - account: Account + account: Pick } /** Displays a preview of the user's account, including avatar, banner, etc. */ diff --git a/app/soapbox/features/edit-profile/index.tsx b/app/soapbox/features/edit-profile/index.tsx index f45d75afa..6a1f1b5d3 100644 --- a/app/soapbox/features/edit-profile/index.tsx +++ b/app/soapbox/features/edit-profile/index.tsx @@ -1,4 +1,3 @@ -import { List as ImmutableList } from 'immutable'; import React, { useState, useEffect, useMemo } from 'react'; import { defineMessages, useIntl, FormattedMessage } from 'react-intl'; @@ -20,22 +19,26 @@ import { Toggle, } from 'soapbox/components/ui'; import { useAppDispatch, useOwnAccount, useFeatures, useInstance } from 'soapbox/hooks'; -import { normalizeAccount } from 'soapbox/normalizers'; +import { accountSchema } from 'soapbox/schemas'; import toast from 'soapbox/toast'; import resizeImage from 'soapbox/utils/resize-image'; import ProfilePreview from './components/profile-preview'; import type { StreamfieldComponent } from 'soapbox/components/ui/streamfield/streamfield'; -import type { Account } from 'soapbox/types/entities'; +import type { Account } from 'soapbox/schemas'; /** * Whether the user is hiding their follows and/or followers. * Pleroma's config is granular, but we simplify it into one setting. */ -const hidesNetwork = (account: Account): boolean => { - const { hide_followers, hide_follows, hide_followers_count, hide_follows_count } = account.pleroma.toJS(); - return Boolean(hide_followers && hide_follows && hide_followers_count && hide_follows_count); +const hidesNetwork = ({ pleroma }: Account): boolean => { + return Boolean( + pleroma?.hide_followers && + pleroma?.hide_follows && + pleroma?.hide_followers_count && + pleroma?.hide_follows_count, + ); }; const messages = defineMessages({ @@ -124,18 +127,18 @@ const accountToCredentials = (account: Account): AccountCredentials => { discoverable: account.discoverable, bot: account.bot, display_name: account.display_name, - note: account.source.get('note', ''), + note: account.source?.note ?? '', locked: account.locked, - fields_attributes: [...account.source.get>('fields', ImmutableList()).toJS()], - stranger_notifications: account.getIn(['pleroma', 'notification_settings', 'block_from_strangers']) === true, - accepts_email_list: account.getIn(['pleroma', 'accepts_email_list']) === true, + fields_attributes: [...account.source?.fields ?? []], + stranger_notifications: account.pleroma?.notification_settings?.block_from_strangers === true, + accepts_email_list: account.pleroma?.accepts_email_list === true, hide_followers: hideNetwork, hide_follows: hideNetwork, hide_followers_count: hideNetwork, hide_follows_count: hideNetwork, website: account.website, location: account.location, - birthday: account.birthday, + birthday: account.pleroma?.birthday ?? undefined, }; }; @@ -299,12 +302,13 @@ const EditProfile: React.FC = () => { /** Preview account data. */ const previewAccount = useMemo(() => { - return normalizeAccount({ - ...account?.toJS(), + return accountSchema.parse({ + id: '1', + ...account, ...data, avatar: avatarUrl, header: headerUrl, - }) as Account; + }); }, [account?.id, data.display_name, avatarUrl, headerUrl]); return ( diff --git a/app/soapbox/features/onboarding/steps/bio-step.tsx b/app/soapbox/features/onboarding/steps/bio-step.tsx index aaf27a131..eee5be200 100644 --- a/app/soapbox/features/onboarding/steps/bio-step.tsx +++ b/app/soapbox/features/onboarding/steps/bio-step.tsx @@ -18,7 +18,7 @@ const BioStep = ({ onNext }: { onNext: () => void }) => { const dispatch = useAppDispatch(); const account = useOwnAccount(); - const [value, setValue] = React.useState(account?.source.get('note') || ''); + const [value, setValue] = React.useState(account?.source?.note ?? ''); const [isSubmitting, setSubmitting] = React.useState(false); const [errors, setErrors] = React.useState([]); diff --git a/app/soapbox/features/settings/components/messages-settings.tsx b/app/soapbox/features/settings/components/messages-settings.tsx index 78d46e751..8c7dda248 100644 --- a/app/soapbox/features/settings/components/messages-settings.tsx +++ b/app/soapbox/features/settings/components/messages-settings.tsx @@ -29,7 +29,7 @@ const MessagesSettings = () => { label={intl.formatMessage(messages.label)} > diff --git a/app/soapbox/features/ui/components/__tests__/subscribe-button.test.tsx b/app/soapbox/features/ui/components/__tests__/subscribe-button.test.tsx index 5edc9636b..7fff45278 100644 --- a/app/soapbox/features/ui/components/__tests__/subscribe-button.test.tsx +++ b/app/soapbox/features/ui/components/__tests__/subscribe-button.test.tsx @@ -1,13 +1,10 @@ import React from 'react'; -import { buildRelationship } from 'soapbox/jest/factory'; +import { buildAccount, buildRelationship } from 'soapbox/jest/factory'; import { render, screen } from 'soapbox/jest/test-helpers'; -import { normalizeAccount } from 'soapbox/normalizers'; import SubscribeButton from '../subscription-button'; -import type { ReducerAccount } from 'soapbox/reducers/accounts'; - const justin = { id: '1', acct: 'justin-username', @@ -20,7 +17,7 @@ describe('', () => { describe('with "accountNotifies" disabled', () => { it('renders nothing', () => { - const account = normalizeAccount({ ...justin, relationship: buildRelationship({ following: true }) }) as ReducerAccount; + const account = buildAccount({ ...justin, relationship: buildRelationship({ following: true }) }); render(, undefined, store); expect(screen.queryAllByTestId('icon-button')).toHaveLength(0); diff --git a/app/soapbox/features/ui/components/modals/report-modal/steps/confirmation-step.tsx b/app/soapbox/features/ui/components/modals/report-modal/steps/confirmation-step.tsx index 2cfe14136..08441a267 100644 --- a/app/soapbox/features/ui/components/modals/report-modal/steps/confirmation-step.tsx +++ b/app/soapbox/features/ui/components/modals/report-modal/steps/confirmation-step.tsx @@ -6,7 +6,7 @@ import { getSoapboxConfig } from 'soapbox/actions/soapbox'; import { Stack, Text } from 'soapbox/components/ui'; import { useAppSelector } from 'soapbox/hooks'; -import type { ReducerAccount } from 'soapbox/reducers/accounts'; +import type { Account } from 'soapbox/schemas'; const messages = defineMessages({ accountEntity: { id: 'report.confirmation.entity.account', defaultMessage: 'account' }, @@ -15,8 +15,8 @@ const messages = defineMessages({ content: { id: 'report.confirmation.content', defaultMessage: 'If we find that this {entity} is violating the {link} we will take further action on the matter.' }, }); -interface IOtherActionsStep { - account: ReducerAccount +interface IConfirmationStep { + account?: Account } const termsOfServiceText = ( ( ); -const ConfirmationStep = ({ account }: IOtherActionsStep) => { +const ConfirmationStep: React.FC = () => { const intl = useIntl(); const links = useAppSelector((state) => getSoapboxConfig(state).get('links') as any); const entityType = useAppSelector((state) => state.reports.new.entityType); diff --git a/app/soapbox/features/ui/components/modals/report-modal/steps/other-actions-step.tsx b/app/soapbox/features/ui/components/modals/report-modal/steps/other-actions-step.tsx index 55e435603..03c45c822 100644 --- a/app/soapbox/features/ui/components/modals/report-modal/steps/other-actions-step.tsx +++ b/app/soapbox/features/ui/components/modals/report-modal/steps/other-actions-step.tsx @@ -9,7 +9,7 @@ import StatusCheckBox from 'soapbox/features/report/components/status-check-box' import { useAppDispatch, useAppSelector, useFeatures } from 'soapbox/hooks'; import { isRemote, getDomain } from 'soapbox/utils/accounts'; -import type { ReducerAccount } from 'soapbox/reducers/accounts'; +import type { Account } from 'soapbox/schemas'; const messages = defineMessages({ addAdditionalStatuses: { id: 'report.otherActions.addAdditional', defaultMessage: 'Would you like to add additional statuses to this report?' }, @@ -20,7 +20,7 @@ const messages = defineMessages({ }); interface IOtherActionsStep { - account: ReducerAccount + account: Account } const OtherActionsStep = ({ account }: IOtherActionsStep) => { @@ -104,7 +104,7 @@ const OtherActionsStep = ({ account }: IOtherActionsStep) => { /> - + diff --git a/app/soapbox/features/ui/components/modals/report-modal/steps/reason-step.tsx b/app/soapbox/features/ui/components/modals/report-modal/steps/reason-step.tsx index cc680114c..d9f006084 100644 --- a/app/soapbox/features/ui/components/modals/report-modal/steps/reason-step.tsx +++ b/app/soapbox/features/ui/components/modals/report-modal/steps/reason-step.tsx @@ -7,7 +7,7 @@ import { fetchRules } from 'soapbox/actions/rules'; import { FormGroup, Stack, Text, Textarea } from 'soapbox/components/ui'; import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; -import type { ReducerAccount } from 'soapbox/reducers/accounts'; +import type { Account } from 'soapbox/schemas'; const messages = defineMessages({ placeholder: { id: 'report.placeholder', defaultMessage: 'Additional comments' }, @@ -15,12 +15,12 @@ const messages = defineMessages({ }); interface IReasonStep { - account: ReducerAccount + account?: Account } const RULES_HEIGHT = 385; -const ReasonStep = (_props: IReasonStep) => { +const ReasonStep: React.FC = () => { const dispatch = useAppDispatch(); const intl = useIntl(); diff --git a/app/soapbox/features/ui/components/profile-field.tsx b/app/soapbox/features/ui/components/profile-field.tsx index d27db0f05..a6468a177 100644 --- a/app/soapbox/features/ui/components/profile-field.tsx +++ b/app/soapbox/features/ui/components/profile-field.tsx @@ -7,7 +7,7 @@ import { HStack, Icon } from 'soapbox/components/ui'; import BundleContainer from 'soapbox/features/ui/containers/bundle-container'; import { CryptoAddress } from 'soapbox/features/ui/util/async-components'; -import type { Field } from 'soapbox/types/entities'; +import type { Account } from 'soapbox/schemas'; const getTicker = (value: string): string => (value.match(/\$([a-zA-Z]*)/i) || [])[1]; const isTicker = (value: string): boolean => Boolean(getTicker(value)); @@ -26,7 +26,7 @@ const dateFormatOptions: FormatDateOptions = { }; interface IProfileField { - field: Field + field: Account['fields'][number] } /** Renders a single profile field. */ diff --git a/app/soapbox/features/ui/components/profile-info-panel.tsx b/app/soapbox/features/ui/components/profile-info-panel.tsx index 39128561d..9cb4e7135 100644 --- a/app/soapbox/features/ui/components/profile-info-panel.tsx +++ b/app/soapbox/features/ui/components/profile-info-panel.tsx @@ -86,7 +86,7 @@ const ProfileInfoPanel: React.FC = ({ account, username }) => }; const renderBirthday = (): React.ReactNode => { - const birthday = account.birthday; + const birthday = account.pleroma?.birthday; if (!birthday) return null; const formattedBirthday = intl.formatDate(birthday, { timeZone: 'UTC', day: 'numeric', month: 'long', year: 'numeric' }); @@ -131,7 +131,7 @@ const ProfileInfoPanel: React.FC = ({ account, username }) => } const content = { __html: account.note_emojified }; - const deactivated = !account.pleroma.get('is_active', true) === true; + const deactivated = account.pleroma?.deactivated ?? false; const displayNameHtml = deactivated ? { __html: intl.formatMessage(messages.deactivated) } : { __html: account.display_name_html }; const memberSinceDate = intl.formatDate(account.created_at, { month: 'long', year: 'numeric' }); const badges = getBadges(); @@ -229,7 +229,7 @@ const ProfileInfoPanel: React.FC = ({ account, username }) => - {account.fields.size > 0 && ( + {account.fields.length > 0 && ( {account.fields.map((field, i) => ( diff --git a/app/soapbox/features/ui/components/subscription-button.tsx b/app/soapbox/features/ui/components/subscription-button.tsx index 94c6d39ef..93244e031 100644 --- a/app/soapbox/features/ui/components/subscription-button.tsx +++ b/app/soapbox/features/ui/components/subscription-button.tsx @@ -22,7 +22,7 @@ const messages = defineMessages({ }); interface ISubscriptionButton { - account: AccountEntity + account: Pick } const SubscriptionButton = ({ account }: ISubscriptionButton) => { @@ -36,8 +36,8 @@ const SubscriptionButton = ({ account }: ISubscriptionButton) => { ? account.relationship?.notifying : account.relationship?.subscribing; const title = isSubscribed - ? intl.formatMessage(messages.unsubscribe, { name: account.get('username') }) - : intl.formatMessage(messages.subscribe, { name: account.get('username') }); + ? intl.formatMessage(messages.unsubscribe, { name: account.username }) + : intl.formatMessage(messages.subscribe, { name: account.username }); const onSubscribeSuccess = () => toast.success(intl.formatMessage(messages.subscribeSuccess)); @@ -53,11 +53,11 @@ const SubscriptionButton = ({ account }: ISubscriptionButton) => { const onNotifyToggle = () => { if (account.relationship?.notifying) { - dispatch(followAccount(account.get('id'), { notify: false } as any)) + dispatch(followAccount(account.id, { notify: false } as any)) ?.then(() => onUnsubscribeSuccess()) .catch(() => onUnsubscribeFailure()); } else { - dispatch(followAccount(account.get('id'), { notify: true } as any)) + dispatch(followAccount(account.id, { notify: true } as any)) ?.then(() => onSubscribeSuccess()) .catch(() => onSubscribeFailure()); } @@ -65,11 +65,11 @@ const SubscriptionButton = ({ account }: ISubscriptionButton) => { const onSubscriptionToggle = () => { if (account.relationship?.subscribing) { - dispatch(unsubscribeAccount(account.get('id'))) + dispatch(unsubscribeAccount(account.id)) ?.then(() => onUnsubscribeSuccess()) .catch(() => onUnsubscribeFailure()); } else { - dispatch(subscribeAccount(account.get('id'))) + dispatch(subscribeAccount(account.id)) ?.then(() => onSubscribeSuccess()) .catch(() => onSubscribeFailure()); } diff --git a/app/soapbox/features/verification/waitlist-page.tsx b/app/soapbox/features/verification/waitlist-page.tsx index 0eb2bb452..5e329f69e 100644 --- a/app/soapbox/features/verification/waitlist-page.tsx +++ b/app/soapbox/features/verification/waitlist-page.tsx @@ -14,7 +14,7 @@ const WaitlistPage = () => { const instance = useInstance(); const me = useOwnAccount(); - const isSmsVerified = me?.source.get('sms_verified'); + const isSmsVerified = me?.source?.sms_verified ?? true; const onClickLogOut: React.MouseEventHandler = (event) => { event.preventDefault(); diff --git a/app/soapbox/jest/factory.ts b/app/soapbox/jest/factory.ts index 9bc4217f6..0e372697a 100644 --- a/app/soapbox/jest/factory.ts +++ b/app/soapbox/jest/factory.ts @@ -1,6 +1,5 @@ import { v4 as uuidv4 } from 'uuid'; -import { normalizeStatus } from 'soapbox/normalizers'; import { accountSchema, adSchema, @@ -10,6 +9,7 @@ import { groupSchema, groupTagSchema, relationshipSchema, + statusSchema, type Account, type Ad, type Card, @@ -22,22 +22,24 @@ import { } from 'soapbox/schemas'; import { GroupRoles } from 'soapbox/schemas/group-member'; +import type { PartialDeep } from 'type-fest'; + // TODO: there's probably a better way to create these factory functions. // This looks promising but didn't work on my first attempt: https://github.com/anatine/zod-plugins/tree/main/packages/zod-mock -function buildAccount(props: Partial = {}): Account { +function buildAccount(props: PartialDeep = {}): Account { return accountSchema.parse(Object.assign({ id: uuidv4(), }, props)); } -function buildCard(props: Partial = {}): Card { +function buildCard(props: PartialDeep = {}): Card { return cardSchema.parse(Object.assign({ url: 'https://soapbox.test', }, props)); } -function buildGroup(props: Partial = {}): Group { +function buildGroup(props: PartialDeep = {}): Group { return groupSchema.parse(Object.assign({ id: uuidv4(), owner: { @@ -46,13 +48,13 @@ function buildGroup(props: Partial = {}): Group { }, props)); } -function buildGroupRelationship(props: Partial = {}): GroupRelationship { +function buildGroupRelationship(props: PartialDeep = {}): GroupRelationship { return groupRelationshipSchema.parse(Object.assign({ id: uuidv4(), }, props)); } -function buildGroupTag(props: Partial = {}): GroupTag { +function buildGroupTag(props: PartialDeep = {}): GroupTag { return groupTagSchema.parse(Object.assign({ id: uuidv4(), name: uuidv4(), @@ -60,8 +62,8 @@ function buildGroupTag(props: Partial = {}): GroupTag { } function buildGroupMember( - props: Partial = {}, - accountProps: Partial = {}, + props: PartialDeep = {}, + accountProps: PartialDeep = {}, ): GroupMember { return groupMemberSchema.parse(Object.assign({ id: uuidv4(), @@ -70,25 +72,26 @@ function buildGroupMember( }, props)); } -function buildAd(props: Partial = {}): Ad { +function buildAd(props: PartialDeep = {}): Ad { return adSchema.parse(Object.assign({ card: buildCard(), }, props)); } -function buildRelationship(props: Partial = {}): Relationship { +function buildRelationship(props: PartialDeep = {}): Relationship { return relationshipSchema.parse(Object.assign({ id: uuidv4(), }, props)); } -function buildStatus(props: Partial = {}) { - return normalizeStatus(Object.assign({ +function buildStatus(props: PartialDeep = {}) { + return statusSchema.parse(Object.assign({ id: uuidv4(), }, props)); } export { + buildAccount, buildAd, buildCard, buildGroup, diff --git a/app/soapbox/normalizers/chat.ts b/app/soapbox/normalizers/chat.ts index 3c0d3a6e1..4c6c9c70f 100644 --- a/app/soapbox/normalizers/chat.ts +++ b/app/soapbox/normalizers/chat.ts @@ -1,10 +1,9 @@ import { Map as ImmutableMap, Record as ImmutableRecord, fromJS } from 'immutable'; -import type { ReducerAccount } from 'soapbox/reducers/accounts'; import type { Account, EmbeddedEntity } from 'soapbox/types/entities'; export const ChatRecord = ImmutableRecord({ - account: null as EmbeddedEntity, + account: null as EmbeddedEntity, id: '', unread: 0, last_message: '' as string || null, diff --git a/app/soapbox/normalizers/status.ts b/app/soapbox/normalizers/status.ts index a207b0434..e20037099 100644 --- a/app/soapbox/normalizers/status.ts +++ b/app/soapbox/normalizers/status.ts @@ -13,9 +13,8 @@ import { import { normalizeAttachment } from 'soapbox/normalizers/attachment'; import { normalizeEmoji } from 'soapbox/normalizers/emoji'; import { normalizeMention } from 'soapbox/normalizers/mention'; -import { cardSchema, pollSchema, tombstoneSchema } from 'soapbox/schemas'; +import { accountSchema, cardSchema, pollSchema, tombstoneSchema } from 'soapbox/schemas'; -import type { ReducerAccount } from 'soapbox/reducers/accounts'; import type { Account, Attachment, Card, Emoji, Group, Mention, Poll, EmbeddedEntity } from 'soapbox/types/entities'; export type StatusApprovalStatus = 'pending' | 'approval' | 'rejected'; @@ -42,7 +41,7 @@ interface Tombstone { // https://docs.joinmastodon.org/entities/status/ export const StatusRecord = ImmutableRecord({ - account: null as EmbeddedEntity, + account: null as unknown as Account, application: null as ImmutableMap | null, approval_status: 'approved' as StatusApprovalStatus, bookmarked: false, @@ -244,6 +243,15 @@ const normalizeDislikes = (status: ImmutableMap) => { return status; }; +const parseAccount = (status: ImmutableMap) => { + try { + const account = accountSchema.parse(status.get('account').toJS()); + return status.set('account', account); + } catch (_e) { + return status.set('account', null); + } +}; + export const normalizeStatus = (status: Record) => { return StatusRecord( ImmutableMap(fromJS(status)).withMutations(status => { @@ -261,6 +269,7 @@ export const normalizeStatus = (status: Record) => { normalizeFilterResults(status); normalizeDislikes(status); normalizeTombstone(status); + parseAccount(status); }), ); }; diff --git a/app/soapbox/pages/profile-page.tsx b/app/soapbox/pages/profile-page.tsx index e83bfa7cd..9f5d27487 100644 --- a/app/soapbox/pages/profile-page.tsx +++ b/app/soapbox/pages/profile-page.tsx @@ -71,7 +71,7 @@ const ProfilePage: React.FC = ({ params, children }) => { if (account) { const ownAccount = account.id === me; - if (ownAccount || !account.pleroma.get('hide_favorites', true)) { + if (ownAccount || account.pleroma?.hide_favorites !== true) { tabItems.push({ text: , to: `/@${account.acct}/favorites`, @@ -129,7 +129,7 @@ const ProfilePage: React.FC = ({ params, children }) => { {Component => } - {account && !account.fields.isEmpty() && ( + {account && !account.fields.length && ( {Component => } diff --git a/app/soapbox/queries/__tests__/chats.test.ts b/app/soapbox/queries/__tests__/chats.test.ts index 3bcd1b9a7..7bcffa90f 100644 --- a/app/soapbox/queries/__tests__/chats.test.ts +++ b/app/soapbox/queries/__tests__/chats.test.ts @@ -3,19 +3,18 @@ import sumBy from 'lodash/sumBy'; import { useEffect } from 'react'; import { __stub } from 'soapbox/api'; -import { buildRelationship } from 'soapbox/jest/factory'; +import { buildAccount, buildRelationship } from 'soapbox/jest/factory'; import { createTestStore, mockStore, queryClient, renderHook, rootState, waitFor } from 'soapbox/jest/test-helpers'; import { normalizeChatMessage } from 'soapbox/normalizers'; import { Store } from 'soapbox/store'; import { ChatMessage } from 'soapbox/types/entities'; import { flattenPages } from 'soapbox/utils/queries'; -import { IAccount } from '../accounts'; import { ChatKeys, IChat, isLastMessage, useChat, useChatActions, useChatMessages, useChats } from '../chats'; const chat: IChat = { accepted: true, - account: { + account: buildAccount({ username: 'username', verified: true, id: '1', @@ -23,7 +22,7 @@ const chat: IChat = { avatar: 'avatar', avatar_static: 'avatar', display_name: 'my name', - } as IAccount, + }), chat_type: 'direct', created_at: '2020-06-10T02:05:06.000Z', created_by_account: '1', diff --git a/app/soapbox/queries/accounts.ts b/app/soapbox/queries/accounts.ts index 20ec74188..f34d4c72d 100644 --- a/app/soapbox/queries/accounts.ts +++ b/app/soapbox/queries/accounts.ts @@ -41,8 +41,8 @@ const useUpdateCredentials = () => { return useMutation((data: UpdateCredentialsData) => api.patch('/api/v1/accounts/update_credentials', data), { onMutate(variables) { - const cachedAccount = account?.toJS(); - dispatch(patchMeSuccess({ ...cachedAccount, ...variables })); + const cachedAccount = account; + dispatch(patchMeSuccess({ ...account, ...variables })); return { cachedAccount }; }, diff --git a/app/soapbox/queries/chats.ts b/app/soapbox/queries/chats.ts index 7840c3dbf..8256a0dc8 100644 --- a/app/soapbox/queries/chats.ts +++ b/app/soapbox/queries/chats.ts @@ -15,7 +15,7 @@ import { flattenPages, PaginatedResult, updatePageItem } from 'soapbox/utils/que import { queryClient } from './client'; import { useFetchRelationships } from './relationships'; -import type { IAccount } from './accounts'; +import type { Account } from 'soapbox/schemas'; export const messageExpirationOptions = [604800, 1209600, 2592000, 7776000]; @@ -28,7 +28,7 @@ export enum MessageExpirationValues { export interface IChat { accepted: boolean - account: IAccount + account: Account chat_type: 'channel' | 'direct' created_at: string created_by_account: string diff --git a/app/soapbox/reducers/auth.ts b/app/soapbox/reducers/auth.ts index 85b8159b4..82f02ac80 100644 --- a/app/soapbox/reducers/auth.ts +++ b/app/soapbox/reducers/auth.ts @@ -272,8 +272,8 @@ const deleteToken = (state: State, token: string) => { }); }; -const deleteUser = (state: State, account: AccountEntity) => { - const accountUrl = account.get('url'); +const deleteUser = (state: State, account: Pick) => { + const accountUrl = account.url; return state.withMutations(state => { state.update('users', users => users.delete(accountUrl)); diff --git a/app/soapbox/reducers/chats.ts b/app/soapbox/reducers/chats.ts index 33c5b995b..5afdf12a3 100644 --- a/app/soapbox/reducers/chats.ts +++ b/app/soapbox/reducers/chats.ts @@ -20,7 +20,6 @@ type ChatRecord = ReturnType; type APIEntities = Array; export interface ReducerChat extends ChatRecord { - account: string | null last_message: string | null } @@ -34,7 +33,6 @@ type State = ReturnType; const minifyChat = (chat: ChatRecord): ReducerChat => { return chat.mergeWith((o, n) => n || o, { - account: normalizeId(chat.getIn(['account', 'id'])), last_message: normalizeId(chat.getIn(['last_message', 'id'])), }) as ReducerChat; }; diff --git a/app/soapbox/reducers/compose.ts b/app/soapbox/reducers/compose.ts index ef1571954..e2be226fe 100644 --- a/app/soapbox/reducers/compose.ts +++ b/app/soapbox/reducers/compose.ts @@ -126,9 +126,9 @@ export const statusToMentionsArray = (status: ImmutableMap, account const author = status.getIn(['account', 'acct']) as string; const mentions = status.get('mentions')?.map((m: ImmutableMap) => m.get('acct')) || []; - return ImmutableOrderedSet([author]) + return ImmutableOrderedSet([author]) .concat(mentions) - .delete(account.get('acct')) as ImmutableOrderedSet; + .delete(account.acct) as ImmutableOrderedSet; }; export const statusToMentionsAccountIdsArray = (status: StatusEntity, account: AccountEntity) => { diff --git a/app/soapbox/reducers/contexts.ts b/app/soapbox/reducers/contexts.ts index ee55586a4..b62b11365 100644 --- a/app/soapbox/reducers/contexts.ts +++ b/app/soapbox/reducers/contexts.ts @@ -17,8 +17,8 @@ import { } from '../actions/statuses'; import { TIMELINE_DELETE } from '../actions/timelines'; -import type { ReducerStatus } from './statuses'; import type { AnyAction } from 'redux'; +import type { Status } from 'soapbox/schemas'; export const ReducerRecord = ImmutableRecord({ inReplyTos: ImmutableMap(), @@ -163,10 +163,10 @@ const filterContexts = ( state: State, relationship: { id: string }, /** The entire statuses map from the store. */ - statuses: ImmutableMap, + statuses: ImmutableMap, ): State => { const ownedStatusIds = statuses - .filter(status => status.account === relationship.id) + .filter(status => status.account.id === relationship.id) .map(status => status.id) .toList() .toArray(); diff --git a/app/soapbox/reducers/relationships.ts b/app/soapbox/reducers/relationships.ts index 40d062f78..b75d63cbd 100644 --- a/app/soapbox/reducers/relationships.ts +++ b/app/soapbox/reducers/relationships.ts @@ -1,4 +1,4 @@ -import { List as ImmutableList, Map as ImmutableMap } from 'immutable'; +import { Map as ImmutableMap } from 'immutable'; import get from 'lodash/get'; import { STREAMING_FOLLOW_RELATIONSHIPS_UPDATE } from 'soapbox/actions/streaming'; @@ -50,7 +50,7 @@ const normalizeRelationships = (state: State, relationships: APIEntities) => { return state; }; -const setDomainBlocking = (state: State, accounts: ImmutableList, blocking: boolean) => { +const setDomainBlocking = (state: State, accounts: string[], blocking: boolean) => { return state.withMutations(map => { accounts.forEach(id => { map.setIn([id, 'domain_blocking'], blocking); diff --git a/app/soapbox/reducers/statuses.ts b/app/soapbox/reducers/statuses.ts index d8c91b6a3..62b0ad01b 100644 --- a/app/soapbox/reducers/statuses.ts +++ b/app/soapbox/reducers/statuses.ts @@ -56,7 +56,6 @@ type APIEntities = Array; type State = ImmutableMap; export interface ReducerStatus extends StatusRecord { - account: string | null reblog: string | null poll: string | null quote: string | null @@ -65,7 +64,6 @@ export interface ReducerStatus extends StatusRecord { const minifyStatus = (status: StatusRecord): ReducerStatus => { return status.mergeWith((o, n) => n || o, { - account: normalizeId(status.getIn(['account', 'id'])), reblog: normalizeId(status.getIn(['reblog', 'id'])), poll: normalizeId(status.getIn(['poll', 'id'])), quote: normalizeId(status.getIn(['quote', 'id'])), diff --git a/app/soapbox/reducers/suggestions.ts b/app/soapbox/reducers/suggestions.ts index e3d72a34c..81d0d9a2f 100644 --- a/app/soapbox/reducers/suggestions.ts +++ b/app/soapbox/reducers/suggestions.ts @@ -68,7 +68,7 @@ const dismissAccount = (state: State, accountId: string) => { return state.update('items', items => items.filterNot(item => item.account === accountId)); }; -const dismissAccounts = (state: State, accountIds: Array) => { +const dismissAccounts = (state: State, accountIds: string[]) => { return state.update('items', items => items.filterNot(item => accountIds.includes(item.account))); }; diff --git a/app/soapbox/reducers/timelines.ts b/app/soapbox/reducers/timelines.ts index b5fe0e049..7f2acba91 100644 --- a/app/soapbox/reducers/timelines.ts +++ b/app/soapbox/reducers/timelines.ts @@ -215,7 +215,7 @@ const filterTimelines = (state: State, relationship: APIEntity, statuses: Immuta statuses.forEach(status => { if (status.get('account') !== relationship.id) return; const references = buildReferencesTo(statuses, status); - deleteStatus(state, status.get('id'), status.get('account') as string, references, relationship.id); + deleteStatus(state, status.id, status.account!.id, references, relationship.id); }); }); }; diff --git a/app/soapbox/schemas/account.ts b/app/soapbox/schemas/account.ts index 6c41bebc4..c588070b1 100644 --- a/app/soapbox/schemas/account.ts +++ b/app/soapbox/schemas/account.ts @@ -5,6 +5,7 @@ import emojify from 'soapbox/features/emoji'; import { unescapeHTML } from 'soapbox/utils/html'; import { customEmojiSchema } from './custom-emoji'; +import { relationshipSchema } from './relationship'; import { contentSchema, filteredArray, makeCustomEmojiMap } from './utils'; import type { Resolve } from 'soapbox/utils/types'; @@ -54,6 +55,8 @@ const baseAccountSchema = z.object({ pleroma: z.object({ accepts_chat_messages: z.boolean().catch(false), accepts_email_list: z.boolean().catch(false), + also_known_as: z.array(z.string().url()).catch([]), + ap_id: z.string().url().optional().catch(undefined), birthday: birthdaySchema.nullish().catch(undefined), deactivated: z.boolean().catch(false), favicon: z.string().url().optional().catch(undefined), @@ -69,6 +72,7 @@ const baseAccountSchema = z.object({ notification_settings: z.object({ block_from_strangers: z.boolean().catch(false), }).optional().catch(undefined), + relationship: relationshipSchema.optional().catch(undefined), tags: z.array(z.string()).catch([]), }).optional().catch(undefined), source: z.object({ @@ -133,7 +137,7 @@ const transformAccount = ({ pleroma, other_setti location: account.location || pleroma?.location || other_settings?.location || '', note_emojified: emojify(account.note, customEmojiMap), pleroma, - relationship: undefined, + relationship: relationshipSchema.parse({ id: account.id, ...pleroma?.relationship }), staff: pleroma?.is_admin || pleroma?.is_moderator || false, suspended: account.suspended || pleroma?.deactivated || false, verified: account.verified || pleroma?.tags.includes('verified') || false, diff --git a/app/soapbox/selectors/index.ts b/app/soapbox/selectors/index.ts index 0caa3e935..45aa5ef66 100644 --- a/app/soapbox/selectors/index.ts +++ b/app/soapbox/selectors/index.ts @@ -394,7 +394,7 @@ export const makeGetStatusIds = () => createSelector([ (state: RootState, { type, prefix }: ColumnQuery) => getSettings(state).get(prefix || type, ImmutableMap()), (state: RootState, { type }: ColumnQuery) => state.timelines.get(type)?.items || ImmutableOrderedSet(), (state: RootState) => state.statuses, -], (columnSettings, statusIds: ImmutableOrderedSet, statuses) => { +], (columnSettings: any, statusIds: ImmutableOrderedSet, statuses) => { return statusIds.filter((id: string) => { const status = statuses.get(id); if (!status) return true; diff --git a/app/soapbox/types/entities.ts b/app/soapbox/types/entities.ts index 712a89e23..4b7823e9e 100644 --- a/app/soapbox/types/entities.ts +++ b/app/soapbox/types/entities.ts @@ -1,7 +1,6 @@ import { AdminAccountRecord, AdminReportRecord, - AccountRecord, AnnouncementRecord, AnnouncementReactionRecord, AttachmentRecord, @@ -23,8 +22,10 @@ import { TagRecord, } from 'soapbox/normalizers'; import { LogEntryRecord } from 'soapbox/reducers/admin-log'; +import { Account as SchemaAccount } from 'soapbox/schemas'; import type { Record as ImmutableRecord } from 'immutable'; +import type { LegacyMap } from 'soapbox/utils/legacy'; type AdminAccount = ReturnType; type AdminLog = ReturnType; @@ -48,11 +49,7 @@ type Notification = ReturnType; type StatusEdit = ReturnType; type Tag = ReturnType; -interface Account extends ReturnType { - // HACK: we can't do a circular reference in the Record definition itself, - // so do it here. - moved: EmbeddedEntity -} +type Account = SchemaAccount & LegacyMap; interface Status extends ReturnType { // HACK: same as above @@ -65,10 +62,10 @@ type APIEntity = Record; type EmbeddedEntity = null | string | ReturnType>; export { + Account, AdminAccount, AdminLog, AdminReport, - Account, Announcement, AnnouncementReaction, Attachment, diff --git a/app/soapbox/utils/__tests__/badges.test.ts b/app/soapbox/utils/__tests__/badges.test.ts index 1f3349fcc..fdddeea9f 100644 --- a/app/soapbox/utils/__tests__/badges.test.ts +++ b/app/soapbox/utils/__tests__/badges.test.ts @@ -1,4 +1,4 @@ -import { normalizeAccount } from 'soapbox/normalizers'; +import { buildAccount } from 'soapbox/jest/factory'; import { tagToBadge, @@ -8,8 +8,6 @@ import { getBadges, } from '../badges'; -import type { Account } from 'soapbox/types/entities'; - test('tagToBadge', () => { expect(tagToBadge('yolo')).toEqual('badge:yolo'); }); @@ -38,6 +36,6 @@ test('getTagDiff', () => { }); test('getBadges', () => { - const account = normalizeAccount({ id: '1', pleroma: { tags: ['a', 'b', 'badge:c'] } }) as Account; + const account = buildAccount({ id: '1', pleroma: { tags: ['a', 'b', 'badge:c'] } }); expect(getBadges(account)).toEqual(['badge:c']); }); \ No newline at end of file diff --git a/app/soapbox/utils/__tests__/chats.test.ts b/app/soapbox/utils/__tests__/chats.test.ts index d1e4ce7f6..b98fb47f6 100644 --- a/app/soapbox/utils/__tests__/chats.test.ts +++ b/app/soapbox/utils/__tests__/chats.test.ts @@ -1,5 +1,5 @@ +import { buildAccount } from 'soapbox/jest/factory'; import { normalizeChatMessage } from 'soapbox/normalizers'; -import { IAccount } from 'soapbox/queries/accounts'; import { ChatKeys, IChat } from 'soapbox/queries/chats'; import { queryClient } from 'soapbox/queries/client'; @@ -7,7 +7,7 @@ import { updateChatMessage } from '../chats'; const chat: IChat = { accepted: true, - account: { + account: buildAccount({ username: 'username', verified: true, id: '1', @@ -15,7 +15,7 @@ const chat: IChat = { avatar: 'avatar', avatar_static: 'avatar', display_name: 'my name', - } as IAccount, + }), chat_type: 'direct', created_at: '2020-06-10T02:05:06.000Z', created_by_account: '1', diff --git a/app/soapbox/utils/__tests__/status.test.ts b/app/soapbox/utils/__tests__/status.test.ts index 2bc803ee5..a3018114b 100644 --- a/app/soapbox/utils/__tests__/status.test.ts +++ b/app/soapbox/utils/__tests__/status.test.ts @@ -1,16 +1,14 @@ -import { normalizeStatus } from 'soapbox/normalizers/status'; +import { buildStatus } from 'soapbox/jest/factory'; import { hasIntegerMediaIds, defaultMediaVisibility, } from '../status'; -import type { ReducerStatus } from 'soapbox/reducers/statuses'; - describe('hasIntegerMediaIds()', () => { it('returns true for a Pleroma deleted status', () => { - const status = normalizeStatus(require('soapbox/__fixtures__/pleroma-status-deleted.json')) as ReducerStatus; + const status = buildStatus(require('soapbox/__fixtures__/pleroma-status-deleted.json')); expect(hasIntegerMediaIds(status)).toBe(true); }); }); @@ -21,17 +19,17 @@ describe('defaultMediaVisibility()', () => { }); it('hides sensitive media by default', () => { - const status = normalizeStatus({ sensitive: true }) as ReducerStatus; + const status = buildStatus({ sensitive: true }); expect(defaultMediaVisibility(status, 'default')).toBe(false); }); it('hides media when displayMedia is hide_all', () => { - const status = normalizeStatus({}) as ReducerStatus; + const status = buildStatus({}); expect(defaultMediaVisibility(status, 'hide_all')).toBe(false); }); it('shows sensitive media when displayMedia is show_all', () => { - const status = normalizeStatus({ sensitive: true }) as ReducerStatus; + const status = buildStatus({ sensitive: true }); expect(defaultMediaVisibility(status, 'show_all')).toBe(true); }); }); diff --git a/app/soapbox/utils/__tests__/timelines.test.ts b/app/soapbox/utils/__tests__/timelines.test.ts index 852a76ef6..a6ed65282 100644 --- a/app/soapbox/utils/__tests__/timelines.test.ts +++ b/app/soapbox/utils/__tests__/timelines.test.ts @@ -1,75 +1,73 @@ import { fromJS } from 'immutable'; -import { normalizeStatus } from 'soapbox/normalizers/status'; +import { buildStatus } from 'soapbox/jest/factory'; import { shouldFilter } from '../timelines'; -import type { ReducerStatus } from 'soapbox/reducers/statuses'; - describe('shouldFilter', () => { it('returns false under normal circumstances', () => { const columnSettings = fromJS({}); - const status = normalizeStatus({}) as ReducerStatus; + const status = buildStatus({}); expect(shouldFilter(status, columnSettings)).toBe(false); }); it('reblog: returns true when `shows.reblog == false`', () => { const columnSettings = fromJS({ shows: { reblog: false } }); - const status = normalizeStatus({ reblog: {} }) as ReducerStatus; + const status = buildStatus({ reblog: {} }); expect(shouldFilter(status, columnSettings)).toBe(true); }); it('reblog: returns false when `shows.reblog == true`', () => { const columnSettings = fromJS({ shows: { reblog: true } }); - const status = normalizeStatus({ reblog: {} }) as ReducerStatus; + const status = buildStatus({ reblog: {} }); expect(shouldFilter(status, columnSettings)).toBe(false); }); it('reply: returns true when `shows.reply == false`', () => { const columnSettings = fromJS({ shows: { reply: false } }); - const status = normalizeStatus({ in_reply_to_id: '1234' }) as ReducerStatus; + const status = buildStatus({ in_reply_to_id: '1234' }); expect(shouldFilter(status, columnSettings)).toBe(true); }); it('reply: returns false when `shows.reply == true`', () => { const columnSettings = fromJS({ shows: { reply: true } }); - const status = normalizeStatus({ in_reply_to_id: '1234' }) as ReducerStatus; + const status = buildStatus({ in_reply_to_id: '1234' }); expect(shouldFilter(status, columnSettings)).toBe(false); }); it('direct: returns true when `shows.direct == false`', () => { const columnSettings = fromJS({ shows: { direct: false } }); - const status = normalizeStatus({ visibility: 'direct' }) as ReducerStatus; + const status = buildStatus({ visibility: 'direct' }); expect(shouldFilter(status, columnSettings)).toBe(true); }); it('direct: returns false when `shows.direct == true`', () => { const columnSettings = fromJS({ shows: { direct: true } }); - const status = normalizeStatus({ visibility: 'direct' }) as ReducerStatus; + const status = buildStatus({ visibility: 'direct' }); expect(shouldFilter(status, columnSettings)).toBe(false); }); it('direct: returns false for a public post when `shows.direct == false`', () => { const columnSettings = fromJS({ shows: { direct: false } }); - const status = normalizeStatus({ visibility: 'public' }) as ReducerStatus; + const status = buildStatus({ visibility: 'public' }); expect(shouldFilter(status, columnSettings)).toBe(false); }); it('multiple settings', () => { const columnSettings = fromJS({ shows: { reblog: false, reply: false, direct: false } }); - const status = normalizeStatus({ reblog: null, in_reply_to_id: null, visibility: 'direct' }) as ReducerStatus; + const status = buildStatus({ reblog: null, in_reply_to_id: null, visibility: 'direct' }); expect(shouldFilter(status, columnSettings)).toBe(true); }); it('multiple settings', () => { const columnSettings = fromJS({ shows: { reblog: false, reply: true, direct: false } }); - const status = normalizeStatus({ reblog: null, in_reply_to_id: '1234', visibility: 'public' }) as ReducerStatus; + const status = buildStatus({ reblog: null, in_reply_to_id: '1234', visibility: 'public' }); expect(shouldFilter(status, columnSettings)).toBe(false); }); it('multiple settings', () => { const columnSettings = fromJS({ shows: { reblog: true, reply: false, direct: true } }); - const status = normalizeStatus({ reblog: {}, in_reply_to_id: '1234', visibility: 'direct' }) as ReducerStatus; + const status = buildStatus({ reblog: {}, in_reply_to_id: '1234', visibility: 'direct' }); expect(shouldFilter(status, columnSettings)).toBe(true); }); }); diff --git a/app/soapbox/utils/badges.ts b/app/soapbox/utils/badges.ts index dbc6b997b..8920f89fe 100644 --- a/app/soapbox/utils/badges.ts +++ b/app/soapbox/utils/badges.ts @@ -33,8 +33,8 @@ const filterBadges = (tags: string[]): string[] => { }; /** Get badges from an account. */ -const getBadges = (account: Account) => { - const tags = Array.from(account?.getIn(['pleroma', 'tags']) as Iterable || []); +const getBadges = (account: Pick) => { + const tags = account?.pleroma?.tags ?? []; return filterBadges(tags); }; diff --git a/app/soapbox/utils/status.ts b/app/soapbox/utils/status.ts index 09593facc..8d99752cd 100644 --- a/app/soapbox/utils/status.ts +++ b/app/soapbox/utils/status.ts @@ -1,18 +1,15 @@ import { isIntegerId } from 'soapbox/utils/numbers'; import type { IntlShape } from 'react-intl'; -import type { Status } from 'soapbox/types/entities'; +import type { Status } from 'soapbox/schemas'; /** Get the initial visibility of media attachments from user settings. */ -export const defaultMediaVisibility = ( - status: Pick | undefined | null, +export const defaultMediaVisibility = >( + status: T | undefined | null, displayMedia: string, ): boolean => { if (!status) return false; - - if (status.reblog && typeof status.reblog === 'object') { - status = status.reblog; - } + status = getActualStatus(status); const isUnderReview = status.visibility === 'self'; @@ -73,14 +70,9 @@ export const textForScreenReader = ( }; /** Get reblogged status if any, otherwise return the original status. */ -// @ts-ignore The type seems right, but TS doesn't like it. -export const getActualStatus: { - >(status: T): T - (status: undefined): undefined - (status: null): null -} = >(status: T | null | undefined) => { +export const getActualStatus = (status: T): T => { if (status?.reblog && typeof status?.reblog === 'object') { - return status.reblog as Status; + return status.reblog; } else { return status; } diff --git a/app/soapbox/utils/timelines.ts b/app/soapbox/utils/timelines.ts index 03ba96044..b6052cbcc 100644 --- a/app/soapbox/utils/timelines.ts +++ b/app/soapbox/utils/timelines.ts @@ -1,8 +1,11 @@ -import { Map as ImmutableMap } from 'immutable'; +import { Map as ImmutableMap, type Collection } from 'immutable'; -import type { Status as StatusEntity } from 'soapbox/types/entities'; +import type { Status } from 'soapbox/schemas'; -export const shouldFilter = (status: StatusEntity, columnSettings: any) => { +export const shouldFilter = ( + status: Pick & { reblog: unknown }, + columnSettings: Collection, +) => { const shows = ImmutableMap({ reblog: status.reblog !== null, reply: status.in_reply_to_id !== null, diff --git a/package.json b/package.json index 4abab6c2d..6e5bd9aea 100644 --- a/package.json +++ b/package.json @@ -181,6 +181,7 @@ "ts-node": "^10.9.1", "tslib": "^2.3.1", "twemoji": "https://github.com/twitter/twemoji#v14.0.2", + "type-fest": "^3.12.0", "typescript": "^5.1.3", "util": "^0.12.4", "uuid": "^9.0.0", diff --git a/yarn.lock b/yarn.lock index 9458d859a..7c5985dee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17111,6 +17111,11 @@ type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-3.12.0.tgz#4ce26edc1ccc59fc171e495887ef391fe1f5280e" + integrity sha512-qj9wWsnFvVEMUDbESiilKeXeHL7FwwiFcogfhfyjmvT968RXSvnl23f1JOClTHYItsi7o501C/7qVllscUP3oA== + type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131"