@mapbox/batfish
Version:
The React-powered static-site generator you didn't know you wanted
141 lines (113 loc) • 3.88 kB
JavaScript
//
import React from 'react';
import PropTypes from 'prop-types';
import linkHijacker from '@mapbox/link-hijacker';
import scrollRestorer from '@mapbox/scroll-restorer';
import linkToLocation from '@mapbox/link-to-location';
import querySelectorContainsNode from '@mapbox/query-selector-contains-node';
import { batfishContext } from 'batfish-internal/context';
import { routeTo } from '@mapbox/batfish/modules/route-to';
import { prefixUrl } from '@mapbox/batfish/modules/prefix-url';
import { findMatchingRoute } from './find-matching-route';
import { scrollToFragment } from './scroll-to-fragment';
import { getWindow } from './get-window';
import { changePage } from './change-page';
import { getCurrentLocation } from './get-current-location';
const {
siteBasePath,
siteOrigin,
manageScrollRestoration,
hijackLinks
} = batfishContext.selectedConfig;
// See explanation for this weirdness in prefix-url.js.
// This happens outside the component lifecycle so it can be used during
// rendering of HTML.
prefixUrl._configure(siteBasePath, siteOrigin);
class Router extends React.PureComponent {
constructor(props ) {
super(props);
const location = {
pathname: this.props.startingPath
};
if (typeof window !== 'undefined') {
const win = getWindow();
location.search = win.location.search;
location.hash = win.location.hash;
}
this.state = {
path: this.props.startingPath,
PageComponent: this.props.startingComponent,
pageProps: this.props.startingProps,
location
};
}
getChildContext() {
return { location: this.state.location };
}
componentDidMount() {
if (manageScrollRestoration) {
scrollRestorer.start({ autoRestore: false });
}
const win = getWindow();
if (!win.location.hash && manageScrollRestoration) {
scrollRestorer.restoreScroll();
} else {
scrollToFragment();
}
routeTo._setRouteToHandler(this.routeTo);
win.addEventListener('popstate', (event) => {
event.preventDefault();
changePage(
{
pathname: win.location.pathname,
search: win.location.search,
hash: win.location.hash
},
this.setState.bind(this)
);
});
if (hijackLinks) {
linkHijacker.hijack(
{
skipFilter: (link) =>
querySelectorContainsNode('[data-batfish-no-hijack]', link)
},
this.routeTo
);
}
this.setState({
location: getCurrentLocation()
});
}
// Converts input to a location object.
// If it matches a route, go there dynamically and scroll to the top of the viewport.
// If it doesn't match a route, go there non-dynamically.
routeTo = (input ) => {
const win = getWindow();
const targetLocation = linkToLocation(input);
if (findMatchingRoute(targetLocation.pathname).is404) {
return win.location.assign(input);
}
changePage(targetLocation, this.setState.bind(this), {
pushState: true,
scrollToTop:
win.location.pathname !== targetLocation.pathname ||
!targetLocation.hash
});
};
render() {
const { PageComponent } = this.state;
if (!PageComponent) return null;
return (
<PageComponent location={this.state.location} {...this.state.pageProps} />
);
}
}
Router.childContextTypes = {
location: PropTypes.shape({
pathname: PropTypes.string.isRequired,
hash: PropTypes.string,
search: PropTypes.string
}).isRequired
};
export { Router };