1 | import React from "react"
|
2 | import PropTypes from "prop-types"
|
3 | import loader from "./loader"
|
4 | import redirects from "./redirects.json"
|
5 | import { apiRunner } from "./api-runner-browser"
|
6 | import emitter from "./emitter"
|
7 | import { navigate as reachNavigate } from "@reach/router"
|
8 | import { parsePath } from "gatsby-link"
|
9 |
|
10 |
|
11 | const redirectMap = redirects.reduce((map, redirect) => {
|
12 | map[redirect.fromPath] = redirect
|
13 | return map
|
14 | }, {})
|
15 |
|
16 | function maybeRedirect(pathname) {
|
17 | const redirect = redirectMap[pathname]
|
18 |
|
19 | if (redirect != null) {
|
20 | if (process.env.NODE_ENV !== `production`) {
|
21 | const pageResources = loader.loadPageSync(pathname)
|
22 |
|
23 | if (pageResources != null) {
|
24 | console.error(
|
25 | `The route "${pathname}" matches both a page and a redirect; this is probably not intentional.`
|
26 | )
|
27 | }
|
28 | }
|
29 |
|
30 | window.___replace(redirect.toPath)
|
31 | return true
|
32 | } else {
|
33 | return false
|
34 | }
|
35 | }
|
36 |
|
37 | const onPreRouteUpdate = (location, prevLocation) => {
|
38 | if (!maybeRedirect(location.pathname)) {
|
39 | apiRunner(`onPreRouteUpdate`, { location, prevLocation })
|
40 | }
|
41 | }
|
42 |
|
43 | const onRouteUpdate = (location, prevLocation) => {
|
44 | if (!maybeRedirect(location.pathname)) {
|
45 | apiRunner(`onRouteUpdate`, { location, prevLocation })
|
46 |
|
47 | window.__navigatingToLink = false
|
48 | }
|
49 | }
|
50 |
|
51 | const navigate = (to, options = {}) => {
|
52 |
|
53 | if (!options.replace) {
|
54 | window.__navigatingToLink = true
|
55 | }
|
56 |
|
57 | let { pathname } = parsePath(to)
|
58 | const redirect = redirectMap[pathname]
|
59 |
|
60 |
|
61 |
|
62 | if (redirect) {
|
63 | to = redirect.toPath
|
64 | pathname = parsePath(to).pathname
|
65 | }
|
66 |
|
67 |
|
68 |
|
69 | if (window.___swUpdated) {
|
70 | window.location = pathname
|
71 | return
|
72 | }
|
73 |
|
74 |
|
75 |
|
76 | const timeoutId = setTimeout(() => {
|
77 | emitter.emit(`onDelayedLoadPageResources`, { pathname })
|
78 | apiRunner(`onRouteUpdateDelayed`, {
|
79 | location: window.location,
|
80 | })
|
81 | }, 1000)
|
82 |
|
83 | loader.loadPage(pathname).then(pageResources => {
|
84 |
|
85 |
|
86 |
|
87 |
|
88 |
|
89 |
|
90 | if (!pageResources || pageResources.status === `error`) {
|
91 | window.history.replaceState({}, ``, location.href)
|
92 | window.location = pathname
|
93 | }
|
94 |
|
95 |
|
96 | if (process.env.NODE_ENV === `production` && pageResources) {
|
97 | if (
|
98 | pageResources.page.webpackCompilationHash !==
|
99 | window.___webpackCompilationHash
|
100 | ) {
|
101 |
|
102 | if (
|
103 | `serviceWorker` in navigator &&
|
104 | navigator.serviceWorker.controller !== null &&
|
105 | navigator.serviceWorker.controller.state === `activated`
|
106 | ) {
|
107 | navigator.serviceWorker.controller.postMessage({
|
108 | gatsbyApi: `clearPathResources`,
|
109 | })
|
110 | }
|
111 |
|
112 | console.log(`Site has changed on server. Reloading browser`)
|
113 | window.location = pathname
|
114 | }
|
115 | }
|
116 | reachNavigate(to, options)
|
117 | clearTimeout(timeoutId)
|
118 | })
|
119 | }
|
120 |
|
121 | function shouldUpdateScroll(prevRouterProps, { location }) {
|
122 | const { pathname, hash } = location
|
123 | const results = apiRunner(`shouldUpdateScroll`, {
|
124 | prevRouterProps,
|
125 |
|
126 | pathname,
|
127 | routerProps: { location },
|
128 | getSavedScrollPosition: args => this._stateStorage.read(args),
|
129 | })
|
130 | if (results.length > 0) {
|
131 |
|
132 |
|
133 | return results[results.length - 1]
|
134 | }
|
135 |
|
136 | if (prevRouterProps) {
|
137 | const {
|
138 | location: { pathname: oldPathname },
|
139 | } = prevRouterProps
|
140 | if (oldPathname === pathname) {
|
141 |
|
142 |
|
143 | return hash ? decodeURI(hash.slice(1)) : [0, 0]
|
144 | }
|
145 | }
|
146 | return true
|
147 | }
|
148 |
|
149 | function init() {
|
150 |
|
151 | window.__navigatingToLink = false
|
152 |
|
153 | window.___push = to => navigate(to, { replace: false })
|
154 | window.___replace = to => navigate(to, { replace: true })
|
155 | window.___navigate = (to, options) => navigate(to, options)
|
156 |
|
157 |
|
158 | maybeRedirect(window.location.pathname)
|
159 | }
|
160 |
|
161 | class RouteAnnouncer extends React.Component {
|
162 | constructor(props) {
|
163 | super(props)
|
164 | this.announcementRef = React.createRef()
|
165 | }
|
166 |
|
167 | componentDidUpdate(prevProps, nextProps) {
|
168 | requestAnimationFrame(() => {
|
169 | let pageName = `new page at ${this.props.location.pathname}`
|
170 | if (document.title) {
|
171 | pageName = document.title
|
172 | }
|
173 | const pageHeadings = document
|
174 | .getElementById(`gatsby-focus-wrapper`)
|
175 | .getElementsByTagName(`h1`)
|
176 | if (pageHeadings && pageHeadings.length) {
|
177 | pageName = pageHeadings[0].textContent
|
178 | }
|
179 | const newAnnouncement = `Navigated to ${pageName}`
|
180 | const oldAnnouncement = this.announcementRef.current.innerText
|
181 | if (oldAnnouncement !== newAnnouncement) {
|
182 | this.announcementRef.current.innerText = newAnnouncement
|
183 | }
|
184 | })
|
185 | }
|
186 |
|
187 | render() {
|
188 | return (
|
189 | <div
|
190 | id="gatsby-announcer"
|
191 | style={{
|
192 | position: `absolute`,
|
193 | width: 1,
|
194 | height: 1,
|
195 | padding: 0,
|
196 | overflow: `hidden`,
|
197 | clip: `rect(0, 0, 0, 0)`,
|
198 | whiteSpace: `nowrap`,
|
199 | border: 0,
|
200 | }}
|
201 | role="alert"
|
202 | aria-live="assertive"
|
203 | aria-atomic="true"
|
204 | ref={this.announcementRef}
|
205 | ></div>
|
206 | )
|
207 | }
|
208 | }
|
209 |
|
210 |
|
211 | class RouteUpdates extends React.Component {
|
212 | constructor(props) {
|
213 | super(props)
|
214 | onPreRouteUpdate(props.location, null)
|
215 | }
|
216 |
|
217 | componentDidMount() {
|
218 | onRouteUpdate(this.props.location, null)
|
219 | }
|
220 |
|
221 | componentDidUpdate(prevProps, prevState, shouldFireRouteUpdate) {
|
222 | if (shouldFireRouteUpdate) {
|
223 | onRouteUpdate(this.props.location, prevProps.location)
|
224 | }
|
225 | }
|
226 |
|
227 | getSnapshotBeforeUpdate(prevProps) {
|
228 | if (this.props.location.pathname !== prevProps.location.pathname) {
|
229 | onPreRouteUpdate(this.props.location, prevProps.location)
|
230 | return true
|
231 | }
|
232 |
|
233 | return false
|
234 | }
|
235 |
|
236 | render() {
|
237 | return (
|
238 | <React.Fragment>
|
239 | {this.props.children}
|
240 | <RouteAnnouncer location={location} />
|
241 | </React.Fragment>
|
242 | )
|
243 | }
|
244 | }
|
245 |
|
246 | RouteUpdates.propTypes = {
|
247 | location: PropTypes.object.isRequired,
|
248 | }
|
249 |
|
250 | export { init, shouldUpdateScroll, RouteUpdates }
|