UNPKG

@mapbox/batfish

Version:

The React-powered static-site generator you didn't know you wanted

141 lines (113 loc) 3.88 kB
// 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 };