Skip to content

Commit 23682e4

Browse files
committedFeb 5, 2022
make category pages
1 parent 34c9e72 commit 23682e4

File tree

10 files changed

+178
-115
lines changed

10 files changed

+178
-115
lines changed
 

‎components/BlogPage/index.js

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/* eslint-disable jsx-a11y/anchor-is-valid */
2+
import Link from 'next/link';
3+
import PropTypes from 'prop-types';
4+
import Layout from 'components/Layout';
5+
import PostList from 'components/PostList';
6+
7+
const BlogPage = ({
8+
posts,
9+
currentCategory,
10+
socialMeta,
11+
categories,
12+
breadcrumbs
13+
}) => (
14+
<Layout socialMeta={socialMeta} breadcrumbs={breadcrumbs}>
15+
<div className='container'>
16+
<h2 className='section__title'>Blog</h2>
17+
<div className='categories'>
18+
{categories.map(category => (
19+
<Link
20+
key={category}
21+
href={category === currentCategory ? '/blog' : `/blog/${category}`}
22+
>
23+
<a className={category === currentCategory ? 'active' : ''}>
24+
{category}
25+
</a>
26+
</Link>
27+
))}
28+
</div>
29+
<PostList posts={posts} />
30+
</div>
31+
</Layout>
32+
);
33+
34+
BlogPage.propTypes = {
35+
posts: PropTypes.array,
36+
currentCategory: PropTypes.string,
37+
categories: PropTypes.array,
38+
socialMeta: PropTypes.object,
39+
breadcrumbs: PropTypes.array
40+
};
41+
42+
export default BlogPage;

‎components/PostItem/index.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,12 @@ export default function PostItem({ item }) {
1212
</div>
1313
<h2 className={styles.post_item__title}>
1414
<Link href={`/post/${item.slug}`}>
15-
<a className={styles.post_item__title__link}>
16-
{item.frontmatter.title}
17-
</a>
15+
<a className={styles.post_item__title__link}>{item.title}</a>
1816
</Link>
1917
</h2>
2018
</div>
2119
<div className={styles.post_item__content}>
22-
{item.frontmatter.intro || item.frontmatter.description}
20+
{item.intro || item.description}
2321
</div>
2422
</li>
2523
);

‎components/PostList/index.js

+6-9
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,15 @@ import PropTypes from 'prop-types';
22
import PostItem from 'components/PostItem';
33
import styles from './post-list.module.scss';
44

