Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesallardice committed Jul 18, 2018
1 parent 3b619df commit 27f5ad8
Show file tree
Hide file tree
Showing 22 changed files with 2,748 additions and 24 deletions.
42 changes: 38 additions & 4 deletions README.md
Expand Up @@ -12,15 +12,20 @@ might better suit your needs:
- Unit testing with [Jest][jest] and [Enzyme][enz]
- Global styling with [Sass][sass]
- [Netlify CMS][ncms] support
- Blogging capability including support for
- Images in blog post Markdown
- Multiple authors
- Blog post categories
- Blog post tags

If you have more or less complex requirements one of our alternative kits, built
upon the same base, might better suit your needs:

- [gatsby-starter-base][gsbase] removes Netlify CMS support and is designed as a
barebones static site generator.

- [gatsby-starter-blog][gsb] builds upon the Netlify CMS starter to include a
blogging framework.
- [gatsby-starter-netlify-cms][gsn] removes much of the blogging configuration
but still includes the basic Netlify CMS setup.

## Usage

Expand All @@ -29,7 +34,7 @@ above recommended). If those are available on your system you can use [npx][npx]
to quickly scaffold a new site from the kit:

```sh
npx -p gatsby-cli gatsby new $DIR_NAME https://github.com/orangejellyfish/gatsby-starter-base
npx -p gatsby-cli gatsby new $DIR_NAME https://github.com/orangejellyfish/gatsby-starter-blog
cd $DIR_NAME
npm start
```
Expand All @@ -42,12 +47,21 @@ overhead associated with such flexibility we favour the following conventions:
- A "layout" is viewed as a common parent to multiple pages. It is represented
as a React component exported from a file in the `src/layouts` directory.

- A "template" is viewed as a blueprint for an individual page. It is represented
as a React component exported from a file in the `src/templates` directory.

- A "page" is viewed as an individual web page. A page is represented as a
React component exported from a file in the `src/pages` directory.

- Content may be placed inline within a page if it is not required to be
editable via the CMS.

- In some cases, such as blog posts, each editable piece of content represents
a page. In such situations editable content may be placed in the `src/pages`
directory. For example, a blog post written in Markdown might be found in
`src/pages/blog/my-post.md`. In this case the page will use a template
component.

- Editable content is placed in a Markdown file in the `src/content` directory.

- The directory structures of the `src/pages` and `src/content` directories are
Expand All @@ -59,6 +73,25 @@ overhead associated with such flexibility we favour the following conventions:
data as props from ancestor components that pull in data from GraphQL) are
placed in the `src/components` directory.

- GraphQL fragments are placed in the `src/fragments` directory. This means all
fragments are co-located so you don't have to search through all of your
components to find one.

## Blogging

This starter kits includes useful blogging functionality. Blog posts are placed
in subdirectories of `src/content/blog-posts`. Images and other attachments for
a given post should be placed alongside the post content itself. We use the
[gatsby-remark-images][gri] plugin to interpret file links within Markdown at
build time. For example, if you have an image `diagram.png` alongside `index.md`
in a blog post directory you could render that image in the post with Markdown:

```md
Take a look at the following diagram:

![This is the diagram alt text](diagram.png)
```

