diff --git a/gatsby-config.js b/gatsby-config.js index 382b7f6..726ff80 100644 --- a/gatsby-config.js +++ b/gatsby-config.js @@ -2,7 +2,7 @@ module.exports = { siteMetadata: { title: `Nature of Code`, siteUrl: `https://natureofcode.com`, - description: ``, + description: `Simulating Natural Systems with JavaScript`, }, plugins: [ { @@ -12,6 +12,13 @@ module.exports = { path: `${__dirname}/content/`, }, }, + { + resolve: `gatsby-source-filesystem`, + options: { + name: `images`, + path: `${__dirname}/src/images/`, + }, + }, `gatsby-plugin-image`, `gatsby-plugin-sharp`, `gatsby-transformer-sharp`, diff --git a/gatsby/lib/parse-content.mjs b/gatsby/lib/parse-content.mjs index d9eb412..c46524a 100644 --- a/gatsby/lib/parse-content.mjs +++ b/gatsby/lib/parse-content.mjs @@ -1,4 +1,5 @@ import { unified } from 'unified'; +import { h } from 'hastscript'; import { visit } from 'unist-util-visit'; import { remove } from 'unist-util-remove'; import rehypeParse from 'rehype-parse'; @@ -16,12 +17,26 @@ export function parseContent(html) { if ( node.properties.className && Array.isArray(node.properties.className) && - (node.properties.className.includes('pdf-only') || - node.properties.className.includes('chapter-opening-figure')) + node.properties.className.includes('pdf-only') ) { remove(tree, node); } + if ( + node.properties.className && + Array.isArray(node.properties.className) && + node.properties.className.includes('chapter-opening-figure') + ) { + // Find the h3 element within the children of the node + node.children.forEach((child, index) => { + if (child.tagName === 'h3') { + // Replace the h3 tag with a span tag + node.children[index] = h('span', child.properties, child.children); + } + }); + node.children.push(h('hr')); + } + if ( node.properties.dataType === 'embed' && node.properties.dataExamplePath @@ -42,6 +57,19 @@ export function parseContent(html) { } }); + /** + * using colon as separator instead of period + * + * e.g. + * 'Chapter 4. Particle Systems' => 'Chapter 4: Particle Systems' + */ + visit(tree, { tagName: 'h1' }, (node) => { + const originalTitle = node.children[0].value; + const modifiedTitle = originalTitle.replace(/(Chapter \d+)\./, '$1:'); + + node.children[0].value = modifiedTitle; + }); + visit(tree, { tagName: 'span' }, (node) => { if ( node.properties.className && @@ -108,6 +136,20 @@ export function parseContent(html) { }); }); + /** + * Generate a short description + */ + const paragraphs = []; + visit(ast, [{ tagName: 'p' }], (node, _, parent) => { + if ( + parent.properties.dataType === 'chapter' || + parent.properties.dataType === 'page' + ) { + paragraphs.push(toString(node).replace(/\s+/g, ' ').trim()); + } + }); + const description = paragraphs.join(' ').trim().substring(0, 150); + const transformedAst = unified() .use(replaceMedia) .use(externalLinkInNewTab) @@ -127,6 +169,7 @@ export function parseContent(html) { return { ast: transformedAst, toc, + description, examples, }; } diff --git a/gatsby/on-create-node.js b/gatsby/on-create-node.js index 0212ad0..6941068 100644 --- a/gatsby/on-create-node.js +++ b/gatsby/on-create-node.js @@ -15,7 +15,7 @@ module.exports = async ({ // load the html source to every HTML file node const content = await loadNodeContent(node); - const { ast, toc, examples } = parseContent(content); + const { ast, toc, examples, description } = parseContent(content); createNodeField({ node, @@ -29,6 +29,12 @@ module.exports = async ({ value: JSON.stringify(toc), }); + createNodeField({ + node, + name: 'description', + value: description, + }); + for (let example of examples) { const exampleNode = { id: createNodeId(example.relativeDirectory), diff --git a/src/components/Head.js b/src/components/Head.js index 5c34446..879877b 100644 --- a/src/components/Head.js +++ b/src/components/Head.js @@ -1,6 +1,7 @@ import React from 'react'; import { Helmet } from 'react-helmet'; import { useStaticQuery, graphql } from 'gatsby'; +import { getSrc } from 'gatsby-plugin-image'; const Head = ({ title, description }) => { const data = useStaticQuery(graphql` @@ -12,6 +13,11 @@ const Head = ({ title, description }) => { description } } + previewImage: file(relativePath: { eq: "cover.png" }) { + childImageSharp { + gatsbyImageData(width: 1200) + } + } } `); @@ -25,6 +31,9 @@ const Head = ({ title, description }) => { }, } = data; + const metaDescription = description || defaultDescription; + const previewImageSrc = getSrc(data.previewImage); + return ( { defaultTitle={defaultTitle} titleTemplate={`%s / ${defaultTitle}`} > - - {title} - + + - + - + ); }; diff --git a/src/components/Header.js b/src/components/Header.js index c8c8740..17a41c2 100644 --- a/src/components/Header.js +++ b/src/components/Header.js @@ -14,20 +14,20 @@ const MenuButton = (props) => { title="Toggle menu" > { href="https://www.patreon.com/codingtrain" className="flex items-center gap-1.5 text-sm text-gray-500 hover:underline" > - + SUPPORT @@ -70,7 +70,7 @@ const Header = (props) => { href="https://github.com/nature-of-code/noc-book-2" className="flex items-center gap-1.5 text-sm text-gray-500 hover:underline" > - + GITHUB @@ -82,7 +82,7 @@ const Header = (props) => { Coding Train's logo CODING TRAIN diff --git a/src/components/SideNav.js b/src/components/SideNav.js index c37b65e..cbd3e6d 100644 --- a/src/components/SideNav.js +++ b/src/components/SideNav.js @@ -54,26 +54,50 @@ const SideNav = (props) => { } `); + const pages = data.allBookSection.edges + .filter(({ node }) => node.type === 'page') + .map(({ node }) => node); + + const introductionPageIndex = pages.findIndex( + (node) => node.title === 'Introduction', + ); + + const chapters = data.allBookSection.edges + .filter(({ node }) => node.type === 'chapter') + .map(({ node }) => node); + return ( -