Skip to content

Commit

Permalink
feat: list post per tags & directory
Browse files Browse the repository at this point in the history
  • Loading branch information
ridhozhr10 committed May 22, 2024
1 parent ffb7158 commit 102783b
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 23 deletions.
4 changes: 2 additions & 2 deletions src/app/_components/BlogPost/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
BiLogoLinkedinSquare,
BiLogoTwitter,
BiShare,
BiTag,
BiTagAlt,
BiX,
} from "react-icons/bi";
import {
Expand Down Expand Up @@ -112,7 +112,7 @@ export default function BlogPost({
<div className="post-info">
{post.tags.length > 0 && (
<p>
<BiTag className="feather" />
<BiTagAlt className="feather" />
{post.tags.map((tag) => (
<span className="tag" key={tag}>
<Link href={`/tags/${tag}`}>{tag}</Link>
Expand Down
74 changes: 63 additions & 11 deletions src/app/posts/[...path]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,57 @@
import BlogPost from "@/app/_components/BlogPost";
import BaseLayout from "@/app/_components/layout/BaseLayout";
import { baseURL } from "@/constants";
import { getAllPosts, getNextPreviousPost, getPostBySlug } from "@/lib/api";
import {
getNextPreviousPost,
getPostAndDirSlugs,
getPostOrDirBySlug,
} from "@/lib/api";
import mdToHtml from "@/lib/markdown";
import dayjs from "dayjs";
import { Metadata } from "next";
import Link from "next/link";
import { notFound } from "next/navigation";
import "@/app/posts/style.scss";

type Props = {
params: { path: string[]; realPath: string };
params: { path: string[] };
};

export default async function Post({ params }: Props) {
const post = getPostBySlug(params.path.join("/"));
const data = getPostOrDirBySlug(params.path.join("/"));
if (data.type === "dir") {
if (data.dir.length < 0) {
return notFound();
}
return (
<BaseLayout logoText={`ls -h $POSTS_DIR/${params.path.join("/")}`}>
<main className="post">
<h1 className="text-5xl font-bold my-6">
Posts on {params.path.join("/")}
</h1>
<div className="posts-group">
<ul className="posts-list">
{data.dir.map((post) => (
<li key={post.path.join("/")} className="post-item">
<Link
href={`/posts/${post.path.join("/")}`}
className="post-item-inner"
>
<span className="post-title">{post.title}</span>
<span className="post-day">
{dayjs(post.created_at).format("MMM DD, YYYY")}
</span>
</Link>
</li>
))}
</ul>
</div>
</main>
</BaseLayout>
);
}

const { post } = data;
const pagination = getNextPreviousPost(post);
if (!post) {
return notFound();
Expand All @@ -28,14 +68,24 @@ export default async function Post({ params }: Props) {
}

export function generateMetadata({ params }: Props): Metadata {
const post = getPostBySlug(params.path.join("/"));

if (!post) {
const data = getPostOrDirBySlug(params.path.join("/"));
if (!data) {
return notFound();
}
if (data.type === "dir") {
if (data.dir.length < 0) {
return notFound();
}
return {
title: `${params.path.join("/")} Posts :: Ridho Azhar`,
};
}

if (!data.post) {
return notFound();
}
const post = data.post;
const title = `${post.title} :: Ridho Azhar`;

return {
title,
description: post.description,
Expand All @@ -54,9 +104,11 @@ export function generateMetadata({ params }: Props): Metadata {
}

export async function generateStaticParams() {
const posts = getAllPosts();
const slugs = getPostAndDirSlugs();

return posts.map((post) => ({
path: post.path,
}));
return slugs.map((slug) => {
return {
path: slug.split("/"),
};
});
}
2 changes: 1 addition & 1 deletion src/app/posts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ export default function Posts() {
var postGroups = getPostGroupByYear();

return (
<BaseLayout logoText="cd /Documents/posts">
<BaseLayout logoText="ls -h $POSTS_DIR">
<main className="post">
<h1 className="text-5xl font-bold my-6">Posts</h1>
{postGroups.map((postGroup) => (
Expand Down
58 changes: 58 additions & 0 deletions src/app/tags/[tag]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { getAllTags, getPostGroupByYear } from "@/lib/api";
import BaseLayout from "@/app/_components/layout/BaseLayout";
import Link from "next/link";
import dayjs from "dayjs";
import "@/app/posts/style.scss";
import { Metadata } from "next";

type Props = {
params: {
tag: string;
};
};

export default function Tag({ params }: Props) {
var postGroups = getPostGroupByYear(params.tag);

return (
<BaseLayout logoText={`ls $POSTS_DIR | grep ${params.tag}`}>
<main className="post">
<h1 className="text-5xl font-bold my-6">{params.tag}</h1>
{postGroups.map((postGroup) => (
<div key={postGroup.year} className="posts-group">
<div className="post-year">{postGroup.year}</div>
<ul className="posts-list">
{postGroup.data.map((post) => (
<li key={post.slug} className="post-item">
<Link
href={`/posts/${post.slug}`}
className="post-item-inner"
>
<span className="post-title">{post.title}</span>
<span className="post-day">
{dayjs(post.date).format("MMM DD")}
</span>
</Link>
</li>
))}
</ul>
</div>
))}
</main>
</BaseLayout>
);
}
export function generateMetadata({ params }: Props): Metadata {
const title = `#${params.tag} :: Ridho Azhar`;

return {
title,
robots: { follow: true, index: true },
};
}
export function generateStaticParams() {
const tags = getAllTags();
return tags.map((tag) => ({
tag,
}));
}
95 changes: 86 additions & 9 deletions src/lib/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Post } from "@/interfaces/post";
import { lstatSync, readFileSync, readdirSync } from "fs";
import { join, sep } from "path";
import { dirname, join, sep } from "path";
import matter from "gray-matter";

const postsDir = join(process.cwd(), "_contents", "posts");
Expand All @@ -22,16 +22,72 @@ export function getPostSlugs(): string[] {
return readDir(postsDir) || [];
}

export function getPostBySlug(slug: string): Post {
export function getPostAndDirSlugs(): string[] {
const readDir = (path: string): string[] | undefined => {
const pathSplit = path.split(sep).map((d) => d.replace(/\.md$/, ""));
const postsDirIndex = pathSplit.indexOf("posts");

if (lstatSync(path).isDirectory()) {
const currentDirName = pathSplit
.filter((_, i) => i > postsDirIndex)
.join("/");
return [
currentDirName,
...readdirSync(path)
.map((childPath) => readDir(join(path, childPath)))
.flat(),
] as string[];
} else {
// get from year only
return [pathSplit.filter((_, i) => i > postsDirIndex).join("/")];
}
};

const res = readDir(postsDir) || [];
return res.filter((d) => d);
}

interface SinglePost {
type: "post";
post: Post;
}

interface Directory {
type: "dir";
dir: Post[];
}

export type PostOrDir = SinglePost | Directory;

export function getPostOrDirBySlug(slug: string): PostOrDir {
const split = slug.split("/").map((d) => d.replace(/\.md$/, ""));
const fullPath = join(postsDir, ...split);
try {
if (lstatSync(fullPath).isDirectory()) {
return {
type: "dir",
dir: getAllPosts().filter((d) => {
const split = slug.split("/");
let res = true;
for (let i = 0; i < split.length; i++) {
res = split[i] === d.path[i];
if (!res) break;
}
return res;
}),
};
}
} catch {
// do nothing
}

const fileContents = readFileSync(`${fullPath}.md`, "utf8");
const { data, content } = matter(fileContents);
const post: Post = { ...(data as Post), path: split, content };
return {
...data,
path: split,
content: content,
} as Post;
post,
type: "post",
};
}

export type SinglePagination = { prev?: Post; next?: Post };
Expand All @@ -56,8 +112,11 @@ export function getNextPreviousPost(post: Post): SinglePagination {

export function getAllPosts(): Post[] {
const slugs = getPostSlugs();
const posts = slugs
.map((slug) => getPostBySlug(slug))
const postList: SinglePost[] = slugs
.map((slug) => getPostOrDirBySlug(slug))
.filter((d) => d.type === "post") as SinglePost[];
const posts = postList
.map((d) => d.post as Post)
.sort((a, b) => {
return a.path.join("/") > b.path.join("/") ? -1 : 1;
});
Expand All @@ -73,16 +132,21 @@ type PostGroup = {
}[];
};

export function getPostGroupByYear(): PostGroup[] {
export function getPostGroupByYear(tag?: string): PostGroup[] {
const result: PostGroup[] = [];
getAllPosts().forEach((post) => {
// filter
if (tag && !post.tags.includes(tag)) {
return;
}
const [year] = post.path;
const idxResult = result.map((d) => d.year).indexOf(year);
const data = {
slug: post.path.join("/"),
title: post.title,
date: new Date(post.created_at),
};

if (idxResult < 0) {
result.push({
year,
Expand All @@ -94,3 +158,16 @@ export function getPostGroupByYear(): PostGroup[] {
});
return result;
}

export function getAllTags(): string[] {
const res: string[] = ["tag6"];
getAllPosts().forEach((post) => {
post.tags.forEach((tag) => {
if (!res.includes(tag)) {
res.push(tag);
}
});
});

return res;
}

0 comments on commit 102783b

Please sign in to comment.