[gatsby]: https://www.gatsbyjs.org/
[oj]: https://orangejellyfish.com/
[gprn]: https://www.npmjs.com/package/gatsby-plugin-react-next
Expand All @@ -68,6 +101,7 @@ overhead associated with such flexibility we favour the following conventions:
[sass]: https://sass-lang.com/
[ncms]: https://www.netlifycms.org/
[gsbase]: https://github.com/orangejellyfish/gatsby-starter-base
[gsb]: https://github.com/orangejellyfish/gatsby-starter-blog
[gsn]: https://github.com/orangejellyfish/gatsby-starter-netlify-cms
[ncms]: https://www.netlifycms.org/
[npx]: https://www.npmjs.com/package/npx
[gri]: https://www.gatsbyjs.org/packages/gatsby-remark-images/
10 changes: 10 additions & 0 deletions gatsby-config.js
@@ -1,6 +1,15 @@
module.exports = {
siteMetadata: {
title: 'Gatsby starter by orangejellyfish',
blogBasePath: '/blog',
},
// Define mappings between Gatsby node fields. Mappings are computed
// recursively after splitting on "." so we can map a field on any node to
// any field on another node. The right hand side of the mapping identifies a
// single field on a node but the entire matched node will be copied into the
// field identified by the left hand side.
mapping: {
'MarkdownRemark.frontmatter.author': 'MarkdownRemark.fields.authorName',
},
plugins: [
{
Expand All @@ -22,6 +31,7 @@ module.exports = {
options: {
plugins: [
'gatsby-remark-copy-linked-files',
'gatsby-remark-images',
],
},
},
Expand Down
152 changes: 152 additions & 0 deletions gatsby-node.js
@@ -0,0 +1,152 @@
const path = require('path');
const makeSlug = require('lodash.kebabcase');
const { createFilePath } = require('gatsby-source-filesystem');

// Map of path matchers to templates. This is used to determine which template
// to use when rendering a page, favouring convention (by file path) over
// configuration.
const pathMatchers = [
{
matcher: /\/content\/blog-posts\//,
template: 'blog-post',
},
];

// Gatsby hook for creating pages at build time. This is invoked after the
// GraphQL schema has been inferred so we can query the data in order to create
// pages from it. We query for all Markdown "page" nodes and create pages from
// them. By convention we expect all Markdown nodes within the "pages" directory
// to represent pages and all others to respresent content that will be pulled
// into other pages. We can therefore filter all the Markdown nodes by file
// path.
exports.createPages = ({
boundActionCreators: { createPage },
graphql,
}) => graphql(`
{
site {
config: siteMetadata {
blogBasePath
}
}
markdown: allMarkdownRemark(
filter: {
fileAbsolutePath: {
regex: "/\\/(pages|content)\\//",
}
}
) {
edges {
node {
fileAbsolutePath
fields {
slug
}
frontmatter {
categories
tags
}
}
}
}
}
`)
.then((result) => { // eslint-disable-line consistent-return
if (result.errors) {
return Promise.reject(result.errors);
}

const categories = new Set();
const tags = new Set();

result.data.markdown.edges.forEach(({ node }) => {
// Determine the template to use with the page. If we can't find a
// template we skip the file.
const match = pathMatchers.find(({ matcher }) =>
matcher.test(node.fileAbsolutePath));

if (!match) {
return;
}

const { template } = match;

// Add the categories of this post to the set of unique categories. The
// set is used to produce a page for each category.
if (template === 'blog-post') {
node.frontmatter.categories.forEach(cat => categories.add(cat));
node.frontmatter.tags.forEach(tag => tags.add(tag));
}

// Create a page at the given path. The context is used to provide
// variable data to the GraphQL query used by the page.
const pagePath = node.fields.slug;
const context = {};

if (node.fields && node.fields.slug) {
context.slug = node.fields.slug;
}

createPage({
path: pagePath,
component: path.resolve(`src/templates/${template}/index.js`),
context,
});
});

// Create category and tag pages containing all of the blog posts belonging
// to the given category or tag.
const { blogBasePath } = result.data.site.config;

categories.forEach(category => createPage({
path: `${blogBasePath}/categories/${makeSlug(category)}`,
component: path.resolve('src/templates/blog-category/index.js'),
context: {
category,
},
}));

tags.forEach(tag => createPage({
path: `${blogBasePath}/tags/${makeSlug(tag)}`,
component: path.resolve('src/templates/blog-tag/index.js'),
context: {
tag,
},
}));
});

// Handle "createNode" events from Gatsby at build time. This runs for every
// node (basically everything in Gatsby is a "node") and allows us to add or
// modify data associated with a node dynamically. Any changes to nodes made
// here make it through to the GraphQL schema and can there be queried.
exports.onCreateNode = ({ node, boundActionCreators, getNode }) => {
const { createNodeField } = boundActionCreators;

// If the node is a blog post we create a slug for it from its file path. The
// file path here is actually just the file name, so the slug for
// "src/content/blog-posts/my-first-post.md" is "my-first-post".
if (/\/content\/blog-posts\//.test(node.fileAbsolutePath)) {
const value = `/blog${createFilePath({
basePath: 'blog-posts',
getNode,
node,
})}`;

createNodeField({
name: 'slug',
node,
value,
});
}

// If the node is an author metadata document we create a uniquely named field
// front the author name to allow mapping to blog post frontmatter. This
// approach allows us to use more semantically named keys in frontmatter.
if (/\/content\/authors\//.test(node.fileAbsolutePath)) {
createNodeField({
name: 'authorName',
value: node.frontmatter.name,
node,
});
}
};

0 comments on commit 27f5ad8

Please sign in to comment.