Skip to content

Commit 8c1045c

Browse files
feat: add sidebar component in blog page with top relevant posts
* Added Sidebar component and logic for top 5 most relevant blogs based on tags * Trigger Netlify rebuild * Optimize blog rendering with node querying and sidebar redesign * Resolve project page rendering with blog nodes context * Removed truncate class from heading * resolved overflow-x issue
1 parent 3ee3c59 commit 8c1045c

File tree

4 files changed

+227
-50
lines changed

4 files changed

+227
-50
lines changed

gatsby-node.js

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ exports.createPages = async ({ graphql, actions, reporter }) => {
2020
people
2121
tags
2222
project
23+
excerpt
24+
author
25+
date
26+
cover {
27+
childImageSharp {
28+
gatsbyImageData(width: 300, height: 200, layout: CONSTRAINED, placeholder: BLURRED)
29+
}
30+
}
2331
}
2432
internal {
2533
contentFilePath
@@ -34,6 +42,10 @@ exports.createPages = async ({ graphql, actions, reporter }) => {
3442
}
3543

3644
const nodes = result.data.allMdx.nodes
45+
const blogNodes = nodes.filter(node =>
46+
node.internal.contentFilePath.indexOf("/src/blog/") !== -1
47+
);
48+
3749
// List of all unique projects tags- ex: all projects will uli and Uli will be represented as uli & Viral Spiral and viral-spiral with viral-spiral, and so on
3850
const projects = [
3951
...new Set(
@@ -56,8 +68,7 @@ exports.createPages = async ({ graphql, actions, reporter }) => {
5668
tags_arr.forEach((tag) => tags_set.add(tag))
5769
}
5870
})
59-
60-
// create folder for user avatar
71+
// create folder for user avatar
6172
// try {
6273
// await fs.access("./src/people/avatar")
6374
// } catch {
@@ -77,7 +88,7 @@ exports.createPages = async ({ graphql, actions, reporter }) => {
7788
path: `/blog/dashboard/`,
7889
component: require.resolve(`./src/components/default-blog-dashboard.js`),
7990
})
80-
//siteMapURLs.set("Blogs", "/blog")
91+
//siteMapURLs.set("Blogs", "/blog")
8192
siteMapNodes.push({ name: "blog", isDir: false, node: { name: "blog" } })
8293

