From e5450a099ad74f9fa302cda525d3b549a71203b7 Mon Sep 17 00:00:00 2001 From: Khinshan Khan Date: Sat, 25 Jan 2020 15:16:47 -0500 Subject: [PATCH 1/8] refact(404): abstract query from possible corrections logic --- src/pages/resources/404.tsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/pages/resources/404.tsx b/src/pages/resources/404.tsx index 607fc9c..aaa6267 100644 --- a/src/pages/resources/404.tsx +++ b/src/pages/resources/404.tsx @@ -57,7 +57,8 @@ function levenshteinDistance(term1: string, term2: string) { return matrix[term2.length][term1.length] } -function getPossibleResources( +function getPossibleCorrections( + basepath: string, location: WindowLocation, data: { allFile: FileConnection } ) { @@ -74,7 +75,7 @@ function getPossibleResources( // 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/", "") + const search = location.pathname.replace(`/${basepath}/`, "") // filter based on distance // levenshtein distance of x means how 'off' it was @@ -96,7 +97,7 @@ function getPossibleResources( {displayArray.map((value, index) => { return (
  • - + {value.node.relativePath}
  • @@ -114,7 +115,7 @@ export default ({ data }: ComponentQuery<{ allFile: FileConnection }>) => (

    RESOURCE NOT FOUND

    You just hit a route that doesn't exist... the sadness.

    - {({ location }) => getPossibleResources(location, data)} + {({ location }) => getPossibleCorrections("resources", location, data)} ) From 713f9c1629488807ebb3b250dbf9f0c3933c18e5 Mon Sep 17 00:00:00 2001 From: Khinshan Khan Date: Sat, 25 Jan 2020 15:34:34 -0500 Subject: [PATCH 2/8] refact(404): abstract 404 link correction logic for collections to a component --- src/components/404LinkCorrection/index.tsx | 108 +++++++++++++++++++++ src/pages/resources/404.tsx | 108 +-------------------- 2 files changed, 111 insertions(+), 105 deletions(-) create mode 100644 src/components/404LinkCorrection/index.tsx diff --git a/src/components/404LinkCorrection/index.tsx b/src/components/404LinkCorrection/index.tsx new file mode 100644 index 0000000..7d3339e --- /dev/null +++ b/src/components/404LinkCorrection/index.tsx @@ -0,0 +1,108 @@ +import { WindowLocation } from "@reach/router" +import { Link } from "gatsby" +import React, { Fragment } from "react" + +import { FileConnection } from "../../../generated/graphql" + +// helper function to make matrix generation easier +// credits to https://stackoverflow.com/a/13808461 +function makeMatrix(w: number, h: number, val: any = null) { + 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: string, term2: string) { + /* 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] +} + +export const getPossibleCorrections = ( + basepath: string, + location: WindowLocation, + data: { allFile: FileConnection } +) => { + // the data prop has the graphql result + // we're abstracting it to `linkArray` 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 linkArray = 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(`/${basepath}/`, "") + + // filter based on distance + // levenshtein distance of x means how 'off' it was + const displayArray = linkArray.filter( + value => + levenshteinDistance( + search.toLowerCase(), + value.node!.relativePath!.toLowerCase() + ) < 8 + ) + + // helpful message if no matches found + const found = displayArray.length === 0 ? "Oops, nothing similar found." : "" + + return ( + +

    Based off of "{search}" you may have meant:

    +
      + {displayArray.map((value, index) => { + return ( +
    • + + {value.node.relativePath} + +
    • + ) + })} +
    + {found} +
    + ) +} diff --git a/src/pages/resources/404.tsx b/src/pages/resources/404.tsx index aaa6267..bd3990f 100644 --- a/src/pages/resources/404.tsx +++ b/src/pages/resources/404.tsx @@ -1,114 +1,12 @@ -import { Location, WindowLocation } from "@reach/router" -import { graphql, Link } from "gatsby" +import { Location } from "@reach/router" +import { graphql } from "gatsby" import React, { Fragment } from "react" import { FileConnection } from "../../../generated/graphql" import { ComponentQuery } from "../../../typings" +import { getPossibleCorrections } from "../../components/404LinkCorrection" import { SEO } from "../../components/SEO" -// helper function to make matrix generation easier -// credits to https://stackoverflow.com/a/13808461 -function makeMatrix(w: number, h: number, val: any = null) { - 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: string, term2: string) { - /* 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 getPossibleCorrections( - basepath: string, - location: WindowLocation, - data: { allFile: FileConnection } -) { - // the data prop has the graphql result - // we're abstracting it to `linkArray` 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 linkArray = 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(`/${basepath}/`, "") - - // filter based on distance - // levenshtein distance of x means how 'off' it was - const displayArray = linkArray.filter( - value => - levenshteinDistance( - search.toLowerCase(), - value.node!.relativePath!.toLowerCase() - ) < 8 - ) - - // helpful message if no matches found - const found = displayArray.length === 0 ? "Oops, nothing similar found." : "" - - return ( - -

    Based off of "{search}" you may have meant:

    -
      - {displayArray.map((value, index) => { - return ( -
    • - - {value.node.relativePath} - -
    • - ) - })} -
    - {found} -
    - ) -} - export default ({ data }: ComponentQuery<{ allFile: FileConnection }>) => ( From 56df4abeb6cae27593397df002071e10c4518df2 Mon Sep 17 00:00:00 2001 From: Khinshan Khan Date: Sat, 25 Jan 2020 15:40:40 -0500 Subject: [PATCH 3/8] feat(404): add threshold parameter, number of characters a link can be editted to be correct --- src/components/404LinkCorrection/index.tsx | 5 +++-- src/pages/resources/404.tsx | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/404LinkCorrection/index.tsx b/src/components/404LinkCorrection/index.tsx index 7d3339e..f54a637 100644 --- a/src/components/404LinkCorrection/index.tsx +++ b/src/components/404LinkCorrection/index.tsx @@ -58,7 +58,8 @@ function levenshteinDistance(term1: string, term2: string) { export const getPossibleCorrections = ( basepath: string, location: WindowLocation, - data: { allFile: FileConnection } + data: { allFile: FileConnection }, + threshold: number ) => { // the data prop has the graphql result // we're abstracting it to `linkArray` to just have array of edges matched @@ -82,7 +83,7 @@ export const getPossibleCorrections = ( levenshteinDistance( search.toLowerCase(), value.node!.relativePath!.toLowerCase() - ) < 8 + ) < threshold ) // helpful message if no matches found diff --git a/src/pages/resources/404.tsx b/src/pages/resources/404.tsx index bd3990f..ebb1cd0 100644 --- a/src/pages/resources/404.tsx +++ b/src/pages/resources/404.tsx @@ -13,7 +13,7 @@ export default ({ data }: ComponentQuery<{ allFile: FileConnection }>) => (

    RESOURCE NOT FOUND

    You just hit a route that doesn't exist... the sadness.

    - {({ location }) => getPossibleCorrections("resources", location, data)} + {({ location }) => getPossibleCorrections("resources", location, data, 8)}
    ) From a796facdb12625c0ca55b5a3df00a52cb747ea2e Mon Sep 17 00:00:00 2001 From: Khinshan Khan Date: Sat, 25 Jan 2020 15:49:53 -0500 Subject: [PATCH 4/8] feat(404): allow for suffix of matching to be passed in to make archive matches more accurate --- src/components/404LinkCorrection/index.tsx | 4 ++-- src/pages/resources/404.tsx | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/404LinkCorrection/index.tsx b/src/components/404LinkCorrection/index.tsx index f54a637..2d00eb0 100644 --- a/src/components/404LinkCorrection/index.tsx +++ b/src/components/404LinkCorrection/index.tsx @@ -74,7 +74,7 @@ export const getPossibleCorrections = ( // 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(`/${basepath}/`, "") + const search = location.pathname.replace(`/${basepath}`, "") // filter based on distance // levenshtein distance of x means how 'off' it was @@ -96,7 +96,7 @@ export const getPossibleCorrections = ( {displayArray.map((value, index) => { return (
  • - + {value.node.relativePath}
  • diff --git a/src/pages/resources/404.tsx b/src/pages/resources/404.tsx index ebb1cd0..c1267af 100644 --- a/src/pages/resources/404.tsx +++ b/src/pages/resources/404.tsx @@ -13,7 +13,9 @@ export default ({ data }: ComponentQuery<{ allFile: FileConnection }>) => (

    RESOURCE NOT FOUND

    You just hit a route that doesn't exist... the sadness.

    - {({ location }) => getPossibleCorrections("resources", location, data, 8)} + {({ location }) => + getPossibleCorrections("resources/", location, data, 8) + } ) From 849bd0be028a3958507eaf139d1ea104dac9b165 Mon Sep 17 00:00:00 2001 From: Khinshan Khan Date: Sat, 25 Jan 2020 15:52:02 -0500 Subject: [PATCH 5/8] feat(404): add collections 404 page to tech spotlights --- gatsby-node.js | 4 +++ src/pages/archives/404.tsx | 32 +++++++++++++++++++ .../{archives.tsx => archives/index.tsx} | 4 +-- 3 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 src/pages/archives/404.tsx rename src/pages/{archives.tsx => archives/index.tsx} (85%) diff --git a/gatsby-node.js b/gatsby-node.js index a62e9b8..f53c260 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -170,6 +170,10 @@ exports.onCreatePage = async ({ page, actions }) => { const oldPage = { ...page } page.matchPath = `/resources/*` deletePage(oldPage) + } else if (page.path.match(/^\/archives\/404/)) { + const oldPage = { ...page } + page.matchPath = `/archives/*` + deletePage(oldPage) } else { page.context.layout = resolveLayout(page.path) } diff --git a/src/pages/archives/404.tsx b/src/pages/archives/404.tsx new file mode 100644 index 0000000..3d31219 --- /dev/null +++ b/src/pages/archives/404.tsx @@ -0,0 +1,32 @@ +import { Location } from "@reach/router" +import { graphql } from "gatsby" +import React, { Fragment } from "react" + +import { FileConnection } from "../../../generated/graphql" +import { ComponentQuery } from "../../../typings" +import { SEO } from "../../components/SEO" + +import { getPossibleCorrections } from "../../components/404LinkCorrection" + +export default ({ data }: ComponentQuery<{ allFile: FileConnection }>) => ( + + +

    RESOURCE NOT FOUND

    +

    You just hit a route that doesn't exist... the sadness.

    + + {({ location }) => getPossibleCorrections("archives/", location, data, 8)} + +
    +) + +export const query = graphql` + query { + allFile(filter: { sourceInstanceName: { eq: "what-is-archive" } }) { + edges { + node { + relativePath + } + } + } + } +` diff --git a/src/pages/archives.tsx b/src/pages/archives/index.tsx similarity index 85% rename from src/pages/archives.tsx rename to src/pages/archives/index.tsx index f7c89b4..a419c56 100644 --- a/src/pages/archives.tsx +++ b/src/pages/archives/index.tsx @@ -1,7 +1,7 @@ import React, { Fragment } from "react" -import { Link } from "../components/Link" -import { SEO } from "../components/SEO" +import { Link } from "../../components/Link" +import { SEO } from "../../components/SEO" function ArchivesPage() { return ( From 8ab5ec17375cf147cb5a5966b5c054157c4656eb Mon Sep 17 00:00:00 2001 From: Khinshan Khan Date: Sun, 26 Jan 2020 10:04:15 -0500 Subject: [PATCH 6/8] fix(404): 404 for collections to properly get their layout applied --- gatsby-node.js | 4 ++-- src/components/404LinkCorrection/index.tsx | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/gatsby-node.js b/gatsby-node.js index f53c260..8874cbd 100644 --- a/gatsby-node.js +++ b/gatsby-node.js @@ -174,9 +174,9 @@ exports.onCreatePage = async ({ page, actions }) => { const oldPage = { ...page } page.matchPath = `/archives/*` deletePage(oldPage) - } else { - page.context.layout = resolveLayout(page.path) } + + page.context.layout = resolveLayout(page.path) createPage(page) } diff --git a/src/components/404LinkCorrection/index.tsx b/src/components/404LinkCorrection/index.tsx index 2d00eb0..9290b88 100644 --- a/src/components/404LinkCorrection/index.tsx +++ b/src/components/404LinkCorrection/index.tsx @@ -78,13 +78,14 @@ export const getPossibleCorrections = ( // filter based on distance // levenshtein distance of x means how 'off' it was - const displayArray = linkArray.filter( - value => + const displayArray = linkArray.filter(value => { + return ( levenshteinDistance( search.toLowerCase(), value.node!.relativePath!.toLowerCase() ) < threshold - ) + ) + }) // helpful message if no matches found const found = displayArray.length === 0 ? "Oops, nothing similar found." : "" From b154d0c51560ef39700c33d5821cc68fff6c58b4 Mon Sep 17 00:00:00 2001 From: Khinshan Khan Date: Sun, 26 Jan 2020 09:40:45 -0500 Subject: [PATCH 7/8] refact(404): convert link correction function to functional component --- src/components/404LinkCorrection/index.tsx | 21 ++++++++++++++------- src/pages/archives/404.tsx | 10 ++++++++-- src/pages/resources/404.tsx | 12 ++++++++---- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/src/components/404LinkCorrection/index.tsx b/src/components/404LinkCorrection/index.tsx index 9290b88..2fca8f2 100644 --- a/src/components/404LinkCorrection/index.tsx +++ b/src/components/404LinkCorrection/index.tsx @@ -1,6 +1,6 @@ import { WindowLocation } from "@reach/router" import { Link } from "gatsby" -import React, { Fragment } from "react" +import React, { FC, Fragment } from "react" import { FileConnection } from "../../../generated/graphql" @@ -55,12 +55,19 @@ function levenshteinDistance(term1: string, term2: string) { return matrix[term2.length][term1.length] } -export const getPossibleCorrections = ( - basepath: string, - location: WindowLocation, - data: { allFile: FileConnection }, - threshold: number -) => { +interface IPossibleCorrections { + basepath: string + location: WindowLocation + data: { allFile: FileConnection } + threshold?: number +} + +export const PossibleCorrections: FC = ({ + basepath, + location, + data, + threshold = 8, +}) => { // the data prop has the graphql result // we're abstracting it to `linkArray` to just have array of edges matched // must use `.node.absolutePath` on each edge to get each node's absolute link diff --git a/src/pages/archives/404.tsx b/src/pages/archives/404.tsx index 3d31219..7db240a 100644 --- a/src/pages/archives/404.tsx +++ b/src/pages/archives/404.tsx @@ -6,7 +6,7 @@ import { FileConnection } from "../../../generated/graphql" import { ComponentQuery } from "../../../typings" import { SEO } from "../../components/SEO" -import { getPossibleCorrections } from "../../components/404LinkCorrection" +import { PossibleCorrections } from "../../components/404LinkCorrection" export default ({ data }: ComponentQuery<{ allFile: FileConnection }>) => ( @@ -14,7 +14,13 @@ export default ({ data }: ComponentQuery<{ allFile: FileConnection }>) => (

    RESOURCE NOT FOUND

    You just hit a route that doesn't exist... the sadness.

    - {({ location }) => getPossibleCorrections("archives/", location, data, 8)} + {({ location }) => ( + + )}
    ) diff --git a/src/pages/resources/404.tsx b/src/pages/resources/404.tsx index c1267af..b8fbe40 100644 --- a/src/pages/resources/404.tsx +++ b/src/pages/resources/404.tsx @@ -4,7 +4,7 @@ import React, { Fragment } from "react" import { FileConnection } from "../../../generated/graphql" import { ComponentQuery } from "../../../typings" -import { getPossibleCorrections } from "../../components/404LinkCorrection" +import { PossibleCorrections } from "../../components/404LinkCorrection" import { SEO } from "../../components/SEO" export default ({ data }: ComponentQuery<{ allFile: FileConnection }>) => ( @@ -13,9 +13,13 @@ export default ({ data }: ComponentQuery<{ allFile: FileConnection }>) => (

    RESOURCE NOT FOUND

    You just hit a route that doesn't exist... the sadness.

    - {({ location }) => - getPossibleCorrections("resources/", location, data, 8) - } + {({ location }) => ( + + )} ) From fd656fdb668404410adcc4c5409272a7103d6525 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Sirois Date: Sun, 26 Jan 2020 12:56:32 -0500 Subject: [PATCH 8/8] fix(404): properly style link --- src/components/404LinkCorrection/index.tsx | 6 +++--- src/components/404LinkCorrection/styles.tsx | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 src/components/404LinkCorrection/styles.tsx diff --git a/src/components/404LinkCorrection/index.tsx b/src/components/404LinkCorrection/index.tsx index 2fca8f2..32b06ca 100644 --- a/src/components/404LinkCorrection/index.tsx +++ b/src/components/404LinkCorrection/index.tsx @@ -1,6 +1,6 @@ import { WindowLocation } from "@reach/router" -import { Link } from "gatsby" import React, { FC, Fragment } from "react" +import * as SC from "./styles" import { FileConnection } from "../../../generated/graphql" @@ -104,9 +104,9 @@ export const PossibleCorrections: FC = ({ {displayArray.map((value, index) => { return (
  • - + {value.node.relativePath} - +
  • ) })} diff --git a/src/components/404LinkCorrection/styles.tsx b/src/components/404LinkCorrection/styles.tsx new file mode 100644 index 0000000..34496dd --- /dev/null +++ b/src/components/404LinkCorrection/styles.tsx @@ -0,0 +1,16 @@ +import { Link } from "gatsby" +import styled from "styled-components" + +export const StyledLink = styled(Link)` + color: #0090d8; + font-weight: 700; + border-bottom: 2px solid; + text-decoration: none; + transition: color 0.3s; + + &:hover, + &:focus { + color: #5dbbea; + transition: none; + } +`