Skip to content

Commit 19ef2b3

Browse files
authored
Merge pull request graphql#1125 from cometkim/custom-blog-posts
refactor blog posts
2 parents 06095bc + b6ece33 commit 19ef2b3

File tree

16 files changed

+370
-310
lines changed

16 files changed

+370
-310
lines changed

gatsby-node.js

Lines changed: 98 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,89 @@ const frontmatterParser = require("parser-front-matter")
55
const { readFile } = require("fs-extra")
66
const { promisify } = require("util")
77

8+
exports.createSchemaCustomization = ({ actions, schema }) => {
9+
const gql = String.raw;
10+
const { createTypes } = actions;
11+
12+
createTypes(gql`
13+
type BlogPost implements Node
14+
@childOf(types: ["MarkdownRemark"])
15+
{
16+
postId: String!
17+
title: String!
18+
tags: [String!]!
19+
date: Date! @dateformat(formatString: "YYYY-MM-DD")
20+
authors: [String!]!
21+
guestBio: String
22+
remark: MarkdownRemark! @link # backlink to the parent
23+
}
24+
`);
25+
};
26+
27+
// Transform nodes, each of logic inside here can be extracted to a separated plugin later.
28+
exports.onCreateNode = async ({
29+
reporter,
30+
node,
31+
actions,
32+
createNodeId,
33+
createContentDigest,
34+
}) => {
35+
const { createNode, createParentChildLink } = actions;
36+
37+
// Derive content nodes from remark nodes
38+
if (node.internal.type === 'MarkdownRemark') {
39+
if (node.frontmatter.layout === 'blog') {
40+
const nodeId = createNodeId(`${node.id} >>> BlogPost`);
41+
42+
const permalink = node.frontmatter.permalink;
43+
if (!permalink?.startsWith('/blog/')) {
44+
reporter.panicOnBuild(`${permalink} is not valid permalink for blog post`);
45+
return;
46+
}
47+
48+
// It contains a kind of transform logic. However, those logics can be extracted to resolvers into ahead of sourcing (createTypes)
49+
const blogPostContent = {
50+
id: nodeId,
51+
postId: permalink.replace('/blog/', '').replace(/\/$/, ''),
52+
title: node.frontmatter.title,
53+
tags: node.frontmatter.tags ?? [],
54+
date: node.frontmatter.date,
55+
authors: (node.frontmatter.byline ?? '')
56+
.split(',')
57+
.map(name => name.trim())
58+
.filter(Boolean),
59+
guestBio: node.frontmatter.guestBio ?? null,
60+
};
61+
62+
createNode({
63+
...blogPostContent,
64+
remark: node.id,
65+
parent: node.id,
66+
children: [],
67+
internal: {
68+
type: 'BlogPost',
69+
contentDigest: createContentDigest(blogPostContent),
70+
},
71+
});
72+
73+
createParentChildLink({
74+
parent: node,
75+
child: blogPostContent,
76+
});
77+
}
78+
}
79+
};
80+
881
exports.onCreatePage = async ({ page, actions }) => {
82+
// trying to refactor code to be "the Gatsby way".
83+
// from the paths on ready, ignores a bunch of existing custom logic below.
84+
if (page.path.startsWith('/blog')) {
85+
return;
86+
}
87+
if (page.path.startsWith('/tags')) {
88+
return;
89+
}
90+
991
const { createPage, deletePage } = actions
1092
deletePage(page)
1193
let context = {
@@ -148,8 +230,8 @@ exports.createPages = async ({ graphql, actions }) => {
148230
}
149231
}
150232
}
151-
tagsGroup: allMarkdownRemark {
152-
group(field: frontmatter___tags) {
233+
allBlogPost {
234+
group(field: tags) {
153235
fieldValue
154236
}
155237
}
@@ -164,6 +246,17 @@ exports.createPages = async ({ graphql, actions }) => {
164246
throw result.errors
165247
}
166248

249+
const tags = result.data.allBlogPost.group.map(group => group.fieldValue)
250+
tags.forEach(tag => {
251+
createPage({
252+
path: `/tags/${tag.toLowerCase()}/`,
253+
component: path.resolve("./src/templates/{BlogPost.tags}.tsx"),
254+
context: {
255+
tag,
256+
},
257+
})
258+
})
259+
167260
const markdownPages = result.data.allMarkdownRemark.edges
168261

169262
// foundation: [
@@ -350,7 +443,9 @@ exports.createPages = async ({ graphql, actions }) => {
350443

351444
// Use all the set up data to now tell Gatsby to create pages
352445
// on the site
353-
allPages.forEach(page => {
446+
allPages
447+
.filter(page => !page.permalink.startsWith('/blog'))
448+
.forEach(page => {
354449
createPage({
355450
path: `${page.permalink}`,
356451
component: docTemplate,
@@ -362,19 +457,6 @@ exports.createPages = async ({ graphql, actions }) => {
362457
},
363458
})
364459
})
365-
366-
// Create tag pages
367-
const tagTemplate = path.resolve("src/templates/tags.tsx")
368-
const tags = result.data.tagsGroup.group
369-
tags.forEach(tag => {
370-
createPage({
371-
path: `/tags/${tag.fieldValue}/`,
372-
component: tagTemplate,
373-
context: {
374-
tag: tag.fieldValue,
375-
},
376-
})
377-
})
378460
}
379461

380462
exports.onCreateWebpackConfig = ({ actions }) => {

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
},
1717
"dependencies": {
1818
"@graphql-tools/schema": "8.2.0",
19+
"@reach/router": "1.3.4",
1920
"@weknow/gatsby-remark-twitter": "0.2.3",
2021
"assert": "2.0.0",
2122
"codemirror": "5.63.0",

src/components/BlogLayout/index.tsx

Lines changed: 15 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,29 @@
1-
import React from "react"
1+
import * as React from "react"
2+
import { graphql } from "gatsby"
23
import BlogSidebar from "../BlogSidebar"
34
import BlogPost from "../BlogPost"
45

6+
export const fragments = graphql`
7+
fragment BlogLayout_post on BlogPost {
8+
...BlogPost_post
9+
}
10+
`;
11+
512
interface Props {
6-
title: string
7-
date: string
8-
permalink: string
9-
byline: string
10-
guestBio: string
11-
rawMarkdownBody: string
12-
sideBarData: any
13-
pageContext: any
14-
tags: Array<string>
13+
post: GatsbyTypes.BlogLayout_postFragment,
1514
}
1615

17-
const index = ({
18-
title,
19-
date,
20-
permalink,
21-
byline,
22-
guestBio,
23-
rawMarkdownBody,
24-
sideBarData,
25-
pageContext,
26-
tags,
27-
}: Props) => {
16+
const BlogLayout: React.FC<Props> = ({
17+
post,
18+
}) => {
2819
return (
2920
<section>
3021
<div className="documentationContent">
31-
<BlogPost
32-
title={title}
33-
date={date}
34-
permalink={permalink}
35-
byline={byline}
36-
guestBio={guestBio}
37-
rawMarkdownBody={rawMarkdownBody}
38-
isPermalink={true}
39-
pageContext={pageContext}
40-
tags={tags}
41-
/>
42-
<BlogSidebar
43-
posts={sideBarData[0].links.sort((a: any, b: any) => {
44-
const aDate = new Date(a.frontmatter.date)
45-
const bDate = new Date(b.frontmatter.date)
46-
if (aDate > bDate) {
47-
return -1
48-
} else if (aDate < bDate) {
49-
return 1
50-
}
51-
return 0
52-
})}
53-
currentPermalink={permalink}
54-
/>
22+
<BlogPost post={post} />
23+
<BlogSidebar />
5524
</div>
5625
</section>
5726
)
5827
}
5928

60-
export default index
29+
export default BlogLayout

src/components/BlogPost/index.tsx

Lines changed: 43 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,51 @@
1-
import React from "react"
1+
import * as React from "react"
2+
import { graphql, Link } from "gatsby"
23
import Marked from "../Marked"
3-
import { Link } from "gatsby"
4+
5+
export const fragments = graphql`
6+
fragment BlogPost_post on BlogPost {
7+
title
8+
date
9+
authors
10+
tags
11+
guestBio
12+
remark {
13+
rawMarkdownBody
14+
}
15+
}
16+
`;
417

518
interface Props {
6-
title: string
7-
date: string
8-
permalink: string
9-
byline: string
10-
guestBio: string
11-
rawMarkdownBody: string
12-
isPermalink: boolean
13-
pageContext: any
14-
excerpt?: string
15-
showExcerpt?: true
16-
tags: Array<string>
19+
post: GatsbyTypes.BlogPost_postFragment,
1720
}
1821

19-
const BlogPost = ({
20-
title,
21-
date,
22-
permalink,
23-
byline,
24-
guestBio,
25-
rawMarkdownBody,
26-
isPermalink,
27-
pageContext,
28-
excerpt,
29-
showExcerpt,
30-
tags,
31-
}: Props) => (
32-
<div className="inner-content">
33-
<h1>{isPermalink ? title : <a href={permalink}>{title}</a>}</h1>
34-
<p>
35-
{new Date(date).toLocaleDateString()} by {byline}
36-
</p>
37-
<div className="tag-wrapper">
38-
{tags.map(tag => (
39-
<span className="tag">
40-
<Link to={`/tags/${tag}`}>{tag}</Link>
41-
</span>
42-
))}
43-
</div>
22+
const BlogPost: React.FC<Props> = ({
23+
post,
24+
}) => {
25+
const byline = post.authors.join(', ')
26+
return (
27+
<div className="inner-content">
28+
<h1>{post.title}</h1>
29+
30+
<p>
31+
{new Date(post.date).toLocaleDateString()} by {byline}
32+
</p>
33+
34+
<div className="tag-wrapper">
35+
{post.tags.map(tag => (
36+
<span key={tag} className="tag">
37+
<Link to={`/tags/${tag}/`}>{tag}</Link>
38+
</span>
39+
))}
40+
</div>
4441

45-
{guestBio ? null : <hr />}
46-
{guestBio && (
47-
<p className="guestBio">{`This guest article contributed by ${byline}, ${guestBio}.`}</p>
48-
)}
49-
50-
{showExcerpt ? (
51-
<p>{excerpt}</p>
52-
) : (
53-
<Marked pageContext={pageContext}>{rawMarkdownBody}</Marked>
54-
)}
55-
</div>
56-
)
42+
{post.guestBio && (
43+
<p className="guestBio">{`This guest article contributed by ${byline}, ${post.guestBio}.`}</p>
44+
)}
45+
46+
<Marked>{post.remark.rawMarkdownBody}</Marked>
47+
</div>
48+
)
49+
}
5750

5851
export default BlogPost
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import * as React from "react"
2+
import { graphql, Link } from "gatsby"
3+
4+
export const fragments = graphql`
5+
fragment BlogPostPreview_post on BlogPost {
6+
title
7+
date
8+
authors
9+
tags
10+
postPath: gatsbyPath(filePath: "/blog/{BlogPost.postId}")
11+
remark {
12+
excerpt
13+
}
14+
}
15+
`;
16+
17+
interface Props {
18+
post: GatsbyTypes.BlogPostPreview_postFragment,
19+
}
20+
21+
const BlogPostPreview: React.FC<Props> = ({
22+
post,
23+
}) => (
24+
<div className="inner-content">
25+
<h1>
26+
<Link to={post.postPath!}>{post.title}</Link>
27+
</h1>
28+
29+
<p>
30+
{new Date(post.date).toLocaleDateString()} by {post.authors.join(', ')}
31+
</p>
32+
33+
<div className="tag-wrapper">
34+
{post.tags.map(tag => (
35+
<span key={tag} className="tag">
36+
<Link to={`/tags/${tag}/`}>{tag}</Link>
37+
</span>
38+
))}
39+
</div>
40+
41+
<p>{post.remark.excerpt}</p>
42+
</div>
43+
)
44+
45+
export default BlogPostPreview

0 commit comments

Comments
 (0)