8394
// CREATE TAGS PAGE
@@ -113,12 +124,12 @@ exports.createPages = async ({ graphql, actions, reporter }) => {
113124

114125
nodes.forEach(async (node) => {
115126
const { internal, id } = node
116-
// console.log(`------ : ${id}`)
127+
// console.log(`------ : ${id}`)
117128

118129
let fileAbsolutePath = internal.contentFilePath
119130

120131
if (fileAbsolutePath.indexOf("/src/people/") !== -1) {
121-
// create QR code avatar
132+
// create QR code avatar
122133

123134
// await QRCode.toFile(
124135
// `./src/people/avatar/${ node.fields.slug}.png`,
@@ -132,7 +143,7 @@ exports.createPages = async ({ graphql, actions, reporter }) => {
132143
context: { id },
133144
})
134145
}
135-
146+
136147
// if (fileAbsolutePath.indexOf("/src/project/") !== -1) {
137148
// createPage({
138149
// path: `/project/${ node.fields.slug}`,
@@ -149,7 +160,10 @@ exports.createPages = async ({ graphql, actions, reporter }) => {
149160
createPage({
150161
path: `/blog/${node.fields.slug}`,
151162
component: `${blogTemplate}?__contentFilePath=${node.internal.contentFilePath}`,
152-
context: { id: node.id },
163+
context: {
164+
id: node.id,
165+
blogNodes: blogNodes
166+
},
153167
})
154168
}
155169

@@ -159,7 +173,9 @@ exports.createPages = async ({ graphql, actions, reporter }) => {
159173
createPage({
160174
path: `/project/${node.fields.slug}`,
161175
component: require.resolve(`./src/components/default-blog-layout.js`),
162-
context: { id: node.id },
176+
context: { id: node.id,
177+
blogNodes: blogNodes
178+
},
163179
})
164180
}
165181
})
@@ -174,7 +190,7 @@ exports.createPages = async ({ graphql, actions, reporter }) => {
174190
exports.onCreateNode = ({ node, actions, getNode }) => {
175191
const { createNodeField } = actions
176192
if (node.internal.type === 'Mdx' || node.internal.type === 'MarkdownRemark') {
177-
// if (node.internal.type === `MarkdownRemark`) {
193+
// if (node.internal.type === `MarkdownRemark`) {
178194
const value = createFilePath({ node, getNode })
179195
// Slugs are starting with "/"
180196
const slug = value.startsWith('/') ? value.slice(1) : value;

src/components/BlogSidebar.jsx

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import React from 'react';
2+
import { Box, Heading, Text, ResponsiveContext } from 'grommet';
3+
import { GatsbyImage } from 'gatsby-plugin-image';
4+
import { PlainLink } from './atomic/TattleLinks';
5+
6+
7+
const RelatedPostItem = ({ post }) => (
8+
<Box
9+
margin={{ bottom: 'small' }}
10+
border={{ color: 'light-4', size: 'xsmall', side: 'bottom' }}
11+
pad={{ bottom: 'xsmall' }}
12+
>
13+
<PlainLink to={`/blog/${post.slug}`}>
14+
{post.coverImage && (
15+
<Box margin={{ bottom: 'xsmall' }} height="130px" overflow="hidden">
16+
<GatsbyImage
17+
image={post.coverImage}
18+
alt={post.title || "Related post"}
19+
className="rounded w-full h-full object-cover"
20+
/>
21+
</Box>
22+
)}
23+
<Heading
24+
level={5}
25+
margin={{ bottom: 'xsmall' }}
26+
className="font-[Bitter] text-[0.9rem] leading-[1.3]"
27+
>
28+
{post.title}
29+
</Heading>
30+
</PlainLink>
31+
{post.excerpt && (
32+
<Text
33+
size="small"
34+
color="dark-3"
35+
className="line-clamp-3"
36+
>
37+
{post.excerpt}
38+
</Text>
39+
)}
40+
</Box>
41+
);
42+
43+
const BlogSidebar = ({ relatedPosts }) => {
44+
return (
45+
<ResponsiveContext.Consumer>
46+
{(size) => {
47+
const isMobile = size === "small";
48+
const isLarge = size === "large" || size === "xlarge";
49+
50+
return (
51+
<Box
52+
pad={isMobile ? "small" : isLarge ? "medium" : "medium"}
53+
background="visuals-two"
54+
round="small"
55+
elevation="small"
56+
width="100%"
57+
margin={isMobile ? { top: "medium" } : "40px"}
58+
>
59+
<Heading
60+
level={4}
61+
margin={{ bottom: isLarge ? "medium" : "small" }}
62+
className={`font-[Bitter] text-center ${isLarge ? "text-[1.2rem]" : ""}`}
63+
>
64+
Related Posts
65+
</Heading>
66+
67+
<Box direction="column" gap={isLarge ? "medium" : "small"}>
68+
{relatedPosts.length > 0 ? (
69+
relatedPosts.map((post) => (
70+
<RelatedPostItem key={post.id} post={post} />
71+
))
72+
) : (
73+
<Text size={isLarge ? "medium" : "small"} color="dark-3" className="text-center">
74+
No related posts found
75+
</Text>
76+
)}
77+
</Box>
78+
</Box>
79+
);
80+
}}
81+
</ResponsiveContext.Consumer>
82+
);
83+
};
84+
85+
export default BlogSidebar;

src/components/atomic/layout/narrow-content-wrapper.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const NarrowContentWrapper = ({ children, justify, width, ...props }) => {
3131

3232
const MediumSection = ({ children }) => (
3333
<Box
34-
width={width ? width : "960px"}
34+
width={width ? width : "1110px"}
3535
alignSelf={"center"}
3636
flex={"grow"}
3737
justify={justify ? justify : "start"}

src/components/default-blog-layout.js

Lines changed: 116 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,95 @@
1-
import React, { useState, useEffect } from "react"
2-
import { graphql } from "gatsby"
3-
import { Box } from "grommet"
4-
import { MDXProvider } from "@mdx-js/react"
5-
import { Link } from "gatsby"
6-
import { primaryNav, footerItems } from "../config/options"
7-
import AppShell from "./atomic/AppShell"
8-
import BlogHeaderCard from "./atomic/BlogHeaderCard"
9-
import { PlainLink } from "./atomic/TattleLinks"
10-
import { Heading } from "grommet"
11-
import { useLocation } from "@reach/router"
12-
import CustomCodeBlock from "./atomic/customCodeBlock"
13-
import InlineCodeBlock from "./atomic/inlineCodeBlock"
14-
import useBlogTags from "../hooks/useBlogTags"
15-
16-
import { projectSlugMaker } from "../lib/project-slug-maker"
17-
import TagsRenderer from "./TagsRenderer"
18-
import { getSrc } from "gatsby-plugin-image"
1+
import React, { useState, useEffect } from "react";
2+
import { graphql } from "gatsby";
3+
import { Box, ResponsiveContext } from "grommet";
4+
import { MDXProvider } from "@mdx-js/react";
5+
import { Link } from "gatsby";
6+
import { primaryNav, footerItems } from "../config/options";
7+
import AppShell from "./atomic/AppShell";
8+
import BlogHeaderCard from "./atomic/BlogHeaderCard";
9+
import { PlainLink } from "./atomic/TattleLinks";
10+
import { Heading } from "grommet";
11+
import { useLocation } from "@reach/router";
12+
import CustomCodeBlock from "./atomic/customCodeBlock";
13+
import InlineCodeBlock from "./atomic/inlineCodeBlock";
14+
import useBlogTags from "../hooks/useBlogTags";
15+
import BlogSidebar from "./BlogSidebar";
16+
import { projectSlugMaker } from "../lib/project-slug-maker";
17+
import TagsRenderer from "./TagsRenderer";
18+
import { getSrc, getImage } from "gatsby-plugin-image";
1919

2020
const shortcodes = {
2121
Link,
2222
BlogHeaderCard,
2323
code: (props) => <CustomCodeBlock {...props} />,
2424
inlineCode: (props) => <InlineCodeBlock {...props} />,
25-
}
25+
};
2626

27-
export default function PageTemplate({ data: { mdx, allMdx }, children }) {
28-
const { tagCounts, projectTagsCounts } = useBlogTags()
29-
const { name, author, project, date, excerpt, cover } = mdx.frontmatter
27+
export default function PageTemplate({ data: { mdx }, pageContext: { blogNodes = [] }, children }) {
28+
const { tagCounts, projectTagsCounts } = useBlogTags();
29+
const { name, author, project, date, excerpt, cover } = mdx.frontmatter;
3030
const tags = mdx.frontmatter.tags
3131
? mdx.frontmatter.tags.split(",").map((tag) => tag.trim())
32-
: []
32+
: [];
3333

34-
const location = useLocation()
35-
const [label, setLabel] = useState("")
34+
const location = useLocation();
35+
const [label, setLabel] = useState("");
3636

3737
useEffect(() => {
38-
setLabel(location.pathname.split("/")[1])
39-
console.log({ l2: location.pathname })
40-
}, [location])
38+
setLabel(location.pathname.split("/")[1]);
39+
}, [location]);
40+
41+
42+
const findRelatedPosts = () => {
43+
// If no tags, return 5 most recent posts
44+
if (!blogNodes.length) {
45+
return [];
46+
}
47+
if (!tags.length) {
48+
return blogNodes
49+
.filter(node => node.id !== mdx.id)
50+
.sort((a, b) => new Date(b.frontmatter.date) - new Date(a.frontmatter.date))
51+
.slice(0, 5)
52+
.map(post => ({
53+
id: post.id,
54+
title: post.frontmatter.name,
55+
slug: post.fields.slug,
56+
coverImage: post.frontmatter.cover ? getImage(post.frontmatter.cover) : null,
57+
excerpt: post.frontmatter.excerpt,
58+
matchingTags: [],
59+
relevanceScore: 0
60+
}));
61+
}
62+
63+
const postsWithRelevance = blogNodes
64+
.filter(node => node.id !== mdx.id)
65+
.map(post => {
66+
const postTags = post.frontmatter.tags
67+
? post.frontmatter.tags.split(",").map(tag => tag.trim())
68+
: [];
69+
70+
const matchingTags = tags.filter(tag => postTags.includes(tag));
71+
72+
const coverImage = post.frontmatter.cover
73+
? getImage(post.frontmatter.cover)
74+
: null;
75+
76+
return {
77+
id: post.id,
78+
title: post.frontmatter.name,
79+
slug: post.fields.slug,
80+
coverImage: coverImage,
81+
excerpt: post.frontmatter.excerpt,
82+
matchingTags: matchingTags,
83+
relevanceScore: matchingTags.length
84+
};
85+
})
86+
.filter(post => post.relevanceScore > 0)
87+
.sort((a, b) => b.relevanceScore - a.relevanceScore);
88+
89+
return postsWithRelevance.slice(0, 5);
90+
};
91+
92+
const relatedPosts = findRelatedPosts();
4193

4294
return (
4395
<AppShell
@@ -60,6 +112,7 @@ export default function PageTemplate({ data: { mdx, allMdx }, children }) {
60112
<Heading level={4}>back to all blogs</Heading>
61113
</PlainLink>
62114
</Box>
115+
63116
<Box>
64117
<BlogHeaderCard
65118
name={name}
@@ -87,18 +140,51 @@ export default function PageTemplate({ data: { mdx, allMdx }, children }) {
87140
</Box>
88141
)}
89142
</Box>
143+
</Box>
144+
<ResponsiveContext.Consumer>
145+
{size => {
146+
const isLarge = size === "large" || size === "xlarge";
147+
return (
148+
<Box
149+
direction={size === "small" ? "column" : "row"}
150+
gap={isLarge ? "xlarge" : "medium"}
151+
align="start"
152+
153+
>
154+
{/* Main Content */}
155+
<Box
156+
flex={true}
157+
width={size === "small" ? "100%" : undefined}
90158

159+
>
91160
{children}
92161
</Box>
162+
163+
{/* Sidebar Section */}
164+
<Box
165+
width={size === "small" ? "100%" : isLarge ? "320px" : "390px"}
166+
167+
flex={false}
168+
>
169+
<BlogSidebar relatedPosts={relatedPosts} />
170+
</Box>
171+
</Box>
172+
);
173+
}}
174+
</ResponsiveContext.Consumer>
93175
</MDXProvider>
94176
</AppShell>
95-
)
177+
);
96178
}
179+
97180
export const pageQuery = graphql`
98181
query BlogPostQuery($id: String) {
99182
mdx(id: { eq: $id }) {
100183
id
101184
body
185+
fields {
186+
slug
187+
}
102188
frontmatter {
103189
name
104190
excerpt
@@ -113,15 +199,5 @@ export const pageQuery = graphql`
113199
}
114200
}
115201
}
116-
allMdx {
117-
nodes {
118-
frontmatter {
119-
name
120-
author
121-
date
122-
tags
123-
}
124-
}
125-
}
126202
}
127-
`
203+
`;

0 commit comments

Comments
 (0)