Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit 27dcfce

Browse files
Feat: Setup gatsby-node for blogs (#1195)
* Modified Gatsby Configuration for querying markdown blogs * Added /blog page * Added author features and updated Navigation Links * Added tests for new Components * Removed extra blogs and added a dummy blog * Removed blog from navigation * Added mock blog files * Modified Gatsby Configuration for querying markdown blogs * Added /blog page * Added author features and updated Navigation Links * Added tests for new Components * Removed extra blogs and added a dummy blog * Removed blog from navigation * Added mock blog files * Updated lock file * Fixed Test Fails * Modified Gatsby Configuration for querying markdown blogs * Added /blog page * Added author features and updated Navigation Links * Added tests for new Components * Removed extra blogs and added a dummy blog * Removed blog from navigation * Added mock blog files * Updated lock file * Fixed Test Fails * Rebased with upstream/master Co-authored-by: Ben Halverson <[email protected]>
1 parent 9245738 commit 27dcfce

22 files changed

Lines changed: 566 additions & 48 deletions

File tree

content/blog/0000-00-00-mock.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
title: mock
3+
author: [manishprivet]
4+
---
5+
6+
This is a mock markdown file to keep blog queries from failing and to keep the blog folder alive.

gatsby-config.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,12 @@ module.exports = {
1111
siteUrl: config.siteUrl,
1212
siteUrlNoSlash: config.siteUrlNoSlash,
1313
},
14+
mapping: {
15+
'MarkdownRemark.frontmatter.author': `AuthorYaml`,
16+
},
1417
plugins: [
1518
'gatsby-plugin-catch-links',
19+
'gatsby-transformer-yaml',
1620
'gatsby-plugin-react-helmet',
1721
{
1822
resolve: 'gatsby-plugin-canonical-urls',
@@ -50,6 +54,20 @@ module.exports = {
5054
path: `${__dirname}/content/community`,
5155
},
5256
},
57+
{
58+
resolve: 'gatsby-source-filesystem',
59+
options: {
60+
name: 'blog',
61+
path: `${__dirname}/content/blog`,
62+
},
63+
},
64+
{
65+
resolve: 'gatsby-source-filesystem',
66+
options: {
67+
name: 'data',
68+
path: `${__dirname}/src/data`,
69+
},
70+
},
5371
{
5472
resolve: `gatsby-plugin-mdx`,
5573
options: {

gatsby-node.js

Lines changed: 57 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
const path = require('path');
33
const createSlug = require('./util-node/createSlug');
44

5+
const BLOG_POST_FILENAME_REGEX = /([0-9]+)-([0-9]+)-([0-9]+)-(.+)\.md$/;
6+
57
exports.createPages = ({ graphql, actions }) => {
68
const { createPage, createRedirect } = actions;
79

810
return new Promise((resolve, reject) => {
911
const docTemplate = path.resolve('./src/templates/learn.tsx');
12+
const blogTemplate = path.resolve('./src/templates/blog.tsx');
1013

1114
resolve(
1215
graphql(
@@ -113,33 +116,38 @@ exports.createPages = ({ graphql, actions }) => {
113116
});
114117

115118
docPages.forEach(page => {
116-
createPage({
117-
path: `/learn/${page.slug}`,
118-
component: docTemplate,
119-
context: {
120-
slug: page.slug,
121-
next: page.next,
122-
previous: page.previous,
123-
relativePath: page.relativePath,
124-
navigationData,
125-
},
126-
});
127-
createRedirect({
128-
fromPath: `/${page.slug}`,
129-
toPath: `/learn/${page.slug}`,
130-
isPermanent: true,
131-
});
119+
const context = {
120+
slug: page.slug,
121+
next: page.next,
122+
previous: page.previous,
123+
relativePath: page.relativePath,
124+
navigationData,
125+
};
126+
127+
if (page.slug.includes('/blog/')) {
128+
createPage({
129+
path: page.slug,
130+
component: blogTemplate,
131+
context,
132+
});
133+
} else {
134+
createPage({
135+
path: `/learn/${page.slug}`,
136+
component: docTemplate,
137+
context,
138+
});
139+
createRedirect({
140+
fromPath: `/${page.slug}`,
141+
toPath: `/learn/${page.slug}`,
142+
isPermanent: true,
143+
});
144+
}
145+
132146
if (page.slug === 'introduction-to-nodejs') {
133147
createPage({
134148
path: `/learn`,
135149
component: docTemplate,
136-
context: {
137-
slug: page.slug,
138-
next: page.next,
139-
previous: page.previous,
140-
relativePath: page.relativePath,
141-
navigationData,
142-
},
150+
context,
143151
});
144152
}
145153
});
@@ -148,11 +156,35 @@ exports.createPages = ({ graphql, actions }) => {
148156
});
149157
};
150158

151-
exports.onCreateNode = ({ node, actions }) => {
159+
exports.onCreateNode = ({ node, actions, getNode }) => {
152160
if (node.internal.type === 'MarkdownRemark') {
153161
const { createNodeField } = actions;
154162

155-
const slug = createSlug(node.frontmatter.title);
163+
const { fileAbsolutePath } = node;
164+
let relativePath = '';
165+
if (node.parent && getNode(node.parent))
166+
relativePath = getNode(node.parent).relativePath;
167+
168+
let slug;
169+
170+
if (fileAbsolutePath && fileAbsolutePath.includes('/blog/')) {
171+
const match = BLOG_POST_FILENAME_REGEX.exec(relativePath || '');
172+
const year = match[1];
173+
const month = match[2];
174+
const day = match[3];
175+
const filename = match[4];
176+
177+
slug = `/blog/${year}/${month}/${day}/${filename}`;
178+
179+
const date = new Date(year, month - 1, day);
180+
181+
createNodeField({
182+
node,
183+
name: 'date',
184+
value: date.toJSON(),
185+
});
186+
} else slug = createSlug(node.frontmatter.title);
187+
156188
createNodeField({
157189
node,
158190
name: 'slug',

package-lock.json

Lines changed: 35 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"gatsby-remark-prismjs": "^3.5.0",
3838
"gatsby-source-filesystem": "^2.2.5",
3939
"gatsby-transformer-remark": "^2.8.5",
40+
"gatsby-transformer-yaml": "^2.11.0",
4041
"intersection-observer": "^0.10.0",
4142
"prismjs": "^1.23.0",
4243
"react": "^16.13.1",

src/components/Article/index.tsx

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
import React from 'react';
22
import { throttle } from 'throttle-debounce';
3-
import { PaginationInfo } from '../../types';
3+
import { PaginationInfo, BlogPostAuthor } from '../../types';
44
import AuthorsList from '../../containers/AuthorList';
55
import EditLink from '../EditLink';
66
import Pagination from '../Pagination';
77
import TOC from '../Toc';
8+
import BlogAuthorsList from '../BlogAuthorsList';
89

910
interface Props {
1011
title: string;
1112
html: string;
12-
tableOfContents: string;
13-
authors: string[];
13+
tableOfContents?: string;
14+
authors: string[] | BlogPostAuthor[];
1415
relativePath?: string;
1516
editPath?: string;
1617
next?: PaginationInfo;
1718
previous?: PaginationInfo;
19+
blog?: boolean;
20+
date?: string;
1821
}
1922

2023
const NAV_HEIGHT = 72;
@@ -28,6 +31,8 @@ const Article = ({
2831
relativePath,
2932
editPath,
3033
authors,
34+
blog,
35+
date,
3136
}: Props): JSX.Element => {
3237
const element = React.useRef<HTMLElement | null>(null);
3338

@@ -103,12 +108,17 @@ const Article = ({
103108
return (
104109
<article className="article-reader">
105110
<h1 className="article-reader__headline">{title}</h1>
106-
<TOC heading="TABLE OF CONTENTS" tableOfContents={tableOfContents} />
111+
{blog && (
112+
<BlogAuthorsList date={date} authors={authors as BlogPostAuthor[]} />
113+
)}
114+
{!blog && (
115+
<TOC heading="TABLE OF CONTENTS" tableOfContents={tableOfContents} />
116+
)}
107117
{/* eslint-disable-next-line react/no-danger */}
108118
<div ref={handleRef} dangerouslySetInnerHTML={{ __html: html }} />
109-
<AuthorsList authors={authors} />
110-
<EditLink relativePath={relativePath} editPath={editPath} />
111-
<Pagination previous={previous} next={next} />
119+
{!blog && <AuthorsList authors={authors as string[]} />}
120+
{!blog && <EditLink relativePath={relativePath} editPath={editPath} />}
121+
{!blog && <Pagination previous={previous} next={next} />}
112122
</article>
113123
);
114124
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.list {
2+
font-size: 1em;
3+
margin-bottom: 40px;
4+
span {
5+
margin: 0;
6+
display: inline-block;
7+
margin-left: 3px;
8+
a {
9+
display: inline-block;
10+
text-decoration: none;
11+
}
12+
}
13+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Blog Author component does not render without authors 1`] = `
4+
<div>
5+
<h5
6+
class="list"
7+
>
8+
by
9+
10+
</h5>
11+
</div>
12+
`;
13+
14+
exports[`Blog Author component renders correctly 1`] = `
15+
<div>
16+
<h5
17+
class="list"
18+
>
19+
by
20+
21+
<span>
22+
<a
23+
href="https://bat.man"
24+
rel="noopener noreferrer"
25+
target="_blank"
26+
>
27+
Bruce Wayne
28+
</a>
29+
30+
</span>
31+
</h5>
32+
</div>
33+
`;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import React from 'react';
2+
import { render } from '@testing-library/react';
3+
import BlogAuthor from '..';
4+
import { BlogPostAuthor } from '../../../types';
5+
6+
describe('Blog Author component', () => {
7+
it('renders correctly', () => {
8+
const authors: BlogPostAuthor[] = [
9+
{ name: 'Bruce Wayne', url: 'https://bat.man' },
10+
];
11+
const { container } = render(<BlogAuthor authors={authors} />);
12+
expect(container).toMatchSnapshot();
13+
});
14+
15+
it('does not render without authors', () => {
16+
const { container } = render(<BlogAuthor authors={[]} />);
17+
expect(container).toMatchSnapshot();
18+
});
19+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import React from 'react';
2+
import { BlogPostAuthor } from '../../types';
3+
import { getTerminatingString } from '../../util/getTerminatingString';
4+
import './BlogAuthorsList.scss';
5+
6+
interface Props {
7+
authors: BlogPostAuthor[];
8+
date?: string;
9+
}
10+
11+
const BlogAuthorsList = ({ authors, date }: Props): null | JSX.Element => {
12+
return (
13+
<h5 className="list">
14+
{date} by{' '}
15+
{authors.map(
16+
(author, i): string | JSX.Element =>
17+
author && (
18+
<span key={author.id}>
19+
{author.url ? (
20+
<a target="_blank" rel="noopener noreferrer" href={author.url}>
21+
{author.name}
22+
</a>
23+
) : (
24+
author.name
25+
)}
26+
{getTerminatingString(i, authors.length)}
27+
</span>
28+
)
29+
)}
30+
</h5>
31+
);
32+
};
33+
34+
export default BlogAuthorsList;

0 commit comments

Comments
 (0)