diff --git a/gatsby-node.js b/gatsby-node.js
index 7688abe..92bd55f 100644
--- a/gatsby-node.js
+++ b/gatsby-node.js
@@ -1,3 +1,4 @@
+const _ = require('lodash')
const path = require('path')
const { createFilePath } = require('gatsby-source-filesystem')
@@ -14,6 +15,7 @@ exports.createPages = ({ boundActionCreators, graphql }) => {
slug
}
frontmatter {
+ tags
templateKey
}
}
@@ -26,10 +28,13 @@ exports.createPages = ({ boundActionCreators, graphql }) => {
return Promise.reject(result.errors)
}
- result.data.allMarkdownRemark.edges.forEach(edge => {
+ const posts = result.data.allMarkdownRemark.edges
+
+ posts.forEach(edge => {
const id = edge.node.id
createPage({
path: edge.node.fields.slug,
+ tags: edge.node.frontmatter.tags,
component: path.resolve(
`src/templates/${String(edge.node.frontmatter.templateKey)}.js`
),
@@ -39,6 +44,30 @@ exports.createPages = ({ boundActionCreators, graphql }) => {
},
})
})
+
+ // Tag pages:
+ let tags = []
+ // Iterate through each post, putting all found tags into `tags`
+ posts.forEach(edge => {
+ if (_.get(edge, `node.frontmatter.tags`)) {
+ tags = tags.concat(edge.node.frontmatter.tags)
+ }
+ })
+ // Eliminate duplicate tags
+ tags = _.uniq(tags)
+
+ // Make tag pages
+ tags.forEach(tag => {
+ const tagPath = `/tags/${_.kebabCase(tag)}/`
+
+ createPage({
+ path: tagPath,
+ component: path.resolve(`src/templates/tags.js`),
+ context: {
+ tag,
+ },
+ })
+ })
})
}
diff --git a/package.json b/package.json
index 92fa01b..47e06cc 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "gatsby-starter-netlify-cms",
"description": "Example Gatsby, and Netlify CMS project",
- "version": "1.1.0",
+ "version": "1.1.1",
"author": "Austin Green",
"dependencies": {
"bulma": "^0.6.0",
@@ -15,6 +15,8 @@
"gatsby-source-filesystem": "^1.5.23",
"gatsby-transformer-remark": "^1.7.33",
"gatsby-transformer-sharp": "^1.6.21",
+ "lodash": "^4.17.5",
+ "lodash-webpack-plugin": "^0.11.4",
"prop-types": "^15.6.0",
"react": "^16.2.0",
"react-helmet": "^5.2.0"
diff --git a/src/cms/preview-templates/AboutPagePreview.js b/src/cms/preview-templates/AboutPagePreview.js
index 71b9d32..413a6da 100644
--- a/src/cms/preview-templates/AboutPagePreview.js
+++ b/src/cms/preview-templates/AboutPagePreview.js
@@ -1,4 +1,5 @@
import React from 'react'
+import PropTypes from 'prop-types'
import { AboutPageTemplate } from '../../templates/about-page'
const AboutPagePreview = ({ entry, widgetFor }) => (
@@ -8,4 +9,11 @@ const AboutPagePreview = ({ entry, widgetFor }) => (
/>
)
+AboutPagePreview.propTypes = {
+ entry: PropTypes.shape({
+ getIn: PropTypes.func,
+ }),
+ widgetFor: PropTypes.func,
+}
+
export default AboutPagePreview
diff --git a/src/cms/preview-templates/BlogPostPreview.js b/src/cms/preview-templates/BlogPostPreview.js
index f3be58b..7677323 100644
--- a/src/cms/preview-templates/BlogPostPreview.js
+++ b/src/cms/preview-templates/BlogPostPreview.js
@@ -1,12 +1,21 @@
import React from 'react'
+import PropTypes from 'prop-types'
import { BlogPostTemplate } from '../../templates/blog-post'
const BlogPostPreview = ({ entry, widgetFor }) => (
)
+BlogPostPreview.propTypes = {
+ entry: PropTypes.shape({
+ getIn: PropTypes.func,
+ }),
+ widgetFor: PropTypes.func,
+}
+
export default BlogPostPreview
diff --git a/src/cms/preview-templates/ProductPagePreview.js b/src/cms/preview-templates/ProductPagePreview.js
index 0173650..7970041 100644
--- a/src/cms/preview-templates/ProductPagePreview.js
+++ b/src/cms/preview-templates/ProductPagePreview.js
@@ -1,4 +1,5 @@
import React from 'react'
+import PropTypes from 'prop-types'
import { ProductPageTemplate } from '../../templates/product-page'
const ProductPagePreview = ({ entry, getAsset }) => {
@@ -45,4 +46,11 @@ const ProductPagePreview = ({ entry, getAsset }) => {
)
}
+ProductPagePreview.propTypes = {
+ entry: PropTypes.shape({
+ getIn: PropTypes.func,
+ }),
+ getAsset: PropTypes.func,
+}
+
export default ProductPagePreview
diff --git a/src/components/Content.js b/src/components/Content.js
index 24ee52e..289e571 100644
--- a/src/components/Content.js
+++ b/src/components/Content.js
@@ -1,8 +1,19 @@
import React from 'react'
+import PropTypes from 'prop-types'
-export default ({ content, className }) => (
-
{content}
-)
export const HTMLContent = ({ content, className }) => (
)
+
+const Content = ({ content, className }) => (
+ {content}
+)
+
+Content.propTypes = {
+ content: PropTypes.string,
+ className: PropTypes.string,
+}
+
+HTMLContent.propTypes = Content.propTypes
+
+export default Content
diff --git a/src/components/Features.js b/src/components/Features.js
index 1b0778c..15c19b2 100644
--- a/src/components/Features.js
+++ b/src/components/Features.js
@@ -1,4 +1,5 @@
import React from 'react'
+import PropTypes from 'prop-types'
const FeatureGrid = ({ gridItems }) => (
@@ -15,4 +16,13 @@ const FeatureGrid = ({ gridItems }) => (
)
+FeatureGrid.propTypes = {
+ gridItems: PropTypes.arrayOf(
+ PropTypes.shape({
+ image: PropTypes.string,
+ text: PropTypes.string,
+ })
+ ),
+}
+
export default FeatureGrid
diff --git a/src/components/Pricing.js b/src/components/Pricing.js
index df49b26..6677a65 100644
--- a/src/components/Pricing.js
+++ b/src/components/Pricing.js
@@ -1,6 +1,7 @@
import React from 'react'
+import PropTypes from 'prop-types'
-export default ({ data }) => (
+const Pricing = ({ data }) => (
{data.map(price => (
@@ -24,3 +25,16 @@ export default ({ data }) => (
))}
)
+
+Pricing.propTypes = {
+ data: PropTypes.arrayOf(
+ PropTypes.shape({
+ plan: PropTypes.string,
+ price: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
+ description: PropTypes.string,
+ items: PropTypes.array,
+ })
+ ),
+}
+
+export default Pricing
diff --git a/src/components/Testimonials.js b/src/components/Testimonials.js
index 171303d..05b15c4 100644
--- a/src/components/Testimonials.js
+++ b/src/components/Testimonials.js
@@ -1,6 +1,7 @@
import React from 'react'
+import PropTypes from 'prop-types'
-export default ({ testimonials }) => (
+const Testimonials = ({ testimonials }) => (
{testimonials.map(testimonial => (
@@ -13,3 +14,14 @@ export default ({ testimonials }) => (
))}
)
+
+Testimonials.propTypes = {
+ testimonials: PropTypes.arrayOf(
+ PropTypes.shape({
+ quote: PropTypes.string,
+ author: PropTypes.string,
+ })
+ ),
+}
+
+export default Testimonials
diff --git a/src/layouts/all.sass b/src/layouts/all.sass
index 32e9e81..9e87d7b 100644
--- a/src/layouts/all.sass
+++ b/src/layouts/all.sass
@@ -7,6 +7,21 @@ $kaldi-red-invert: #fff
$primary: $kaldi-red
$primary-invert: $kaldi-red-invert
+.content .taglist
+ list-style: none
+ margin-bottom: 0
+ margin-left: 0
+ margin-right: 1.5rem
+ margin-top: 1.5rem
+ display: flex
+ flex-wrap: wrap
+ justify-content: left
+ align-items: center
+ li
+ padding: 0 2rem 1rem 0
+ margin-bottom: 1.5rem
+ margin-top: 0
+
// Helper Classes
.full-width-image-container
width: 100vw
diff --git a/src/pages/blog/2016-12-17-making-sense-of-the-scaas-new-flavor-wheel.md b/src/pages/blog/2016-12-17-making-sense-of-the-scaas-new-flavor-wheel.md
index 5d3036a..ca4b004 100644
--- a/src/pages/blog/2016-12-17-making-sense-of-the-scaas-new-flavor-wheel.md
+++ b/src/pages/blog/2016-12-17-making-sense-of-the-scaas-new-flavor-wheel.md
@@ -3,6 +3,9 @@ templateKey: blog-post
title: Making sense of the SCAA’s new Flavor Wheel
date: 2016-12-17T15:04:10.000Z
description: The Coffee Taster’s Flavor Wheel, the official resource used by coffee tasters, has been revised for the first time this year.
+tags:
+ - flavor
+ - tasting
---

diff --git a/src/pages/blog/2017-01-04-a-beginners-guide-to-brewing-with-chemex.md b/src/pages/blog/2017-01-04-a-beginners-guide-to-brewing-with-chemex.md
index 8889c48..f06dd8a 100644
--- a/src/pages/blog/2017-01-04-a-beginners-guide-to-brewing-with-chemex.md
+++ b/src/pages/blog/2017-01-04-a-beginners-guide-to-brewing-with-chemex.md
@@ -3,6 +3,9 @@ templateKey: blog-post
title: A beginners’ guide to brewing with Chemex
date: 2017-01-04T15:04:10.000Z
description: Brewing with a Chemex probably seems like a complicated, time-consuming ordeal, but once you get used to the process, it becomes a soothing ritual that's worth the effort every time.
+tags:
+ - brewing
+ - chemex
---

diff --git a/src/pages/blog/2017-01-04-just-in-small-batch-of-jamaican-blue-mountain-in-store-next-week.md b/src/pages/blog/2017-01-04-just-in-small-batch-of-jamaican-blue-mountain-in-store-next-week.md
index 9007f16..9f899b0 100644
--- a/src/pages/blog/2017-01-04-just-in-small-batch-of-jamaican-blue-mountain-in-store-next-week.md
+++ b/src/pages/blog/2017-01-04-just-in-small-batch-of-jamaican-blue-mountain-in-store-next-week.md
@@ -5,6 +5,11 @@ date: 2017-01-04T15:04:10.000Z
description: >-
We’re proud to announce that we’ll be offering a small batch of Jamaica Blue
Mountain coffee beans in our store next week.
+tags:
+ - jamaica
+ - green beans
+ - flavor
+ - tasting
---
We expect the shipment of a limited quantity of green beans next Monday. We’ll be offering the roasted beans from Tuesday, but quantities are limited, so be quick.
diff --git a/src/pages/index.js b/src/pages/index.js
index f519327..512870e 100644
--- a/src/pages/index.js
+++ b/src/pages/index.js
@@ -1,4 +1,5 @@
import React from 'react'
+import PropTypes from 'prop-types'
import Link from 'gatsby-link'
export default class IndexPage extends React.Component {
@@ -43,6 +44,14 @@ export default class IndexPage extends React.Component {
}
}
+IndexPage.propTypes = {
+ data: PropTypes.shape({
+ allMarkdownRemark: PropTypes.shape({
+ edges: PropTypes.array,
+ }),
+ }),
+}
+
export const pageQuery = graphql`
query IndexQuery {
allMarkdownRemark(sort: { order: DESC, fields: [frontmatter___date] }) {
diff --git a/src/pages/tags/index.js b/src/pages/tags/index.js
new file mode 100644
index 0000000..06580a7
--- /dev/null
+++ b/src/pages/tags/index.js
@@ -0,0 +1,49 @@
+import React from 'react'
+import { kebabCase } from 'lodash'
+import Helmet from 'react-helmet'
+import Link from 'gatsby-link'
+
+const TagsPage = ({
+ data: { allMarkdownRemark: { group }, site: { siteMetadata: { title } } },
+}) => (
+
+
+
+
+
+
Tags
+
+ {group.map(tag => (
+ -
+
+ {tag.fieldValue} ({tag.totalCount})
+
+
+ ))}
+
+
+
+
+
+)
+
+export default TagsPage
+
+export const tagPageQuery = graphql`
+ query TagsQuery {
+ site {
+ siteMetadata {
+ title
+ }
+ }
+ allMarkdownRemark(limit: 1000) {
+ group(field: frontmatter___tags) {
+ fieldValue
+ totalCount
+ }
+ }
+ }
+`
diff --git a/src/templates/about-page.js b/src/templates/about-page.js
index b36f822..a662a62 100644
--- a/src/templates/about-page.js
+++ b/src/templates/about-page.js
@@ -1,4 +1,5 @@
import React from 'react'
+import PropTypes from 'prop-types'
import Content, { HTMLContent } from '../components/Content'
export const AboutPageTemplate = ({ title, content, contentComponent }) => {
@@ -22,7 +23,13 @@ export const AboutPageTemplate = ({ title, content, contentComponent }) => {
)
}
-export default ({ data }) => {
+AboutPageTemplate.propTypes = {
+ title: PropTypes.string.isRequired,
+ content: PropTypes.string,
+ contentComponent: PropTypes.func,
+}
+
+const AboutPage = ({ data }) => {
const { markdownRemark: post } = data
return (
@@ -34,6 +41,12 @@ export default ({ data }) => {
)
}
+AboutPage.propTypes = {
+ data: PropTypes.object.isRequired,
+}
+
+export default AboutPage
+
export const aboutPageQuery = graphql`
query AboutPage($id: String!) {
markdownRemark(id: { eq: $id }) {
diff --git a/src/templates/blog-post.js b/src/templates/blog-post.js
index f90af05..8437026 100644
--- a/src/templates/blog-post.js
+++ b/src/templates/blog-post.js
@@ -1,11 +1,15 @@
import React from 'react'
+import PropTypes from 'prop-types'
+import { kebabCase } from 'lodash'
import Helmet from 'react-helmet'
+import Link from 'gatsby-link'
import Content, { HTMLContent } from '../components/Content'
export const BlogPostTemplate = ({
content,
contentComponent,
description,
+ tags,
title,
helmet,
}) => {
@@ -22,6 +26,18 @@ export const BlogPostTemplate = ({
{description}
+ {tags && tags.length ? (
+
+
Tags
+
+ {tags.map(tag => (
+ -
+ {tag}
+
+ ))}
+
+
+ ) : null}
@@ -29,20 +45,37 @@ export const BlogPostTemplate = ({
)
}
-export default props => {
- const { markdownRemark: post } = props.data
+BlogPostTemplate.propTypes = {
+ content: PropTypes.string.isRequired,
+ contentComponent: PropTypes.func,
+ description: PropTypes.string,
+ title: PropTypes.string,
+ helmet: PropTypes.instanceOf(Helmet),
+}
+
+const BlogPost = ({ data }) => {
+ const { markdownRemark: post } = data
return (
}
+ helmet={}
+ tags={post.frontmatter.tags}
title={post.frontmatter.title}
/>
)
}
+BlogPost.propTypes = {
+ data: PropTypes.shape({
+ markdownRemark: PropTypes.object,
+ }),
+}
+
+export default BlogPost
+
export const pageQuery = graphql`
query BlogPostByID($id: String!) {
markdownRemark(id: { eq: $id }) {
@@ -52,6 +85,7 @@ export const pageQuery = graphql`
date(formatString: "MMMM DD, YYYY")
title
description
+ tags
}
}
}
diff --git a/src/templates/product-page.js b/src/templates/product-page.js
index 3e5ac9d..d556fbb 100644
--- a/src/templates/product-page.js
+++ b/src/templates/product-page.js
@@ -1,4 +1,5 @@
import React from 'react'
+import PropTypes from 'prop-types'
import Features from '../components/Features'
import Testimonials from '../components/Testimonials'
import Pricing from '../components/Pricing'
@@ -104,7 +105,31 @@ export const ProductPageTemplate = ({
)
-export default ({ data }) => {
+ProductPageTemplate.propTypes = {
+ image: PropTypes.string,
+ title: PropTypes.string,
+ heading: PropTypes.string,
+ description: PropTypes.string,
+ intro: PropTypes.shape({
+ blurbs: PropTypes.array,
+ }),
+ main: PropTypes.shape({
+ heading: PropTypes.string,
+ description: PropTypes.string,
+ image1: PropTypes.object,
+ image2: PropTypes.object,
+ image3: PropTypes.object,
+ }),
+ testimonials: PropTypes.array,
+ fullImage: PropTypes.string,
+ pricing: PropTypes.shape({
+ heading: PropTypes.string,
+ description: PropTypes.string,
+ plans: PropTypes.array,
+ }),
+}
+
+const ProductPage = ({ data }) => {
const { frontmatter } = data.markdownRemark
return (
@@ -122,6 +147,16 @@ export default ({ data }) => {
)
}
+ProductPage.propTypes = {
+ data: PropTypes.shape({
+ markdownRemark: PropTypes.shape({
+ frontmatter: PropTypes.object,
+ }),
+ }),
+}
+
+export default ProductPage
+
export const productPageQuery = graphql`
query ProductPage($id: String!) {
markdownRemark(id: { eq: $id }) {
diff --git a/src/templates/tags.js b/src/templates/tags.js
new file mode 100644
index 0000000..0879a7b
--- /dev/null
+++ b/src/templates/tags.js
@@ -0,0 +1,71 @@
+import React from 'react'
+import Helmet from 'react-helmet'
+import Link from 'gatsby-link'
+
+class TagRoute extends React.Component {
+ render() {
+ const posts = this.props.data.allMarkdownRemark.edges
+ const postLinks = posts.map(post => (
+
+
+ {post.node.frontmatter.title}
+
+
+ ))
+ const tag = this.props.pathContext.tag
+ const title = this.props.data.site.siteMetadata.title
+ const totalCount = this.props.data.allMarkdownRemark.totalCount
+ const tagHeader = `${totalCount} post${
+ totalCount === 1 ? '' : 's'
+ } tagged with “${tag}”`
+
+ return (
+
+
+
+
+
+
{tagHeader}
+
+
+ Browse all tags
+
+
+
+
+
+ )
+ }
+}
+
+export default TagRoute
+
+export const tagPageQuery = graphql`
+ query TagPage($tag: String) {
+ site {
+ siteMetadata {
+ title
+ }
+ }
+ allMarkdownRemark(
+ limit: 1000
+ sort: { fields: [frontmatter___date], order: DESC }
+ filter: { frontmatter: { tags: { in: [$tag] } } }
+ ) {
+ totalCount
+ edges {
+ node {
+ fields {
+ slug
+ }
+ frontmatter {
+ title
+ }
+ }
+ }
+ }
+ }
+`
diff --git a/static/admin/config.yml b/static/admin/config.yml
index 35d0a2b..020c0db 100644
--- a/static/admin/config.yml
+++ b/static/admin/config.yml
@@ -17,6 +17,7 @@ collections:
- {label: "Publish Date", name: "date", widget: "datetime"}
- {label: "Description", name: "description", widget: "text"}
- {label: "Body", name: "body", widget: "markdown"}
+ - {label: "Tags", name: "tags", widget: "list"}
- name: "pages"
label: "Pages"
diff --git a/yarn.lock b/yarn.lock
index 16707c1..2ceb48f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5761,6 +5761,12 @@ lodash-id@^0.14.0:
version "0.14.0"
resolved "https://registry.yarnpkg.com/lodash-id/-/lodash-id-0.14.0.tgz#baf48934e543a1b5d6346f8c84698b1a8c803896"
+lodash-webpack-plugin@^0.11.4:
+ version "0.11.4"
+ resolved "https://registry.yarnpkg.com/lodash-webpack-plugin/-/lodash-webpack-plugin-0.11.4.tgz#6c3ecba3d4b8d24b53940b63542715c5ed3c4ac5"
+ dependencies:
+ lodash "^4.17.4"
+
lodash._basecopy@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36"
@@ -5965,7 +5971,7 @@ lodash@3.10.1:
version "3.10.1"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-3.10.1.tgz#5bf45e8e49ba4189e17d482789dfd15bd140b7b6"
-lodash@4, lodash@^4.0.0, lodash@^4.1.0, lodash@^4.12.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1, lodash@~4.17.4:
+lodash@4, lodash@^4.0.0, lodash@^4.1.0, lodash@^4.12.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.2, lodash@^4.17.3, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.0, lodash@^4.2.1, lodash@^4.3.0, lodash@^4.6.1, lodash@~4.17.4:
version "4.17.5"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"