From 15182d1e5e595b21c47b48c5d258f30a0251e753 Mon Sep 17 00:00:00 2001 From: Claire Date: Tue, 24 Oct 2023 15:24:57 +0200 Subject: [PATCH] Fix history handling not properly handling states after update to react-router v5 (#27526) --- app/javascript/mastodon/components/router.tsx | 66 ++++++++++++------- 1 file changed, 43 insertions(+), 23 deletions(-) diff --git a/app/javascript/mastodon/components/router.tsx b/app/javascript/mastodon/components/router.tsx index 7779f52e2..e38157167 100644 --- a/app/javascript/mastodon/components/router.tsx +++ b/app/javascript/mastodon/components/router.tsx @@ -3,7 +3,11 @@ import React from 'react'; import { Router as OriginalRouter } from 'react-router'; -import type { LocationDescriptor, Path } from 'history'; +import type { + LocationDescriptor, + LocationDescriptorObject, + Path, +} from 'history'; import { createBrowserHistory } from 'history'; import { layoutFromWindow } from 'mastodon/is_mobile'; @@ -20,39 +24,55 @@ const browserHistory = createBrowserHistory< const originalPush = browserHistory.push.bind(browserHistory); const originalReplace = browserHistory.replace.bind(browserHistory); -function extractRealPath(path: HistoryPath) { - if (typeof path === 'string') return path; - else return path.pathname; +function normalizePath( + path: HistoryPath, + state?: MastodonLocationState, +): LocationDescriptorObject { + const location = typeof path === 'string' ? { pathname: path } : { ...path }; + + if (location.state === undefined && state !== undefined) { + location.state = state; + } else if ( + location.state !== undefined && + state !== undefined && + process.env.NODE_ENV === 'development' + ) { + // eslint-disable-next-line no-console + console.log( + 'You should avoid providing a 2nd state argument to push when the 1st argument is a location-like object that already has state; it is ignored', + ); + } + + if ( + layoutFromWindow() === 'multi-column' && + !location.pathname?.startsWith('/deck') + ) { + location.pathname = `/deck${location.pathname}`; + } + + return location; } browserHistory.push = (path: HistoryPath, state?: MastodonLocationState) => { - state = state ?? {}; - state.fromMastodon = true; + const location = normalizePath(path, state); - const realPath = extractRealPath(path); - if (!realPath) return; + location.state = location.state ?? {}; + location.state.fromMastodon = true; - if (layoutFromWindow() === 'multi-column' && !realPath.startsWith('/deck')) { - originalPush(`/deck${realPath}`, state); - } else { - originalPush(path, state); - } + originalPush(location); }; browserHistory.replace = (path: HistoryPath, state?: MastodonLocationState) => { + const location = normalizePath(path, state); + + if (!location.pathname) return; + if (browserHistory.location.state?.fromMastodon) { - state = state ?? {}; - state.fromMastodon = true; + location.state = location.state ?? {}; + location.state.fromMastodon = true; } - const realPath = extractRealPath(path); - if (!realPath) return; - - if (layoutFromWindow() === 'multi-column' && !realPath.startsWith('/deck')) { - originalReplace(`/deck${realPath}`, state); - } else { - originalReplace(path, state); - } + originalReplace(location); }; export const Router: React.FC = ({ children }) => {