5-
export default function PostList({ posts, max, filter }) {
5+
export default function PostList({ posts, max }) {
66
if (posts === 'undefined') return null;
7-
const filteredPosts = filter
8-
? posts.filter(post => post.frontmatter?.tags?.includes(filter))
9-
: posts;
7+
108
return (
119
<div>
12-
{!filteredPosts && <div>No posts!</div>}
10+
{!posts && <div>No posts!</div>}
1311
<ul className={styles.post_list}>
14-
{filteredPosts &&
15-
filteredPosts.map((post, i) => {
12+
{posts &&
13+
posts.map((post, i) => {
1614
if (i > max - 1) return null;
1715
return <PostItem key={post.slug} item={post} />;
1816
})}
@@ -23,6 +21,5 @@ export default function PostList({ posts, max, filter }) {
2321

2422
PostList.propTypes = {
2523
posts: PropTypes.array,
26-
max: PropTypes.number,
27-
filter: PropTypes.string
24+
max: PropTypes.number
2825
};

‎helpers/generate-social-meta.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ const generateSocialMeta = data => {
7474
};
7575
return Object.entries(withDefault)
7676
.filter(([key]) => META_TAG_TYPES[key])
77-
.filter(([key, value]) => Boolean(value))
77+
.filter(([, value]) => Boolean(value))
7878
.map(([key, value]) => META_TAG_TYPES[key](value));
7979
};
8080

‎helpers/get-categories.js

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
const getCategories = posts =>
2+
posts.reduce((tags, post) => {
3+
if (!post?.tags) return tags;
4+
const newTags = post.tags.split(', ').filter(tag => !tags.includes(tag));
5+
return [...tags, ...newTags];
6+
}, []);
7+
8+
export default getCategories;

‎helpers/parse-posts.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import matter from 'gray-matter';
2+
3+
const parsePosts = context => {
4+
const keys = context.keys();
5+
const values = keys.map(context);
6+
7+
const data = keys
8+
.map((key, index) => {
9+
const slug = key.replace(/^.*[\\/]/, '').slice(0, -3);
10+
const date = slug.match(/(\d{1,4}([.\--])\d{1,2}([.\--])\d{1,4})/g);
11+
const value = values[index];
12+
const document = matter(value.default);
13+
return {
14+
...document.data,
15+
markdownBody: document.content,
16+
slug,
17+
date
18+
};
19+
})
20+
.sort((a, b) => new Date(b.date) - new Date(a.date));
21+
return data;
22+
};
23+
24+
export default parsePosts;

‎pages/blog.js

+16-79
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import PropTypes from 'prop-types';
2-
import matter from 'gray-matter';
3-
import Layout from 'components/Layout';
4-
import PostList from 'components/PostList';
5-
import { useState, useEffect } from 'react';
2+
import BlogPage from 'components/BlogPage';
3+
import parsePosts from 'helpers/parse-posts';
4+
import getCategories from 'helpers/get-categories';
65

76
const socialMeta = {
87
image:
@@ -13,95 +12,33 @@ const socialMeta = {
1312
url: 'blog'
1413
};
1514

16-
const getTags = posts =>
17-
posts.reduce((tags, post) => {
18-
if (!post.frontmatter?.tags) return tags;
19-
const newTags = post.frontmatter.tags
20-
.split(', ')
21-
.filter(tag => !tags.includes(tag));
22-
return [...tags, ...newTags];
23-
}, []);
24-
25-
const Index = ({ posts, title }) => {
26-
const [filter, setFilter] = useState(null);
27-
28-
useEffect(() => {
29-
const { hash } = window.location;
30-
if (hash) {
31-
setFilter(hash.replace('#', ''));
32-
}
33-
34-
const onHashChanged = () => {
35-
const { hash: newHash } = window.location;
36-
setFilter(newHash.replace('#', ''));
37-
};
38-
39-
window.addEventListener('hashchange', onHashChanged);
40-
41-
return () => {
42-
window.removeEventListener('hashchange', onHashChanged);
43-
};
44-
}, []);
45-
46-
return (
47-
<Layout
48-
socialMeta={{ ...socialMeta, title }}
49-
breadcrumbs={[{ name: 'blog', item: 'blog/' }]}
50-
>
51-
<div className='container'>
52-
<h2 className='section__title'>Blog</h2>
53-
<div className='tags'>
54-
{getTags(posts).map(tag => (
55-
<a
56-
className={tag === filter ? 'active' : ''}
57-
key={tag}
58-
type='button'
59-
href={tag === filter ? '#' : `#${tag}`}
60-
>
61-
{tag}
62-
</a>
63-
))}
64-
</div>
65-
<PostList posts={posts} filter={filter} />
66-
</div>
67-
</Layout>
68-
);
69-
};
15+
const Index = props => (
16+
<BlogPage
17+
{...props}
18+
socialMeta={{ ...socialMeta, title: props.title }}
19+
breadcrumbs={[{ name: 'blog', item: `blog/${props.currentCategory}` }]}
20+
/>
21+
);
7022

7123
Index.propTypes = {
72-
posts: PropTypes.array,
73-
title: PropTypes.string
24+
title: PropTypes.string,
25+
currentCategory: PropTypes.string
7426
};
7527

7628
export default Index;
7729

7830
export async function getStaticProps() {
7931
const configData = await import('../siteconfig.json');
8032

81-
const posts = (context => {
82-
const keys = context.keys();
83-
const values = keys.map(context);
33+
const posts = parsePosts(require.context('../posts', true, /\.\/.*\.md$/));
8434

85-
const data = keys
86-
.map((key, index) => {
87-
const slug = key.replace(/^.*[\\/]/, '').slice(0, -3);
88-
const date = slug.match(/(\d{1,4}([.\--])\d{1,2}([.\--])\d{1,4})/g);
89-
const value = values[index];
90-
const document = matter(value.default);
91-
return {
92-
frontmatter: document.data,
93-
markdownBody: document.content,
94-
slug,
95-
date
96-
};
97-
})
98-
.sort((a, b) => new Date(b.date) - new Date(a.date));
99-
return data;
100-
})(require.context('../posts', true, /\.\/.*\.md$/));
35+
const categories = getCategories(posts);
10136

10237
return {
10338
props: {
10439
posts,
40+
categories,
41+
currentCategory: '',
10542
title: 'Blog | '.concat(configData.default.title)
10643
}
10744
};

‎pages/blog/[categoryname].js

+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import PropTypes from 'prop-types';
2+
import matter from 'gray-matter';
3+
import BlogPage from 'components/BlogPage';
4+
import parsePosts from 'helpers/parse-posts';
5+
import getCategories from 'helpers/get-categories';
6+
7+
const socialMeta = {
8+
image:
9+
'https://khendrikse.github.io/_next/static/chunks/images/portait-linoosk-db0fc2adaa55eb6080c20ff88376c1ba.png',
10+
imageAlt: 'Drawn avatar of khendrikse'
11+
};
12+
13+
const Index = props => (
14+
<BlogPage
15+
{...props}
16+
socialMeta={{
17+
...socialMeta,
18+
url: `blog/${props.currentCategory}`,
19+
description: `Blog about ${props.currentCategory}`,
20+
title: props.title
21+
}}
22+
breadcrumbs={[{ name: 'blog', item: `blog/${props.currentCategory}` }]}
23+
/>
24+
);
25+
26+
Index.propTypes = {
27+
title: PropTypes.string,
28+
currentCategory: PropTypes.string
29+
};
30+
31+
export default Index;
32+
33+
export async function getStaticProps({ ...ctx }) {
34+
const { categoryname } = ctx.params;
35+
const allPosts = parsePosts(
36+
require.context('../../posts', true, /\.\/.*\.md$/)
37+
);
38+
39+
const categories = getCategories(allPosts);
40+
const posts = allPosts.filter(post => post?.tags?.includes(categoryname));
41+
42+
const config = await import('../../siteconfig.json');
43+
44+
return {
45+
props: {
46+
title: 'Blog - '.concat(categoryname, ' | ', config.title),
47+
categories,
48+
posts,
49+
currentCategory: categoryname
50+
}
51+
};
52+
}
53+
54+
export async function getStaticPaths() {
55+
const posts = await (async context => {
56+
const keys = context.keys();
57+
const data = keys
58+
.map(key => key.replace(/^.*[\\/]/, '').slice(0, -3))
59+
.map(key => import(`../../posts/${key}.md`));
60+
61+
const allPosts = await Promise.all(data);
62+
return allPosts.map(post => matter(post.default).data);
63+
})(require.context('../../posts', true, /\.\/.*\.md$/));
64+
const categorySlugs = getCategories(posts);
65+
66+
const paths = categorySlugs.map(slug => `/blog/${slug}`);
67+
68+
return {
69+
paths,
70+
fallback: false
71+
};
72+
}

‎pages/index.js

+2-21
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import PropTypes from 'prop-types';
2-
import matter from 'gray-matter';
32
import Layout from 'components/Layout';
43
import PostList from 'components/PostList';
54
import styles from 'styles/home.module.scss';
5+
import parsePosts from 'helpers/parse-posts';
66
import createFeeds from '../scripts/feed';
77

88
const socialMeta = {
@@ -41,26 +41,7 @@ export async function getStaticProps() {
4141
createFeeds();
4242
}
4343

44-
const posts = (context => {
45-
const keys = context.keys();
46-
const values = keys.map(context);
47-
48-
const data = keys
49-
.map((key, index) => {
50-
const slug = key.replace(/^.*[\\/]/, '').slice(0, -3);
51-
const date = slug.match(/(\d{1,4}([.\--])\d{1,2}([.\--])\d{1,4})/g);
52-
const value = values[index];
53-
const document = matter(value.default);
54-
return {
55-
frontmatter: document.data,
56-
markdownBody: document.content,
57-
slug,
58-
date
59-
};
60-
})
61-
.sort((a, b) => new Date(b.date) - new Date(a.date));
62-
return data;
63-
})(require.context('../posts', true, /\.\/.*\.md$/));
44+
const posts = parsePosts(require.context('../posts', true, /\.\/.*\.md$/));
6445

6546
return {
6647
props: {

‎styles/blog.scss

+5-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@
44
margin-bottom: 48px;
55
}
66

7-
.tags {
7+
.categories {
8+
display: flex;
9+
flex-direction: row;
10+
flex-wrap: wrap;
11+
812
a {
913
font-size: 14px;
1014
display: inline;

0 commit comments

Comments
 (0)
Please sign in to comment.