1 | import React from "react"
|
2 | import PropTypes from "prop-types"
|
3 | import { graphql, Link, navigate } from "gatsby"
|
4 | import queryString from "query-string"
|
5 |
|
6 | class Dev404Page extends React.Component {
|
7 | static propTypes = {
|
8 | data: PropTypes.object,
|
9 | custom404: PropTypes.element,
|
10 | location: PropTypes.object,
|
11 | }
|
12 |
|
13 | constructor(props) {
|
14 | super(props)
|
15 | const { data, location } = this.props
|
16 | const pagePaths = data.allSitePage.nodes.map(node => node.path)
|
17 | const urlState = queryString.parse(location.search)
|
18 |
|
19 | const initialPagePathSearchTerms = urlState.filter ? urlState.filter : ``
|
20 |
|
21 | this.state = {
|
22 | hasMounted: false,
|
23 | showCustom404: process.env.GATSBY_DISABLE_CUSTOM_404 || false,
|
24 | initPagePaths: pagePaths,
|
25 | pagePathSearchTerms: initialPagePathSearchTerms,
|
26 | pagePaths: this.getFilteredPagePaths(
|
27 | pagePaths,
|
28 | initialPagePathSearchTerms
|
29 | ),
|
30 | }
|
31 | this.showCustom404 = this.showCustom404.bind(this)
|
32 | this.handlePagePathSearch = this.handlePagePathSearch.bind(this)
|
33 | this.handleSearchTermChange = this.handleSearchTermChange.bind(this)
|
34 | }
|
35 |
|
36 | componentDidMount() {
|
37 | this.setState({
|
38 | hasMounted: true,
|
39 | })
|
40 | }
|
41 |
|
42 | showCustom404() {
|
43 | this.setState({ showCustom404: true })
|
44 | }
|
45 |
|
46 | handleSearchTermChange(event) {
|
47 | const searchValue = event.target.value
|
48 |
|
49 | this.setSearchUrl(searchValue)
|
50 |
|
51 | this.setState({
|
52 | pagePathSearchTerms: searchValue,
|
53 | })
|
54 | }
|
55 |
|
56 | handlePagePathSearch(event) {
|
57 | event.preventDefault()
|
58 | const allPagePaths = [...this.state.initPagePaths]
|
59 | this.setState({
|
60 | pagePaths: this.getFilteredPagePaths(
|
61 | allPagePaths,
|
62 | this.state.pagePathSearchTerms
|
63 | ),
|
64 | })
|
65 | }
|
66 |
|
67 | getFilteredPagePaths(allPagePaths, pagePathSearchTerms) {
|
68 | const searchTerm = new RegExp(`${pagePathSearchTerms}`)
|
69 | return allPagePaths.filter(pagePath => searchTerm.test(pagePath))
|
70 | }
|
71 |
|
72 | setSearchUrl(searchValue) {
|
73 | const {
|
74 | location: { pathname, search },
|
75 | } = this.props
|
76 |
|
77 | const searchMap = queryString.parse(search)
|
78 | searchMap.filter = searchValue
|
79 |
|
80 | const newSearch = queryString.stringify(searchMap)
|
81 |
|
82 | if (search !== `?${newSearch}`) {
|
83 | navigate(`${pathname}?${newSearch}`, { replace: true })
|
84 | }
|
85 | }
|
86 |
|
87 | render() {
|
88 | if (!this.state.hasMounted) {
|
89 | return null
|
90 | }
|
91 |
|
92 | const { pathname } = this.props.location
|
93 | let newFilePath
|
94 | let newAPIPath
|
95 | if (pathname === `/`) {
|
96 | newFilePath = `src/pages/index.js`
|
97 | } else if (pathname.slice(0, 4) === `/api`) {
|
98 | newAPIPath = `src${pathname}.js`
|
99 | } else if (pathname.slice(-1) === `/`) {
|
100 | newFilePath = `src/pages${pathname.slice(0, -1)}.js`
|
101 | } else {
|
102 | newFilePath = `src/pages${pathname}.js`
|
103 | }
|
104 |
|
105 | return this.state.showCustom404 ? (
|
106 | this.props.custom404
|
107 | ) : (
|
108 | <div>
|
109 | <h1>Gatsby.js development 404 page</h1>
|
110 | <p>
|
111 | There's not a page or function yet at{` `}
|
112 | <code>{pathname}</code>
|
113 | </p>
|
114 | {this.props.custom404 ? (
|
115 | <p>
|
116 | <button onClick={this.showCustom404}>
|
117 | Preview custom 404 page
|
118 | </button>
|
119 | </p>
|
120 | ) : (
|
121 | <p>
|
122 | {`A custom 404 page wasn't detected - if you would like to add one, create a component in your site directory at `}
|
123 | <code>src/pages/404.js</code>.
|
124 | </p>
|
125 | )}
|
126 | {newFilePath && (
|
127 | <div>
|
128 | <h2>Create a page at this url</h2>
|
129 | <p>
|
130 | Create a React.js component like the following in your site
|
131 | directory at
|
132 | {` `}"<code>{newFilePath}</code>"{` `}
|
133 | and then refresh to show the new page component you created.
|
134 | </p>
|
135 | <pre
|
136 | style={{
|
137 | border: `1px solid lightgray`,
|
138 | padding: `8px`,
|
139 | maxWidth: `80ch`,
|
140 | background: `#f3f3f3`,
|
141 | }}
|
142 | >
|
143 | <code
|
144 | dangerouslySetInnerHTML={{
|
145 | __html: `import * as React from "react"
|
146 |
|
147 | export default function Component () {
|
148 | return "Hello world"
|
149 | }`,
|
150 | }}
|
151 | />
|
152 | </pre>
|
153 | </div>
|
154 | )}
|
155 | {newAPIPath && (
|
156 | <div>
|
157 | <h2>Create an API function at this url</h2>
|
158 | <p>
|
159 | Create a javascript file like the following in your site directory
|
160 | at
|
161 | {` `}"<code>{newAPIPath}</code>"{` `}
|
162 | and refresh to execute the new API function you created.
|
163 | </p>
|
164 | <pre
|
165 | style={{
|
166 | border: `1px solid lightgray`,
|
167 | padding: `8px`,
|
168 | maxWidth: `80ch`,
|
169 | background: `#f3f3f3`,
|
170 | }}
|
171 | >
|
172 | <code
|
173 | dangerouslySetInnerHTML={{
|
174 | __html: `
|
175 | export default function API (req, res) {
|
176 | res.json({ hello: "world" })
|
177 | }`,
|
178 | }}
|
179 | />
|
180 | </pre>
|
181 | </div>
|
182 | )}
|
183 | {this.state.initPagePaths.length > 0 && (
|
184 | <div>
|
185 | <hr />
|
186 | <p>
|
187 | If you were trying to reach another page or function, perhaps you
|
188 | can find it below.
|
189 | </p>
|
190 | <h2>Functions ({this.props.data.allSiteFunction.nodes.length})</h2>
|
191 | <ul>
|
192 | {this.props.data.allSiteFunction.nodes.map(node => {
|
193 | const functionRoute = `/api/${node.functionRoute}`
|
194 | return (
|
195 | <li key={functionRoute}>
|
196 | <a href={functionRoute}>{functionRoute}</a>
|
197 | </li>
|
198 | )
|
199 | })}
|
200 | </ul>
|
201 | <h2>
|
202 | Pages (
|
203 | {this.state.pagePaths.length != this.state.initPagePaths.length
|
204 | ? `${this.state.pagePaths.length}/${this.state.initPagePaths.length}`
|
205 | : this.state.initPagePaths.length}
|
206 | )
|
207 | </h2>
|
208 |
|
209 | <form onSubmit={this.handlePagePathSearch}>
|
210 | <label>
|
211 | Search:
|
212 | <input
|
213 | type="text"
|
214 | id="search"
|
215 | placeholder="Search pages..."
|
216 | value={this.state.pagePathSearchTerms}
|
217 | onChange={this.handleSearchTermChange}
|
218 | />
|
219 | </label>
|
220 | <input type="submit" value="Submit" />
|
221 | </form>
|
222 | <ul>
|
223 | {this.state.pagePaths.map(
|
224 | (pagePath, index) =>
|
225 | index < 100 && (
|
226 | <li key={pagePath}>
|
227 | <Link to={pagePath}>{pagePath}</Link>
|
228 | </li>
|
229 | )
|
230 | )}
|
231 | {this.state.pagePaths.length > 100 && (
|
232 | <p style={{ fontWeight: `bold` }}>
|
233 | ... and {this.state.pagePaths.length - 100} more.
|
234 | </p>
|
235 | )}
|
236 | </ul>
|
237 | </div>
|
238 | )}
|
239 | </div>
|
240 | )
|
241 | }
|
242 | }
|
243 |
|
244 | export default Dev404Page
|
245 |
|
246 |
|
247 |
|
248 | export const pagesQuery = graphql`
|
249 | query PagesQuery {
|
250 | allSiteFunction {
|
251 | nodes {
|
252 | functionRoute
|
253 | }
|
254 | }
|
255 | allSitePage(filter: { path: { regex: "/^(?!\/dev-404-page).+$/" } }) {
|
256 | nodes {
|
257 | path
|
258 | }
|
259 | }
|
260 | }
|
261 | `
|
262 |
|