diff --git a/app/soapbox/actions/compose.ts b/app/soapbox/actions/compose.ts index 5ebb229a99..a6b6dcd134 100644 --- a/app/soapbox/actions/compose.ts +++ b/app/soapbox/actions/compose.ts @@ -1,5 +1,4 @@ import axios, { AxiosError, Canceler } from 'axios'; -import { List as ImmutableList, Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable'; import throttle from 'lodash/throttle'; import { defineMessages, IntlShape } from 'react-intl'; @@ -100,7 +99,7 @@ const messages = defineMessages({ const COMPOSE_PANEL_BREAKPOINT = 600 + (285 * 1) + (10 * 1); const ensureComposeIsVisible = (getState: () => RootState, routerHistory: History) => { - if (!getState().compose.get('mounted') && window.innerWidth < COMPOSE_PANEL_BREAKPOINT) { + if (!getState().compose.mounted && window.innerWidth < COMPOSE_PANEL_BREAKPOINT) { routerHistory.push('/posts/new'); } }; @@ -212,16 +211,16 @@ const handleComposeSubmit = (dispatch: AppDispatch, getState: () => RootState, d }; const needsDescriptions = (state: RootState) => { - const media = state.compose.get('media_attachments') as ImmutableList>; + const media = state.compose.media_attachments; const missingDescriptionModal = getSettings(state).get('missingDescriptionModal'); - const hasMissing = media.filter(item => !item.get('description')).size > 0; + const hasMissing = media.filter(item => !item.description).size > 0; return missingDescriptionModal && hasMissing; }; const validateSchedule = (state: RootState) => { - const schedule = state.compose.get('schedule'); + const schedule = state.compose.schedule; if (!schedule) return true; const fiveMinutesFromNow = new Date(new Date().getTime() + 300000); @@ -234,10 +233,10 @@ const submitCompose = (routerHistory: History, force = false) => if (!isLoggedIn(getState)) return; const state = getState(); - const status = state.compose.get('text') || ''; - const media = state.compose.get('media_attachments') as ImmutableList>; - const statusId = state.compose.get('id') || null; - let to = state.compose.get('to') || ImmutableOrderedSet(); + const status = state.compose.text; + const media = state.compose.media_attachments; + const statusId = state.compose.id; + let to = state.compose.to; if (!validateSchedule(state)) { dispatch(snackbar.error(messages.scheduleError)); @@ -259,7 +258,7 @@ const submitCompose = (routerHistory: History, force = false) => } if (to && status) { - const mentions: string[] = status.match(/(?:^|\s|\.)@([a-z0-9_]+(?:@[a-z0-9\.\-]+)?)/gi); // not a perfect regex + const mentions: string[] | null = status.match(/(?:^|\s|\.)@([a-z0-9_]+(?:@[a-z0-9\.\-]+)?)/gi); // not a perfect regex if (mentions) to = to.union(mentions.map(mention => mention.trim().slice(1))); @@ -268,19 +267,19 @@ const submitCompose = (routerHistory: History, force = false) => dispatch(submitComposeRequest()); dispatch(closeModal()); - const idempotencyKey = state.compose.get('idempotencyKey'); + const idempotencyKey = state.compose.idempotencyKey; const params = { status, - in_reply_to_id: state.compose.get('in_reply_to') || null, - quote_id: state.compose.get('quote') || null, - media_ids: media.map(item => item.get('id')), - sensitive: state.compose.get('sensitive'), - spoiler_text: state.compose.get('spoiler_text') || '', - visibility: state.compose.get('privacy'), - content_type: state.compose.get('content_type'), - poll: state.compose.get('poll') || null, - scheduled_at: state.compose.get('schedule') || null, + in_reply_to_id: state.compose.in_reply_to, + quote_id: state.compose.quote, + media_ids: media.map(item => item.id), + sensitive: state.compose.sensitive, + spoiler_text: state.compose.spoiler_text, + visibility: state.compose.privacy, + content_type: state.compose.content_type, + poll: state.compose.poll, + scheduled_at: state.compose.schedule, to, }; @@ -315,7 +314,7 @@ const uploadCompose = (files: FileList, intl: IntlShape) => const maxImageSize = getState().instance.configuration.getIn(['media_attachments', 'image_size_limit']) as number | undefined; const maxVideoSize = getState().instance.configuration.getIn(['media_attachments', 'video_size_limit']) as number | undefined; - const media = getState().compose.get('media_attachments'); + const media = getState().compose.media_attachments; const progress = new Array(files.length).fill(0); let total = Array.from(files).reduce((a, v) => a + v.size, 0); @@ -550,7 +549,7 @@ const updateTagHistory = (tags: string[]) => ({ const insertIntoTagHistory = (recognizedTags: APIEntity[], text: string) => (dispatch: AppDispatch, getState: () => RootState) => { const state = getState(); - const oldHistory = state.compose.get('tagHistory') as ImmutableList; + const oldHistory = state.compose.tagHistory; const me = state.me; const names = recognizedTags .filter(tag => text.match(new RegExp(`#${tag.name}`, 'i'))) diff --git a/app/soapbox/actions/me.ts b/app/soapbox/actions/me.ts index acd845f8bf..4cb4b2350a 100644 --- a/app/soapbox/actions/me.ts +++ b/app/soapbox/actions/me.ts @@ -25,7 +25,7 @@ const getMeId = (state: RootState) => state.me || getAuthUserId(state); const getMeUrl = (state: RootState) => { const accountId = getMeId(state); - return state.accounts.get(accountId)!.url || getAuthUserUrl(state); + return state.accounts.get(accountId)?.url || getAuthUserUrl(state); }; const getMeToken = (state: RootState) => { diff --git a/app/soapbox/actions/statuses.ts b/app/soapbox/actions/statuses.ts index cf9306b678..f0a6aad150 100644 --- a/app/soapbox/actions/statuses.ts +++ b/app/soapbox/actions/statuses.ts @@ -47,7 +47,7 @@ const statusExists = (getState: () => RootState, statusId: string) => { return (getState().statuses.get(statusId) || null) !== null; }; -const createStatus = (params: Record, idempotencyKey: string, statusId: string) => { +const createStatus = (params: Record, idempotencyKey: string, statusId: string | null) => { return (dispatch: AppDispatch, getState: () => RootState) => { dispatch({ type: STATUS_CREATE_REQUEST, params, idempotencyKey }); diff --git a/app/soapbox/components/modal_root.js b/app/soapbox/components/modal_root.js index 5acedae297..842b66ba6f 100644 Binary files a/app/soapbox/components/modal_root.js and b/app/soapbox/components/modal_root.js differ diff --git a/app/soapbox/features/backups/index.tsx b/app/soapbox/features/backups/index.tsx index c78ceb2987..0efc4e15ee 100644 --- a/app/soapbox/features/backups/index.tsx +++ b/app/soapbox/features/backups/index.tsx @@ -22,7 +22,7 @@ const Backups = () => { const intl = useIntl(); const dispatch = useAppDispatch(); - const backups = useAppSelector>>((state) => state.backups.toList().sortBy((backup: ImmutableMap) => backup.get('inserted_at'))); + const backups = useAppSelector((state) => state.backups.toList().sortBy((backup) => backup.inserted_at)); const [isLoading, setIsLoading] = useState(true); @@ -63,12 +63,12 @@ const Backups = () => { > {backups.map((backup) => (
- {backup.get('processed') - ? {backup.get('inserted_at')} - :
{intl.formatMessage(messages.pending)}: {backup.get('inserted_at')}
+ {backup.processed + ? {backup.inserted_at} + :
{intl.formatMessage(messages.pending)}: {backup.inserted_at}
}
))} diff --git a/app/soapbox/features/compose/components/polls/poll-form.tsx b/app/soapbox/features/compose/components/polls/poll-form.tsx index cfcefc8222..4f427dfcd4 100644 --- a/app/soapbox/features/compose/components/polls/poll-form.tsx +++ b/app/soapbox/features/compose/components/polls/poll-form.tsx @@ -49,7 +49,7 @@ const Option = (props: IOption) => { const dispatch = useAppDispatch(); const intl = useIntl(); - const suggestions = useAppSelector((state) => state.compose.get('suggestions')); + const suggestions = useAppSelector((state) => state.compose.suggestions); const handleOptionTitleChange = (event: React.ChangeEvent) => onChange(index, event.target.value); @@ -107,9 +107,9 @@ const PollForm = () => { const intl = useIntl(); const pollLimits = useAppSelector((state) => state.instance.getIn(['configuration', 'polls']) as any); - const options = useAppSelector((state) => state.compose.getIn(['poll', 'options'])); - const expiresIn = useAppSelector((state) => state.compose.getIn(['poll', 'expires_in'])); - const isMultiple = useAppSelector((state) => state.compose.getIn(['poll', 'multiple'])); + const options = useAppSelector((state) => state.compose.poll?.options); + const expiresIn = useAppSelector((state) => state.compose.poll?.expires_in); + const isMultiple = useAppSelector((state) => state.compose.poll?.multiple); const maxOptions = pollLimits.get('max_options'); const maxOptionChars = pollLimits.get('max_characters_per_option'); diff --git a/app/soapbox/features/compose/components/reply_mentions.tsx b/app/soapbox/features/compose/components/reply_mentions.tsx index c25c2f3df1..8a7be612f7 100644 --- a/app/soapbox/features/compose/components/reply_mentions.tsx +++ b/app/soapbox/features/compose/components/reply_mentions.tsx @@ -13,9 +13,9 @@ import type { Status as StatusEntity } from 'soapbox/types/entities'; const ReplyMentions: React.FC = () => { const dispatch = useDispatch(); const instance = useAppSelector((state) => state.instance); - const status = useAppSelector(state => makeGetStatus()(state, { id: state.compose.get('in_reply_to') })); + const status = useAppSelector(state => makeGetStatus()(state, { id: state.compose.in_reply_to! })); - const to = useAppSelector((state) => state.compose.get('to')); + const to = useAppSelector((state) => state.compose.to); const account = useAppSelector((state) => state.accounts.get(state.me)); const { explicitAddressing } = getFeatures(instance); @@ -24,7 +24,7 @@ const ReplyMentions: React.FC = () => { return null; } - const parentTo = status && statusToMentionsAccountIdsArray(status, account); + const parentTo = status && statusToMentionsAccountIdsArray(status, account!); const handleClick = (e: React.MouseEvent) => { e.preventDefault(); diff --git a/app/soapbox/features/compose/components/schedule_form.tsx b/app/soapbox/features/compose/components/schedule_form.tsx index 6467ba51ef..5352b15eee 100644 --- a/app/soapbox/features/compose/components/schedule_form.tsx +++ b/app/soapbox/features/compose/components/schedule_form.tsx @@ -31,7 +31,7 @@ const ScheduleForm: React.FC = () => { const dispatch = useAppDispatch(); const intl = useIntl(); - const scheduledAt = useAppSelector((state) => state.compose.get('schedule')); + const scheduledAt = useAppSelector((state) => state.compose.schedule); const active = !!scheduledAt; const onSchedule = (date: Date) => { diff --git a/app/soapbox/features/compose/components/sensitive-button.tsx b/app/soapbox/features/compose/components/sensitive-button.tsx index e1c7b48bad..fbf415e077 100644 --- a/app/soapbox/features/compose/components/sensitive-button.tsx +++ b/app/soapbox/features/compose/components/sensitive-button.tsx @@ -15,8 +15,8 @@ const SensitiveButton: React.FC = () => { const intl = useIntl(); const dispatch = useAppDispatch(); - const active = useAppSelector(state => state.compose.get('sensitive') === true); - const disabled = useAppSelector(state => state.compose.get('spoiler') === true); + const active = useAppSelector(state => state.compose.sensitive === true); + const disabled = useAppSelector(state => state.compose.spoiler === true); const onClick = () => { dispatch(changeComposeSensitivity()); diff --git a/app/soapbox/features/compose/components/upload-progress.tsx b/app/soapbox/features/compose/components/upload-progress.tsx index 083cbf6750..6b6fd93030 100644 --- a/app/soapbox/features/compose/components/upload-progress.tsx +++ b/app/soapbox/features/compose/components/upload-progress.tsx @@ -5,8 +5,8 @@ import { useAppSelector } from 'soapbox/hooks'; /** File upload progress bar for post composer. */ const ComposeUploadProgress = () => { - const active = useAppSelector((state) => state.compose.get('is_uploading')); - const progress = useAppSelector((state) => state.compose.get('progress')); + const active = useAppSelector((state) => state.compose.is_uploading); + const progress = useAppSelector((state) => state.compose.progress); if (!active) { return null; diff --git a/app/soapbox/features/compose/components/upload_form.tsx b/app/soapbox/features/compose/components/upload_form.tsx index ae44d25619..b177e16957 100644 --- a/app/soapbox/features/compose/components/upload_form.tsx +++ b/app/soapbox/features/compose/components/upload_form.tsx @@ -10,7 +10,7 @@ import UploadContainer from '../containers/upload_container'; import type { Attachment as AttachmentEntity } from 'soapbox/types/entities'; const UploadForm = () => { - const mediaIds = useAppSelector((state) => state.compose.get('media_attachments').map((item: AttachmentEntity) => item.get('id'))); + const mediaIds = useAppSelector((state) => state.compose.media_attachments.map((item: AttachmentEntity) => item.id)); const classes = classNames('compose-form__uploads-wrapper', { 'contains-media': mediaIds.size !== 0, }); diff --git a/app/soapbox/features/compose/containers/quoted_status_container.tsx b/app/soapbox/features/compose/containers/quoted_status_container.tsx index 923ee7baa0..0ab894578e 100644 --- a/app/soapbox/features/compose/containers/quoted_status_container.tsx +++ b/app/soapbox/features/compose/containers/quoted_status_container.tsx @@ -10,7 +10,7 @@ const getStatus = makeGetStatus(); /** QuotedStatus shown in post composer. */ const QuotedStatusContainer: React.FC = () => { const dispatch = useAppDispatch(); - const status = useAppSelector(state => getStatus(state, { id: state.compose.get('quote') })); + const status = useAppSelector(state => getStatus(state, { id: state.compose.quote! })); const onCancel = () => { dispatch(cancelQuoteCompose()); diff --git a/app/soapbox/features/reply_mentions/account.tsx b/app/soapbox/features/reply_mentions/account.tsx index b96d6cf5b1..fbfcdec0b9 100644 --- a/app/soapbox/features/reply_mentions/account.tsx +++ b/app/soapbox/features/reply_mentions/account.tsx @@ -26,7 +26,7 @@ const Account: React.FC = ({ accountId, author }) => { const dispatch = useAppDispatch(); const account = useAppSelector((state) => getAccount(state, accountId)); - const added = useAppSelector((state) => !!account && state.compose.get('to').includes(account.acct)); + const added = useAppSelector((state) => !!account && state.compose.to?.includes(account.acct)); const onRemove = () => dispatch(removeFromMentions(accountId)); const onAdd = () => dispatch(addToMentions(accountId)); diff --git a/app/soapbox/features/status/index.tsx b/app/soapbox/features/status/index.tsx index bc571aa6c6..b27be7e5b0 100644 --- a/app/soapbox/features/status/index.tsx +++ b/app/soapbox/features/status/index.tsx @@ -165,7 +165,7 @@ const makeMapStateToProps = () => { status, ancestorsIds, descendantsIds, - askReplyConfirmation: state.compose.get('text', '').trim().length !== 0, + askReplyConfirmation: state.compose.text.trim().length !== 0, me: state.me, displayMedia: getSettings(state).get('displayMedia'), allowedEmoji: soapbox.allowedEmoji, diff --git a/app/soapbox/features/ui/components/compose_modal.js b/app/soapbox/features/ui/components/compose_modal.js deleted file mode 100644 index 3e029070d6..0000000000 Binary files a/app/soapbox/features/ui/components/compose_modal.js and /dev/null differ diff --git a/app/soapbox/features/ui/components/compose_modal.tsx b/app/soapbox/features/ui/components/compose_modal.tsx new file mode 100644 index 0000000000..470c04b699 --- /dev/null +++ b/app/soapbox/features/ui/components/compose_modal.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import { defineMessages, FormattedMessage, useIntl } from 'react-intl'; + +import { cancelReplyCompose } from 'soapbox/actions/compose'; +import { openModal, closeModal } from 'soapbox/actions/modals'; +import { Modal } from 'soapbox/components/ui'; +import { useAppDispatch, useAppSelector } from 'soapbox/hooks'; + +import ComposeFormContainer from '../../compose/containers/compose_form_container'; + +const messages = defineMessages({ + close: { id: 'lightbox.close', defaultMessage: 'Close' }, + confirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' }, +}); + +interface IComposeModal { + onClose: (type?: string) => void, +} + +const ComposeModal: React.FC = ({ onClose }) => { + const intl = useIntl(); + const dispatch = useAppDispatch(); + + const statusId = useAppSelector((state) => state.compose.id); + const composeText = useAppSelector((state) => state.compose.text); + const privacy = useAppSelector((state) => state.compose.privacy); + const inReplyTo = useAppSelector((state) => state.compose.in_reply_to); + const quote = useAppSelector((state) => state.compose.quote); + + const onClickClose = () => { + if (composeText) { + dispatch(openModal('CONFIRM', { + icon: require('@tabler/icons/icons/trash.svg'), + heading: , + message: , + confirm: intl.formatMessage(messages.confirm), + onConfirm: () => { + dispatch(closeModal('COMPOSE')); + dispatch(cancelReplyCompose()); + }, + })); + } else { + onClose('COMPOSE'); + } + }; + + const renderTitle = () => { + if (statusId) { + return ; + } else if (privacy === 'direct') { + return ; + } else if (inReplyTo) { + return ; + } else if (quote) { + return ; + } else { + return ; + } + }; + + return ( + + + + ); +}; + +export default ComposeModal; diff --git a/app/soapbox/features/ui/components/reply_mentions_modal.tsx b/app/soapbox/features/ui/components/reply_mentions_modal.tsx index b1a959afb4..a21a92d3a6 100644 --- a/app/soapbox/features/ui/components/reply_mentions_modal.tsx +++ b/app/soapbox/features/ui/components/reply_mentions_modal.tsx @@ -15,10 +15,10 @@ interface IReplyMentionsModal { } const ReplyMentionsModal: React.FC = ({ onClose }) => { - const status = useAppSelector(state => makeGetStatus()(state, { id: state.compose.get('in_reply_to') })); + const status = useAppSelector(state => makeGetStatus()(state, { id: state.compose.in_reply_to! })); const account = useAppSelector((state) => state.accounts.get(state.me)); - const mentions = statusToMentionsAccountIdsArray(status, account); + const mentions = statusToMentionsAccountIdsArray(status!, account!); const author = (status?.account as AccountEntity).id; const onClickClose = () => { diff --git a/app/soapbox/features/ui/util/pending_status_builder.ts b/app/soapbox/features/ui/util/pending_status_builder.ts index e74b0a8979..1ec288bbeb 100644 --- a/app/soapbox/features/ui/util/pending_status_builder.ts +++ b/app/soapbox/features/ui/util/pending_status_builder.ts @@ -4,21 +4,22 @@ import { normalizeStatus } from 'soapbox/normalizers/status'; import { calculateStatus } from 'soapbox/reducers/statuses'; import { makeGetAccount } from 'soapbox/selectors'; +import type { PendingStatus } from 'soapbox/reducers/pending_statuses'; import type { RootState } from 'soapbox/store'; const getAccount = makeGetAccount(); -const buildMentions = (pendingStatus: ImmutableMap) => { - if (pendingStatus.get('in_reply_to_id')) { - return ImmutableList(pendingStatus.get('to') || []).map(acct => ImmutableMap({ acct })); +const buildMentions = (pendingStatus: PendingStatus) => { + if (pendingStatus.in_reply_to_id) { + return ImmutableList(pendingStatus.to || []).map(acct => ImmutableMap({ acct })); } else { return ImmutableList(); } }; -const buildPoll = (pendingStatus: ImmutableMap) => { +const buildPoll = (pendingStatus: PendingStatus) => { if (pendingStatus.hasIn(['poll', 'options'])) { - return pendingStatus.get('poll').update('options', (options: ImmutableMap) => { + return pendingStatus.poll!.update('options', (options: ImmutableMap) => { return options.map((title: string) => ImmutableMap({ title })); }); } else { @@ -26,23 +27,23 @@ const buildPoll = (pendingStatus: ImmutableMap) => { } }; -export const buildStatus = (state: RootState, pendingStatus: ImmutableMap, idempotencyKey: string) => { +export const buildStatus = (state: RootState, pendingStatus: PendingStatus, idempotencyKey: string) => { const me = state.me as string; const account = getAccount(state, me); - const inReplyToId = pendingStatus.get('in_reply_to_id'); + const inReplyToId = pendingStatus.in_reply_to_id; const status = ImmutableMap({ account, - content: pendingStatus.get('status', '').replace(new RegExp('\n', 'g'), '
'), /* eslint-disable-line no-control-regex */ + content: pendingStatus.status.replace(new RegExp('\n', 'g'), '
'), /* eslint-disable-line no-control-regex */ id: `ęœ«pending-${idempotencyKey}`, in_reply_to_account_id: state.statuses.getIn([inReplyToId, 'account'], null), in_reply_to_id: inReplyToId, - media_attachments: pendingStatus.get('media_ids', ImmutableList()).map((id: string) => ImmutableMap({ id })), + media_attachments: (pendingStatus.media_ids || ImmutableList()).map((id: string) => ImmutableMap({ id })), mentions: buildMentions(pendingStatus), poll: buildPoll(pendingStatus), - quote: pendingStatus.get('quote_id', null), - sensitive: pendingStatus.get('sensitive', false), - visibility: pendingStatus.get('visibility', 'public'), + quote: pendingStatus.quote_id, + sensitive: pendingStatus.sensitive, + visibility: pendingStatus.visibility, }); return calculateStatus(normalizeStatus(status)); diff --git a/app/soapbox/normalizers/status.ts b/app/soapbox/normalizers/status.ts index 9a2bb1337b..5474fa3952 100644 --- a/app/soapbox/normalizers/status.ts +++ b/app/soapbox/normalizers/status.ts @@ -19,7 +19,7 @@ import { normalizePoll } from 'soapbox/normalizers/poll'; import type { ReducerAccount } from 'soapbox/reducers/accounts'; import type { Account, Attachment, Card, Emoji, Mention, Poll, EmbeddedEntity } from 'soapbox/types/entities'; -type StatusVisibility = 'public' | 'unlisted' | 'private' | 'direct'; +export type StatusVisibility = 'public' | 'unlisted' | 'private' | 'direct'; // https://docs.joinmastodon.org/entities/status/ export const StatusRecord = ImmutableRecord({ diff --git a/app/soapbox/reducers/backups.js b/app/soapbox/reducers/backups.js deleted file mode 100644 index 4ce6446100..0000000000 Binary files a/app/soapbox/reducers/backups.js and /dev/null differ diff --git a/app/soapbox/reducers/backups.tsx b/app/soapbox/reducers/backups.tsx new file mode 100644 index 0000000000..6786827ce0 --- /dev/null +++ b/app/soapbox/reducers/backups.tsx @@ -0,0 +1,43 @@ +import { Map as ImmutableMap, Record as ImmutableRecord, fromJS } from 'immutable'; + +import { + BACKUPS_FETCH_SUCCESS, + BACKUPS_CREATE_SUCCESS, +} from '../actions/backups'; + +import type { AnyAction } from 'redux'; +import type { APIEntity } from 'soapbox/types/entities'; + +const BackupRecord = ImmutableRecord({ + id: null as number | null, + content_type: '', + url: '', + file_size: null as number | null, + processed: false, + inserted_at: '', +}); + +type Backup = ReturnType; +type State = ImmutableMap; + +const initialState: State = ImmutableMap(); + +const importBackup = (state: State, backup: APIEntity) => { + return state.set(backup.inserted_at, BackupRecord(backup)); +}; + +const importBackups = (state: State, backups: APIEntity[]) => { + return state.withMutations(mutable => { + backups.forEach(backup => importBackup(mutable, backup)); + }); +}; + +export default function backups(state = initialState, action: AnyAction) { + switch (action.type) { + case BACKUPS_FETCH_SUCCESS: + case BACKUPS_CREATE_SUCCESS: + return importBackups(state, action.backups); + default: + return state; + } +} diff --git a/app/soapbox/reducers/compose.js b/app/soapbox/reducers/compose.ts similarity index 66% rename from app/soapbox/reducers/compose.js rename to app/soapbox/reducers/compose.ts index 60f7136570..ad10c903da 100644 Binary files a/app/soapbox/reducers/compose.js and b/app/soapbox/reducers/compose.ts differ diff --git a/app/soapbox/reducers/pending_statuses.js b/app/soapbox/reducers/pending_statuses.js deleted file mode 100644 index 4e9c247a91..0000000000 Binary files a/app/soapbox/reducers/pending_statuses.js and /dev/null differ diff --git a/app/soapbox/reducers/pending_statuses.ts b/app/soapbox/reducers/pending_statuses.ts new file mode 100644 index 0000000000..090b8694a3 --- /dev/null +++ b/app/soapbox/reducers/pending_statuses.ts @@ -0,0 +1,44 @@ +import { List as ImmutableList, Map as ImmutableMap, Record as ImmutableRecord, fromJS } from 'immutable'; +import { AnyAction } from 'redux'; + +import { + STATUS_CREATE_REQUEST, + STATUS_CREATE_SUCCESS, +} from 'soapbox/actions/statuses'; + +import type { StatusVisibility } from 'soapbox/normalizers/status'; + +const PendingStatusRecord = ImmutableRecord({ + content_type: '', + in_reply_to_id: null as string | null, + media_ids: null as ImmutableList | null, + quote_id: null as string | null, + poll: null as ImmutableMap | null, + sensitive: false, + spoiler_text: '', + status: '', + to: null as ImmutableList | null, + visibility: 'public' as StatusVisibility, +}); + +export type PendingStatus = ReturnType; +type State = ImmutableMap; + +const initialState: State = ImmutableMap(); + +const importStatus = (state: State, params: ImmutableMap, idempotencyKey: string) => { + return state.set(idempotencyKey, PendingStatusRecord(params)); +}; + +const deleteStatus = (state: State, idempotencyKey: string) => state.delete(idempotencyKey); + +export default function pending_statuses(state = initialState, action: AnyAction) { + switch (action.type) { + case STATUS_CREATE_REQUEST: + return importStatus(state, ImmutableMap(fromJS(action.params)), action.idempotencyKey); + case STATUS_CREATE_SUCCESS: + return deleteStatus(state, action.idempotencyKey); + default: + return state; + } +}