Removes Immutable Map from composer typing.

This commit is contained in:
ChaosExAnima 2025-08-05 16:26:47 +02:00
parent 39a3ffaf2f
commit d11894e7af
No known key found for this signature in database
GPG Key ID: 8F2B333100FB6117
27 changed files with 205 additions and 175 deletions

View File

@ -90,7 +90,7 @@ const messages = defineMessages({
});
export const ensureComposeIsVisible = (getState) => {
if (!getState().getIn(['compose', 'mounted'])) {
if (!getState().compose.mounted) {
browserHistory.push('/publish');
}
};
@ -184,10 +184,14 @@ export function directCompose(account) {
}
export function submitCompose() {
/**
* @param {import('../store').AppDispatch} dispatch
* @param {() => import('../store').RootState} getState
*/
return function (dispatch, getState) {
const status = getState().getIn(['compose', 'text'], '');
const media = getState().getIn(['compose', 'media_attachments']);
const statusId = getState().getIn(['compose', 'id'], null);
const status = getState().compose.text;
const media = getState().compose.media_attachments;
const statusId = getState().compose.id;
if ((!status || !status.length) && media.size === 0) {
return;
@ -220,17 +224,17 @@ export function submitCompose() {
method: statusId === null ? 'post' : 'put',
data: {
status,
in_reply_to_id: getState().getIn(['compose', 'in_reply_to'], null),
in_reply_to_id: getState().compose.in_reply_to,
media_ids: media.map(item => item.get('id')),
media_attributes,
sensitive: getState().getIn(['compose', 'sensitive']),
spoiler_text: getState().getIn(['compose', 'spoiler']) ? getState().getIn(['compose', 'spoiler_text'], '') : '',
visibility: getState().getIn(['compose', 'privacy']),
poll: getState().getIn(['compose', 'poll'], null),
language: getState().getIn(['compose', 'language']),
sensitive: getState().compose.sensitive,
spoiler_text: getState().compose.spoiler ? getState().compose.spoiler_text : '',
visibility: getState().compose.privacy,
poll: getState().compose.poll,
language: getState().compose.language,
},
headers: {
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
'Idempotency-Key': getState().compose.idempotencyKey,
},
}).then(function (response) {
if ((browserHistory.location.pathname === '/publish' || browserHistory.location.pathname === '/statuses/new') && window.history.state) {
@ -296,11 +300,16 @@ export function submitComposeFail(error) {
};
}
/** @param {FileList} files */
export function uploadCompose(files) {
/**
* @param {import('../store').AppDispatch} dispatch
* @param {() => import('../store').RootState} getState
*/
return function (dispatch, getState) {
const uploadLimit = getState().getIn(['server', 'server', 'configuration', 'statuses', 'max_media_attachments']);
const media = getState().getIn(['compose', 'media_attachments']);
const pending = getState().getIn(['compose', 'pending_media_attachments']);
const media = getState().compose.media_attachments;
const pending = getState().compose.pending_media_attachments;
const progress = new Array(files.length).fill(0);
let total = Array.from(files).reduce((a, v) => a + v.size, 0);
@ -310,7 +319,7 @@ export function uploadCompose(files) {
return;
}
if (getState().getIn(['compose', 'poll'])) {
if (getState().compose.poll) {
dispatch(showAlert({ message: messages.uploadErrorPoll }));
return;
}
@ -438,7 +447,7 @@ export function changeUploadCompose(id, params) {
return (dispatch, getState) => {
dispatch(changeUploadComposeRequest());
let media = getState().getIn(['compose', 'media_attachments']).find((item) => item.get('id') === id);
let media = getState().compose.media_attachments.find((item) => item.get('id') === id);
// Editing already-attached media is deferred to editing the post itself.
// For simplicity's sake, fake an API reply.
@ -701,7 +710,7 @@ export function hydrateCompose() {
function insertIntoTagHistory(recognizedTags, text) {
return (dispatch, getState) => {
const state = getState();
const oldHistory = state.getIn(['compose', 'tagHistory']);
const oldHistory = state.compose.tagHistory;
const me = state.getIn(['meta', 'me']);
// FIXME: Matching input hashtags with recognized hashtags has become more

View File

@ -1,4 +1,4 @@
import type { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import { createAction } from '@reduxjs/toolkit';
import { apiUpdateMedia } from 'mastodon/api/compose';
import type { ApiMediaAttachmentJSON } from 'mastodon/api_types/media_attachments';
@ -29,6 +29,10 @@ const simulateModifiedApiResponse = (
return data;
};
export const quoteComposeById = createAction<number>(
'compose/quoteComposeById',
);
export const changeUploadCompose = createDataLoadingThunk(
'compose/changeUpload',
async (
@ -42,11 +46,9 @@ export const changeUploadCompose = createDataLoadingThunk(
},
{ getState },
) => {
const media = (
(getState().compose as ImmutableMap<string, unknown>).get(
'media_attachments',
) as ImmutableList<MediaAttachment>
).find((item) => item.get('id') === id);
const media = getState().compose.media_attachments.find(
(item) => item.get('id') === id,
);
// Editing already-attached media is deferred to editing the post itself.
// For simplicity's sake, fake an API reply.

View File

@ -64,7 +64,7 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({
dispatch((_, getState) => {
let state = getState();
if (state.getIn(['compose', 'text']).trim().length !== 0) {
if (state.compose.text.trim().length !== 0) {
dispatch(openModal({ modalType: 'CONFIRM_REPLY', modalProps: { status } }));
} else {
dispatch(replyCompose(status));
@ -118,7 +118,7 @@ const mapDispatchToProps = (dispatch, { contextType }) => ({
onEdit (status) {
dispatch((_, getState) => {
let state = getState();
if (state.getIn(['compose', 'text']).trim().length !== 0) {
if (state.compose.text.trim().length !== 0) {
dispatch(openModal({ modalType: 'CONFIRM_EDIT_STATUS', modalProps: { statusId: status.get('id') } }));
} else {
dispatch(editStatus(status.get('id')));

View File

@ -10,8 +10,6 @@ import { FormattedMessage, useIntl, defineMessages } from 'react-intl';
import classNames from 'classnames';
import type { List as ImmutableList, Map as ImmutableMap } from 'immutable';
import { useSpring, animated } from '@react-spring/web';
import Textarea from 'react-textarea-autosize';
import { length } from 'stringz';
@ -28,7 +26,6 @@ import { CharacterCounter } from 'mastodon/features/compose/components/character
import { Tesseract as fetchTesseract } from 'mastodon/features/ui/util/async-components';
import { Video, getPointerPosition } from 'mastodon/features/video';
import { me } from 'mastodon/initial_state';
import type { MediaAttachment } from 'mastodon/models/media_attachment';
import { useAppSelector, useAppDispatch } from 'mastodon/store';
import { assetHost } from 'mastodon/utils/config';
@ -113,11 +110,7 @@ const Preview: React.FC<{
immediate: draggingRef.current,
});
const media = useAppSelector((state) =>
(
(state.compose as ImmutableMap<string, unknown>).get(
'media_attachments',
) as ImmutableList<MediaAttachment>
).find((x) => x.get('id') === mediaId),
state.compose.media_attachments.find((x) => x.get('id') === mediaId),
);
const account = useAppSelector((state) =>
me ? state.accounts.get(me) : undefined,
@ -253,18 +246,9 @@ export const AltTextModal = forwardRef<ModalRef, Props & Partial<RestoreProps>>(
const intl = useIntl();
const dispatch = useAppDispatch();
const media = useAppSelector((state) =>
(
(state.compose as ImmutableMap<string, unknown>).get(
'media_attachments',
) as ImmutableList<MediaAttachment>
).find((x) => x.get('id') === mediaId),
);
const lang = useAppSelector(
(state) =>
(state.compose as ImmutableMap<string, unknown>).get(
'language',
) as string,
state.compose.media_attachments.find((x) => x.get('id') === mediaId),
);
const lang = useAppSelector((state) => state.compose.language);
const focusX =
(media?.getIn(['meta', 'focus', 'x'], 0) as number | undefined) ?? 0;
const focusY =

View File

@ -22,7 +22,7 @@ const messages = defineMessages({
export const EditIndicator = () => {
const intl = useIntl();
const dispatch = useDispatch();
const id = useSelector(state => state.getIn(['compose', 'id']));
const id = useSelector(state => state.compose.id);
const status = useSelector(state => state.getIn(['statuses', id]));
const account = useSelector(state => state.getIn(['accounts', status?.get('account')]));

View File

@ -332,10 +332,8 @@ export const LanguageDropdown: React.FC = () => {
const intl = useIntl();
const dispatch = useAppDispatch();
const value = useAppSelector(
(state) => state.compose.get('language') as string,
);
const text = useAppSelector((state) => state.compose.get('text') as string);
const value = useAppSelector((state) => state.compose.language);
const text = useAppSelector((state) => state.compose.text);
const current =
(preloadedLanguages as Language[]).find((lang) => lang[0] === value) ?? [];

View File

@ -16,9 +16,7 @@ const messages = defineMessages({
export const NavigationBar: React.FC = () => {
const dispatch = useAppDispatch();
const intl = useIntl();
const isReplying = useAppSelector(
(state) => !!state.compose.get('in_reply_to'),
);
const isReplying = useAppSelector((state) => !!state.compose.in_reply_to);
const handleCancelClick = useCallback(() => {
dispatch(cancelReplyCompose());

View File

@ -56,8 +56,8 @@ Select.propTypes = {
const Option = ({ multipleChoice, index, title, autoFocus }) => {
const intl = useIntl();
const dispatch = useDispatch();
const suggestions = useSelector(state => state.getIn(['compose', 'suggestions']));
const lang = useSelector(state => state.getIn(['compose', 'language']));
const suggestions = useSelector(state => state.compose.suggestions);
const lang = useSelector(state => state.compose.language);
const maxOptions = useSelector(state => state.getIn(['server', 'server', 'configuration', 'polls', 'max_options']));
const handleChange = useCallback(({ target: { value } }) => {
@ -108,7 +108,7 @@ Option.propTypes = {
export const PollForm = () => {
const intl = useIntl();
const dispatch = useDispatch();
const poll = useSelector(state => state.getIn(['compose', 'poll']));
const poll = useSelector(state => state.compose.poll);
const options = poll?.get('options');
const expiresIn = poll?.get('expires_in');
const isMultiple = poll?.get('multiple');

View File

@ -12,7 +12,7 @@ import { Icon } from 'mastodon/components/icon';
import { EmbeddedStatusContent } from 'mastodon/features/notifications_v2/components/embedded_status_content';
export const ReplyIndicator = () => {
const inReplyToId = useSelector(state => state.getIn(['compose', 'in_reply_to']));
const inReplyToId = useSelector(state => state.compose.in_reply_to);
const status = useSelector(state => state.getIn(['statuses', inReplyToId]));
const account = useSelector(state => state.getIn(['accounts', status?.get('account')]));

View File

@ -4,8 +4,6 @@ import { FormattedMessage } from 'react-intl';
import classNames from 'classnames';
import type { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import { useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
@ -16,7 +14,6 @@ import { undoUploadCompose } from 'mastodon/actions/compose';
import { openModal } from 'mastodon/actions/modal';
import { Blurhash } from 'mastodon/components/blurhash';
import { Icon } from 'mastodon/components/icon';
import type { MediaAttachment } from 'mastodon/models/media_attachment';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
export const Upload: React.FC<{
@ -29,15 +26,9 @@ export const Upload: React.FC<{
}> = ({ id, dragging, draggable = true, overlay, tall, wide }) => {
const dispatch = useAppDispatch();
const media = useAppSelector((state) =>
(
(state.compose as ImmutableMap<string, unknown>).get(
'media_attachments',
) as ImmutableList<MediaAttachment>
).find((item) => item.get('id') === id),
);
const sensitive = useAppSelector(
(state) => state.compose.get('spoiler') as boolean,
state.compose.media_attachments.find((item) => item.get('id') === id),
);
const sensitive = useAppSelector((state) => state.compose.spoiler);
const handleUndoClick = useCallback(() => {
dispatch(undoUploadCompose(id));

View File

@ -2,11 +2,7 @@ import { useState, useCallback, useMemo } from 'react';
import { useIntl, defineMessages } from 'react-intl';
import type {
List,
Map as ImmutableMap,
List as ImmutableList,
} from 'immutable';
import type { List } from 'immutable';
import type {
DragStartEvent,
@ -67,21 +63,13 @@ export const UploadForm: React.FC = () => {
const intl = useIntl();
const mediaIds = useAppSelector(
(state) =>
(
(state.compose as ImmutableMap<string, unknown>).get(
'media_attachments',
) as ImmutableList<MediaAttachment>
).map((item: MediaAttachment) => item.get('id')) as List<string>,
);
const active = useAppSelector(
(state) => state.compose.get('is_uploading') as boolean,
);
const progress = useAppSelector(
(state) => state.compose.get('progress') as number,
);
const isProcessing = useAppSelector(
(state) => state.compose.get('is_processing') as boolean,
state.compose.media_attachments.map((item: MediaAttachment) =>
item.get('id'),
) as List<string>,
);
const active = useAppSelector((state) => state.compose.is_uploading);
const progress = useAppSelector((state) => state.compose.progress);
const isProcessing = useAppSelector((state) => state.compose.is_processing);
const [activeId, setActiveId] = useState<UniqueIdentifier | null>(null);
const sensors = useSensors(
useSensor(PointerSensor, {

View File

@ -10,9 +10,9 @@ import type { RootState } from 'mastodon/store';
import { HASHTAG_PATTERN_REGEX } from 'mastodon/utils/hashtags';
const selector = createSelector(
(state: RootState) => state.compose.get('privacy') as string,
(state: RootState) => state.compose.privacy,
(state: RootState) => !!state.accounts.getIn([me, 'locked']),
(state: RootState) => state.compose.get('text') as string,
(state: RootState) => state.compose.text,
(privacy, locked, text) => ({
needsLockWarning: privacy === 'private' && !locked,
hashtagWarning: privacy !== 'public' && HASHTAG_PATTERN_REGEX.test(text),

View File

@ -15,22 +15,22 @@ import { openModal } from 'mastodon/actions/modal';
import ComposeForm from '../components/compose_form';
const mapStateToProps = state => ({
text: state.getIn(['compose', 'text']),
suggestions: state.getIn(['compose', 'suggestions']),
spoiler: state.getIn(['compose', 'spoiler']),
spoilerText: state.getIn(['compose', 'spoiler_text']),
privacy: state.getIn(['compose', 'privacy']),
focusDate: state.getIn(['compose', 'focusDate']),
caretPosition: state.getIn(['compose', 'caretPosition']),
preselectDate: state.getIn(['compose', 'preselectDate']),
isSubmitting: state.getIn(['compose', 'is_submitting']),
isEditing: state.getIn(['compose', 'id']) !== null,
isChangingUpload: state.getIn(['compose', 'is_changing_upload']),
isUploading: state.getIn(['compose', 'is_uploading']),
anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
missingAltText: state.getIn(['compose', 'media_attachments']).some(media => ['image', 'gifv'].includes(media.get('type')) && (media.get('description') ?? '').length === 0),
isInReply: state.getIn(['compose', 'in_reply_to']) !== null,
lang: state.getIn(['compose', 'language']),
text: state.compose.text,
suggestions: state.compose.suggestions,
spoiler: state.compose.spoiler,
spoilerText: state.compose.spoiler_text,
privacy: state.compose.privacy,
focusDate: state.compose.focusDate,
caretPosition: state.compose.caretPosition,
preselectDate: state.compose.preselectDate,
isSubmitting: state.compose.is_submitting,
isEditing: state.compose.id !== null,
isChangingUpload: state.compose.is_changing_upload,
isUploading: state.compose.is_uploading,
anyMedia: state.compose.media_attachments.size > 0,
missingAltText: state.compose.media_attachments.some(media => ['image', 'gifv'].includes(media.get('type')) && (media.get('description') ?? '').length === 0),
isInReply: state.compose.in_reply_to !== null,
lang: state.compose.language,
maxChars: state.getIn(['server', 'server', 'configuration', 'statuses', 'max_characters'], 500),
});

View File

@ -4,15 +4,15 @@ import { addPoll, removePoll } from '../../../actions/compose';
import PollButton from '../components/poll_button';
const mapStateToProps = state => ({
disabled: state.getIn(['compose', 'is_uploading']) || (state.getIn(['compose', 'media_attachments']).size > 0),
active: state.getIn(['compose', 'poll']) !== null,
disabled: state.compose.is_uploading || (state.compose.media_attachments.size > 0),
active: state.compose.poll !== null,
});
const mapDispatchToProps = dispatch => ({
onClick () {
dispatch((_, getState) => {
if (getState().getIn(['compose', 'poll'])) {
if (getState().compose.poll) {
dispatch(removePoll());
} else {
dispatch(addPoll());

View File

@ -6,7 +6,7 @@ import { isUserTouching } from '../../../is_mobile';
import PrivacyDropdown from '../components/privacy_dropdown';
const mapStateToProps = state => ({
value: state.getIn(['compose', 'privacy']),
value: state.compose.privacy,
});
const mapDispatchToProps = dispatch => ({

View File

@ -14,8 +14,8 @@ const messages = defineMessages({
const mapStateToProps = (state, { intl }) => ({
iconComponent: WarningIcon,
title: intl.formatMessage(state.getIn(['compose', 'spoiler']) ? messages.marked : messages.unmarked),
active: state.getIn(['compose', 'spoiler']),
title: intl.formatMessage(state.compose.spoiler ? messages.marked : messages.unmarked),
active: state.compose.spoiler,
ariaControls: 'cw-spoiler-input',
size: 18,
inverted: true,

View File

@ -4,17 +4,17 @@ import { uploadCompose } from '../../../actions/compose';
import UploadButton from '../components/upload_button';
const mapStateToProps = state => {
const isPoll = state.getIn(['compose', 'poll']) !== null;
const isUploading = state.getIn(['compose', 'is_uploading']);
const readyAttachmentsSize = state.getIn(['compose', 'media_attachments']).size ?? 0;
const pendingAttachmentsSize = state.getIn(['compose', 'pending_media_attachments']).size ?? 0;
const isPoll = state.compose.poll !== null;
const isUploading = state.compose.is_uploading;
const readyAttachmentsSize = state.compose.media_attachments.size ?? 0;
const pendingAttachmentsSize = state.compose.pending_media_attachments.size ?? 0;
const attachmentsSize = readyAttachmentsSize + pendingAttachmentsSize;
const isOverLimit = attachmentsSize > state.getIn(['server', 'server', 'configuration', 'statuses', 'max_media_attachments'])-1;
const hasVideoOrAudio = state.getIn(['compose', 'media_attachments']).some(m => ['video', 'audio'].includes(m.get('type')));
const hasVideoOrAudio = state.compose.media_attachments.some(m => ['video', 'audio'].includes(m.get('type')));
return {
disabled: isPoll || isUploading || isOverLimit || hasVideoOrAudio,
resetFileKey: state.getIn(['compose', 'resetFileKey']),
resetFileKey: state.compose.resetFileKey,
};
};

View File

@ -98,7 +98,7 @@ export const Conversation = ({ conversation, scrollKey, onMoveUp, onMoveDown })
dispatch((_, getState) => {
let state = getState();
if (state.getIn(['compose', 'text']).trim().length !== 0) {
if (state.compose.text.trim().length !== 0) {
dispatch(openModal({ modalType: 'CONFIRM_REPLY', modalProps: { status: lastStatus } }));
} else {
dispatch(replyCompose(lastStatus));

View File

@ -69,7 +69,7 @@ export const Footer: React.FC<{
const status = useAppSelector((state) => getStatus(state, { id: statusId }));
const account = status?.get('account') as Account | undefined;
const askReplyConfirmation = useAppSelector(
(state) => (state.compose.get('text') as string).trim().length !== 0,
(state) => state.compose.text.trim().length !== 0,
);
const handleReplyClick = useCallback(() => {

View File

@ -97,7 +97,7 @@ const makeMapStateToProps = () => {
status,
ancestorsIds,
descendantsIds,
askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0,
askReplyConfirmation: state.compose.text.trim().length !== 0,
domain: state.getIn(['meta', 'domain']),
pictureInPicture: getPictureInPicture(state, { id: props.params.statusId }),
};

View File

@ -28,7 +28,7 @@ export const BoostModal: React.FC<{
const intl = useIntl();
const defaultPrivacy = useAppSelector(
(state) => state.compose.get('default_privacy') as StatusVisibility,
(state) => state.compose.default_privacy,
);
const statusId = status.get('id') as string;

View File

@ -21,7 +21,7 @@ export const ComposePanel: React.FC = () => {
}, [dispatch]);
const { signedIn } = useIdentity();
const hideComposer = useAppSelector((state) => {
const mounted = state.compose.get('mounted');
const mounted = state.compose.mounted;
if (typeof mounted === 'number') {
return mounted > 1;
}
@ -65,8 +65,8 @@ export const ComposePanel: React.FC = () => {
export const RedirectToMobileComposeIfNeeded: React.FC = () => {
const history = useAppHistory();
const shouldRedirect = useAppSelector((state) =>
state.compose.get('should_redirect_to_compose_page'),
const shouldRedirect = useAppSelector(
(state) => state.compose.should_redirect_to_compose_page,
);
useLayoutEffect(() => {

View File

@ -55,7 +55,7 @@ const DiscardDraftConfirmationModal: React.FC<
} & BaseConfirmationModalProps
> = ({ onConfirm, onClose }) => {
const intl = useIntl();
const isEditing = useAppSelector((state) => !!state.compose.get('id'));
const isEditing = useAppSelector((state) => !!state.compose.id);
const contextualMessages = isEditing ? editMessages : postMessages;

View File

@ -2,11 +2,8 @@ import { useCallback } from 'react';
import { defineMessages, useIntl } from 'react-intl';
import type { Map as ImmutableMap, List as ImmutableList } from 'immutable';
import { submitCompose } from 'mastodon/actions/compose';
import { openModal } from 'mastodon/actions/modal';
import type { MediaAttachment } from 'mastodon/models/media_attachment';
import { useAppDispatch, useAppSelector } from 'mastodon/store';
import type { BaseConfirmationModalProps } from './confirmation_modal';
@ -39,11 +36,7 @@ export const ConfirmMissingAltTextModal: React.FC<
const dispatch = useAppDispatch();
const mediaId = useAppSelector(
(state) =>
(
(state.compose as ImmutableMap<string, unknown>).get(
'media_attachments',
) as ImmutableList<MediaAttachment>
)
state.compose.media_attachments
.find(
(media) =>
['image', 'gifv'].includes(media.get('type') as string) &&

View File

@ -89,10 +89,10 @@ const messages = defineMessages({
const mapStateToProps = state => ({
layout: state.getIn(['meta', 'layout']),
isComposing: state.getIn(['compose', 'is_composing']),
hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0,
hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0,
canUploadMore: !state.getIn(['compose', 'media_attachments']).some(x => ['audio', 'video'].includes(x.get('type'))) && state.getIn(['compose', 'media_attachments']).size < state.getIn(['server', 'server', 'configuration', 'statuses', 'max_media_attachments']),
isComposing: state.compose.is_composing,
hasComposingText: state.compose.text.trim().length !== 0,
hasMediaAttachments: state.compose.media_attachments.size > 0,
canUploadMore: !state.compose.media_attachments.some(x => ['audio', 'video'].includes(x.get('type'))) && state.compose.media_attachments.size < state.getIn(['server', 'server', 'configuration', 'statuses', 'max_media_attachments']),
firstLaunch: state.getIn(['settings', 'introductionVersion'], 0) < INTRODUCTION_VERSION,
newAccount: !state.getIn(['accounts', me, 'note']) && !state.getIn(['accounts', me, 'bot']) && state.getIn(['accounts', me, 'following_count'], 0) === 0 && state.getIn(['accounts', me, 'statuses_count'], 0) === 0,
username: state.getIn(['accounts', me, 'username']),

View File

@ -52,44 +52,7 @@ import { me } from '../initial_state';
import { unescapeHTML } from '../utils/html';
import { uuid } from '../uuid';
const initialState = ImmutableMap({
mounted: 0,
sensitive: false,
spoiler: false,
spoiler_text: '',
privacy: null,
id: null,
text: '',
focusDate: null,
caretPosition: null,
preselectDate: null,
in_reply_to: null,
is_composing: false,
is_submitting: false,
is_changing_upload: false,
is_uploading: false,
should_redirect_to_compose_page: false,
progress: 0,
isUploadingThumbnail: false,
thumbnailProgress: 0,
media_attachments: ImmutableList(),
pending_media_attachments: 0,
poll: null,
suggestion_token: null,
suggestions: ImmutableList(),
default_privacy: 'public',
default_sensitive: false,
default_language: 'en',
resetFileKey: Math.floor((Math.random() * 0x10000)),
idempotencyKey: null,
tagHistory: ImmutableList(),
});
const initialPoll = ImmutableMap({
options: ImmutableList(['', '']),
expires_in: 24 * 3600,
multiple: false,
});
import { initialState, initialPollState } from './compose_typed';
function statusToTextMentions(state, status) {
let set = ImmutableOrderedSet([]);
@ -321,7 +284,7 @@ export const composeReducer = (state = initialState, action) => {
switch(action.type) {
case STORE_HYDRATE:
return hydrate(state, action.state.get('compose'));
return hydrate(state, action.state.compose);
case COMPOSE_MOUNT:
return state
.set('mounted', state.get('mounted') + 1)
@ -538,7 +501,7 @@ export const composeReducer = (state = initialState, action) => {
}
});
case COMPOSE_POLL_ADD:
return state.set('poll', initialPoll);
return state.set('poll', initialPollState);
case COMPOSE_POLL_REMOVE:
return state.set('poll', null);
case COMPOSE_POLL_OPTION_CHANGE:

View File

@ -0,0 +1,104 @@
import { createReducer } from '@reduxjs/toolkit';
import { List as ImmutableList } from 'immutable';
import { quoteComposeById } from '../actions/compose_typed';
import type { StatusVisibility } from '../api_types/statuses';
import type { ApiHashtagJSON } from '../api_types/tags';
import type { CustomEmoji } from '../models/custom_emoji';
import type { MediaAttachment } from '../models/media_attachment';
type SuggestionEmoji = CustomEmoji & { type: 'emoji' };
type SuggestionHashtag = ApiHashtagJSON & { type: 'hashtag' };
interface SuggestionAccount {
type: 'account';
id: string;
}
type Suggestion = SuggestionEmoji | SuggestionHashtag | SuggestionAccount;
export interface ComposeShape {
mounted: number;
sensitive: boolean;
spoiler: boolean;
spoiler_text: string;
privacy: StatusVisibility | null;
id: string | null;
text: string;
language: string;
focusDate: Date | null;
caretPosition: number | null;
preselectDate: Date | null;
in_reply_to: string | null;
is_composing: boolean;
is_submitting: boolean;
is_changing_upload: boolean;
is_uploading: boolean;
is_processing: boolean;
should_redirect_to_compose_page: boolean;
progress: number;
isUploadingThumbnail: boolean;
thumbnailProgress: number;
media_attachments: ImmutableList<MediaAttachment>;
pending_media_attachments: number;
poll: ComposePollShape | null;
suggestion_token: string | null;
suggestions: ImmutableList<Suggestion>;
default_privacy: StatusVisibility;
default_sensitive: boolean;
default_language: string;
resetFileKey: number;
idempotencyKey: string | null;
tagHistory: ImmutableList<string>;
}
export const initialState: ComposeShape = {
mounted: 0,
sensitive: false,
spoiler: false,
spoiler_text: '',
privacy: null,
id: null,
text: '',
language: 'en',
focusDate: null,
caretPosition: null,
preselectDate: null,
in_reply_to: null,
is_composing: false,
is_submitting: false,
is_changing_upload: false,
is_uploading: false,
is_processing: false,
should_redirect_to_compose_page: false,
progress: 0,
isUploadingThumbnail: false,
thumbnailProgress: 0,
media_attachments: ImmutableList(),
pending_media_attachments: 0,
poll: null,
suggestion_token: null,
suggestions: ImmutableList(),
default_privacy: 'public',
default_sensitive: false,
default_language: 'en',
resetFileKey: Math.floor(Math.random() * 0x10000),
idempotencyKey: null,
tagHistory: ImmutableList(),
};
export interface ComposePollShape {
options: string[];
expires_in: number;
multiple: boolean;
}
export const initialPollState = {
options: ['', ''],
expires_in: 24 * 3600,
multiple: false,
} satisfies ComposePollShape;
export const composeReducer = createReducer(initialState, (builder) => {
builder.addCase(quoteComposeById, (state, action) => {
state.id = action.payload.toString(); // Temp
});
});