diff --git a/packages/docusaurus/src/client/PendingNavigation.js b/packages/docusaurus/src/client/PendingNavigation.js index 860557956e93..ae6a22d29ec4 100644 --- a/packages/docusaurus/src/client/PendingNavigation.js +++ b/packages/docusaurus/src/client/PendingNavigation.js @@ -11,6 +11,7 @@ import nprogress from 'nprogress'; import clientLifecyclesDispatcher from './client-lifecycles-dispatcher'; import preload from './preload'; +import normalizeLocation from './normalizeLocation'; import 'nprogress/nprogress.css'; @@ -37,19 +38,20 @@ class PendingNavigation extends React.Component { // If `routeDidChange` is true, means the router is trying to navigate to a new // route. We will preload the new route. if (routeDidChange) { + const nextLocation = normalizeLocation(nextProps.location); this.startProgressBar(delay); // Save the location first. - this.previousLocation = this.props.location; + this.previousLocation = normalizeLocation(this.props.location); this.setState({ nextRouteHasLoaded: false, }); // Load data while the old screen remains. - preload(routes, nextProps.location.pathname) + preload(routes, nextLocation.pathname) .then(() => { clientLifecyclesDispatcher.onRouteUpdate({ previousLocation: this.previousLocation, - location: nextProps.location, + location: nextLocation, }); // Route has loaded, we can reset previousLocation. this.previousLocation = null; @@ -59,7 +61,7 @@ class PendingNavigation extends React.Component { }, this.stopProgressBar, ); - const {hash} = nextProps.location; + const {hash} = nextLocation; if (!hash) { window.scrollTo(0, 0); } else { @@ -94,7 +96,7 @@ class PendingNavigation extends React.Component { this.clearProgressBarTimeout(); this.progressBarTimeout = setTimeout(() => { clientLifecyclesDispatcher.onRouteUpdateDelayed({ - location: this.props.location, + location: normalizeLocation(this.props.location), }); nprogress.start(); }, delay); @@ -107,7 +109,9 @@ class PendingNavigation extends React.Component { render() { const {children, location} = this.props; - return children} />; + return ( + children} /> + ); } } diff --git a/packages/docusaurus/src/client/__tests__/normalizeLocation.test.js b/packages/docusaurus/src/client/__tests__/normalizeLocation.test.js new file mode 100644 index 000000000000..415250a2cd28 --- /dev/null +++ b/packages/docusaurus/src/client/__tests__/normalizeLocation.test.js @@ -0,0 +1,70 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import normalizeLocation from '../normalizeLocation'; + +describe('normalizeLocation', () => { + test('rewrite locations with index.html', () => { + expect( + normalizeLocation({ + pathname: '/docs/introduction/index.html', + search: '?search=foo', + hash: '#features', + }), + ).toEqual({ + pathname: '/docs/introduction', + search: '?search=foo', + hash: '#features', + }); + + expect( + normalizeLocation({ + pathname: '/index.html', + search: '', + hash: '#features', + }), + ).toEqual({ + pathname: '/', + search: '', + hash: '#features', + }); + }); + + test('untouched pathnames', () => { + expect( + normalizeLocation({ + pathname: '/docs/introduction', + search: '', + hash: '#features', + }), + ).toEqual({ + pathname: '/docs/introduction', + search: '', + hash: '#features', + }); + + expect( + normalizeLocation({ + pathname: '/docs/introduction/foo.html', + search: '', + hash: '#bar', + }), + ).toEqual({ + pathname: '/docs/introduction/foo.html', + search: '', + hash: '#bar', + }); + + expect( + normalizeLocation({ + pathname: '/', + }), + ).toEqual({ + pathname: '/', + }); + }); +}); diff --git a/packages/docusaurus/src/client/normalizeLocation.js b/packages/docusaurus/src/client/normalizeLocation.js new file mode 100644 index 000000000000..5e85db009e43 --- /dev/null +++ b/packages/docusaurus/src/client/normalizeLocation.js @@ -0,0 +1,34 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +// Memoize previously normalized pathnames. +const pathnames = {}; + +function normalizeLocation(location) { + if (pathnames[location.pathname]) { + return { + ...location, + pathname: pathnames[location.pathname], + }; + } + + let pathname = location.pathname || '/'; + pathname = pathname.trim().replace(/\/index\.html$/, ''); + + if (pathname === '') { + pathname = '/'; + } + + pathnames[location.pathname] = pathname; + + return { + ...location, + pathname, + }; +} + +export default normalizeLocation;