diff --git a/app/javascript/mastodon/features/ui/components/columns_area.jsx b/app/javascript/mastodon/features/ui/components/columns_area.jsx
index 9737d31e31b..1b882a1c550 100644
--- a/app/javascript/mastodon/features/ui/components/columns_area.jsx
+++ b/app/javascript/mastodon/features/ui/components/columns_area.jsx
@@ -23,7 +23,7 @@ import { useColumnsContext } from '../util/columns_context';
import BundleColumnError from './bundle_column_error';
import { ColumnLoading } from './column_loading';
-import { ComposePanel } from './compose_panel';
+import { ComposePanel, RedirectToMobileComposeIfNeeded } from './compose_panel';
import DrawerLoading from './drawer_loading';
import { NavigationPanel } from 'mastodon/features/navigation_panel';
@@ -124,6 +124,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
{renderComposePanel && }
+
diff --git a/app/javascript/mastodon/features/ui/components/compose_panel.tsx b/app/javascript/mastodon/features/ui/components/compose_panel.tsx
index aa155203095..cc55ff4cef6 100644
--- a/app/javascript/mastodon/features/ui/components/compose_panel.tsx
+++ b/app/javascript/mastodon/features/ui/components/compose_panel.tsx
@@ -1,4 +1,4 @@
-import { useCallback, useEffect } from 'react';
+import { useCallback, useEffect, useLayoutEffect } from 'react';
import { useLayout } from '@/mastodon/hooks/useLayout';
import { useAppDispatch, useAppSelector } from '@/mastodon/store';
@@ -7,6 +7,7 @@ import {
mountCompose,
unmountCompose,
} from 'mastodon/actions/compose';
+import { useAppHistory } from 'mastodon/components/router';
import ServerBanner from 'mastodon/components/server_banner';
import { Search } from 'mastodon/features/compose/components/search';
import ComposeFormContainer from 'mastodon/features/compose/containers/compose_form_container';
@@ -54,3 +55,25 @@ export const ComposePanel: React.FC = () => {
);
};
+
+/**
+ * Redirect the user to the standalone compose page when the
+ * sidebar composer is hidden due to a change in viewport size
+ * while a post is being written.
+ */
+
+export const RedirectToMobileComposeIfNeeded: React.FC = () => {
+ const history = useAppHistory();
+
+ const shouldRedirect = useAppSelector((state) =>
+ state.compose.get('should_redirect_to_compose_page'),
+ );
+
+ useLayoutEffect(() => {
+ if (shouldRedirect) {
+ history.push('/publish');
+ }
+ }, [history, shouldRedirect]);
+
+ return null;
+};
diff --git a/app/javascript/mastodon/reducers/compose.js b/app/javascript/mastodon/reducers/compose.js
index 330dcd93d09..6b799a46e80 100644
--- a/app/javascript/mastodon/reducers/compose.js
+++ b/app/javascript/mastodon/reducers/compose.js
@@ -68,6 +68,7 @@ const initialState = ImmutableMap({
is_submitting: false,
is_changing_upload: false,
is_uploading: false,
+ should_redirect_to_compose_page: false,
progress: 0,
isUploadingThumbnail: false,
thumbnailProgress: 0,
@@ -322,11 +323,21 @@ export const composeReducer = (state = initialState, action) => {
case STORE_HYDRATE:
return hydrate(state, action.state.get('compose'));
case COMPOSE_MOUNT:
- return state.set('mounted', state.get('mounted') + 1);
+ return state
+ .set('mounted', state.get('mounted') + 1)
+ .set('should_redirect_to_compose_page', false);
case COMPOSE_UNMOUNT:
return state
.set('mounted', Math.max(state.get('mounted') - 1, 0))
- .set('is_composing', false);
+ .set('is_composing', false)
+ .set(
+ 'should_redirect_to_compose_page',
+ (state.get('mounted') === 1 &&
+ state.get('is_composing') === true &&
+ (state.get('text').trim() !== '' ||
+ state.get('media_attachments').size > 0)
+ )
+ );
case COMPOSE_SENSITIVITY_CHANGE:
return state.withMutations(map => {
if (!state.get('spoiler')) {