feat: support graceful 404 pages for resources

This commit is contained in:
Khinshan Khan
2019-12-01 18:30:22 -05:00
committed by Jean-Philippe Sirois
parent 0c3d8a3b4f
commit c96fb2eabf
6 changed files with 157 additions and 7 deletions
+1
View File
@@ -72,6 +72,7 @@ module.exports = {
},
`gatsby-transformer-sharp`,
`gatsby-plugin-react-svg`,
`gatsby-plugin-remove-trailing-slashes`,
`gatsby-plugin-sharp`,
{
resolve: `gatsby-plugin-manifest`,
+9 -3
View File
@@ -136,10 +136,16 @@ exports.onCreateNode = async ({ node, getNode, actions }) => {
}
}
exports.onCreatePage = ({ page, actions }) => {
const { createPage } = actions
exports.onCreatePage = async ({ page, actions }) => {
const { createPage, deletePage } = actions
page.context.layout = resolveLayout(page.path)
if (page.path.match(/^\/resources\/404/)) {
const oldPage = { ...page }
page.matchPath = `/resources/*`
deletePage(oldPage)
} else {
page.context.layout = resolveLayout(page.path)
}
createPage(page)
}
+20
View File
@@ -9167,6 +9167,26 @@
"svg-react-loader": "^0.4.4"
}
},
"gatsby-plugin-remove-trailing-slashes": {
"version": "2.1.15",
"resolved": "https://registry.npmjs.org/gatsby-plugin-remove-trailing-slashes/-/gatsby-plugin-remove-trailing-slashes-2.1.15.tgz",
"integrity": "sha512-IMuubnIdsgRyEGWYZZD5QhynO3Mw3rf+cM5uBk594I97MR/BcqYVjXVhVKp4zGpip2HQNzPPgHWODS8tn6n30g==",
"dev": true,
"requires": {
"@babel/runtime": "^7.7.4"
},
"dependencies": {
"@babel/runtime": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.7.4.tgz",
"integrity": "sha512-r24eVUUr0QqNZa+qrImUk8fn5SPhHq+IfYvIoIMg0do3GdK9sMdiLKP3GYVVaxpPKORgm8KRKaNTEhAjgIpLMw==",
"dev": true,
"requires": {
"regenerator-runtime": "^0.13.2"
}
}
}
},
"gatsby-plugin-sharp": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/gatsby-plugin-sharp/-/gatsby-plugin-sharp-2.3.2.tgz",
+1
View File
@@ -73,6 +73,7 @@
"@types/styled-components": "^4.4.0",
"babel-plugin-styled-components": "^1.10.6",
"discord.js": "^11.5.1",
"gatsby-plugin-remove-trailing-slashes": "^2.1.14",
"prettier": "^1.19.1",
"tslint": "^5.20.1",
"tslint-config-prettier": "^1.18.0",
+122
View File
@@ -0,0 +1,122 @@
import { graphql, Link } from "gatsby"
import React, { Component, Fragment } from "react"
import { Location } from "@reach/router"
import { SEO } from "../../components/SEO"
// helper function to make matrix generation easier
// credits to https://stackoverflow.com/a/13808461
function makeMatrix(w, h, val) {
return Array(h)
.fill(null)
.map(() => Array(w).fill(val))
}
// calculate semantic difference
// based on one character edits
// efficient refactoring inspired by MIT licensed repo:
// https://github.com/trekhleb/javascript-algorithms
function levenshteinDistance(term1, term2) {
/* base case: empty strings */
if (term1.length == 0) return term2.length
if (term2.length == 0) return term1.length
// this will be our comparison matrix
const matrix = makeMatrix(term1.length + 1, term2.length + 1, null)
// make zeroeth row based off first term
for (let i = 0; i <= term1.length; i += 1) {
matrix[0][i] = i
}
// make zeroeth column based off second term
for (let j = 0; j <= term2.length; j += 1) {
matrix[j][0] = j
}
// iterative with full matrix implementation
// https://en.wikipedia.org/wiki/Levenshtein_distance
for (let j = 1; j <= term2.length; j += 1) {
for (let i = 1; i <= term1.length; i += 1) {
const substitutionCost = term1[i - 1] === term2[j - 1] ? 0 : 1
matrix[j][i] = Math.min(
matrix[j - 1][i] + 1, // deletion
matrix[j][i - 1] + 1, // insertion
matrix[j - 1][i - 1] + substitutionCost
) // substitution
}
}
return matrix[term2.length][term1.length]
}
function getPossibleResources(location, data) {
// the data prop has the graphql result
// we're abstracting it to `link_array` to just have array of edges matched
// must use `.node.absolutePath` on each edge to get each node's absolute link
// we will compare this link
// however, the relative links are more user friendly
const link_array = data.allFile.edges
// location prop has several attributes
// location.origin is base url
// location.href is the window location
// location.pathname is link after protocol and hostname
// by replacing `"/resources/"`,
// we are left with only the relative path to `resources`
const search = location.pathname.replace("/resources/", "")
// filter based on distance
// levenshtein distance of x means how 'off' it was
const display_array = link_array.filter(
(value, index) =>
levenshteinDistance(
search.toLowerCase(),
value.node.relativePath.toLowerCase()
) < 8
)
// helpful message if no matches found
const found = display_array.length == 0 ? "Oops, nothing similar found." : ""
return (
<Fragment>
<h3>Based off of "{search}" you may have meant:</h3>
<ul>
{display_array.map((value, index) => {
return (
<li key={index}>
<Link to={`${"resources/" + value.node.relativePath}`}>
{value.node.relativePath}
</Link>
</li>
)
})}
</ul>
{found}
</Fragment>
)
}
export default ({ location, data }) => (
<Fragment>
<SEO title="404: Resource Not found" />
<h1>RESOURCE NOT FOUND</h1>
<p>You just hit a route that doesn&#39;t exist... the sadness.</p>
<Location>
{({ navigate, location }) => getPossibleResources(location, data)}
</Location>
</Fragment>
)
export const query = graphql`
query {
allFile(filter: { sourceInstanceName: { eq: "resources" } }) {
edges {
node {
relativePath
}
}
}
}
`
@@ -1,9 +1,9 @@
import React, { Fragment } from "react"
import packageJson from "../../package.json"
import { Link } from "../components/Link"
import { ResourcesList } from "../components/ResourcesList"
import { SEO } from "../components/SEO"
import packageJson from "../../../package.json"
import { Link } from "../../components/Link"
import { ResourcesList } from "../../components/ResourcesList"
import { SEO } from "../../components/SEO"
function ResourcesPage() {
return (