Skip to content
This repository has been archived by the owner on Aug 3, 2024. It is now read-only.

Commit

Permalink
add blog posts
Browse files Browse the repository at this point in the history
  • Loading branch information
sametcn99 committed Mar 3, 2024
1 parent 6df717a commit 2dfd1d7
Show file tree
Hide file tree
Showing 10 changed files with 305 additions and 6,821 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,4 @@ next-env.d.ts

.vscode
.vscode/settings.json
pnpm-lock.yaml
66 changes: 66 additions & 0 deletions app/blog/[postId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { notFound } from "next/navigation"; // Importing the function to show the "not found" page
import { getPostData, getSortedPostsData } from "@/lib/utils/blog/getPosts";
import { Card } from "@radix-ui/themes";
import getFormattedDate from "@/lib/utils";
import "./styles.css";

// Function to generate static page parameters
export function generateStaticParams() {
const posts = getSortedPostsData(); // Getting sorted post data
return posts.map((post) => ({
postId: post.id, // Using the post's ID as a parameter
}));
}

// Function to generate page metadata
export function generateMetadata({ params }: { params: { postId: string } }) {
const posts = getSortedPostsData(); // Getting sorted post data
const { postId } = params; // Extracting the post ID from parameters

const post = posts.find((post) => post.id === postId); // Finding the post with the given ID

if (!post) {
return {
title: "Post Not Found", // Using "Post Not Found" as the title if the post is not found
description: "This post does not exist.", // Add a description for SEO
keywords: [], // Add an empty tags array to avoid errors
author: "Unknown", // Add an unknown author to avoid errors
};
}

return {
title: post.title, // Using the post's title as the title
description: post.description,
keywords: post.keywords, // Using the post's tags as the tags
author: post.author, // Using the post's author as the author
};
}

// Main function to create the post page
export default async function Post({ params }: { params: { postId: string } }) {
const posts = getSortedPostsData(); // Getting sorted post data
const { postId } = params; // Extracting the post ID from parameters
const page = posts.find((post) => post.id === postId); // Finding the post with the given ID
if (!page) notFound(); // Displaying a 404 error if the post is not found
const { title, date, contentHtml, author, keywords } =
await getPostData(postId); // Fetching post data

const pubDate = getFormattedDate(date); // Getting a formatted version of the date

return (
<div className="mt-10 flex min-h-screen w-full justify-center px-2">
<Card>
<article>
<h1 className="text-3xl font-bold">{title}</h1>
<p className="font-thin">
{pubDate} / Author: {author}
</p>
<article
dangerouslySetInnerHTML={{ __html: contentHtml }}
className="article"
/>
</article>
</Card>
</div>
);
}
69 changes: 69 additions & 0 deletions app/blog/[postId]/styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/* Heading Styles for article */
.article h1 {
margin-top: 1rem;
font-size: 2.25rem;
font-weight: bold;
}

.article h2 {
font-size: 1.5rem;
font-weight: bold;
}

.article h3 {
font-size: 1.25rem;
font-weight: bold;
}

/* Paragraph Styles for article */
.article p {
font-size: 1rem;
line-height: 1.6;
}

/* Emphasis for article */
.article em {
font-style: italic;
}

/* Bold Text for article */
.article strong {
font-weight: bold;
}

/* Link Styles for article */
.article a {
text-decoration: none;
}

/* Visited Links for article */
.article a:visited {
}

/* Hovering Over Links for article */
.article a:hover {
text-decoration: underline;
}

/* Lists for article */
.article ul {
list-style-type: disc;
margin-left: 1.5rem;
}

.article ol {
list-style-type: decimal;
margin-left: 1.5rem;
}

.article li {
font-size: 1rem;
margin-bottom: 0.5rem;
}

/* Block Quotes for article */
.article blockquote {
border-left: 0.25rem solid #333;
margin: 0;
padding: 1rem 2rem;
}
17 changes: 17 additions & 0 deletions app/sitemap.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
import { getSiteUrl } from "@/lib/utils";
import { getSortedPostsData } from "@/lib/utils/blog/getPosts";
import { MetadataRoute } from "next";

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = getSortedPostsData();

return [
{
url: "https://www.githubprofileviewer.com/",
lastModified: new Date(),
changeFrequency: "monthly",
priority: 1,
},
...posts.map((post: BlogPost) => ({
url: `${getSiteUrl()}/blog/${post.title}`,
lastModified: new Date(),
changeFrequency: "monthly" as
| "monthly"
| "always"
| "hourly"
| "daily"
| "weekly"
| "yearly"
| "never",
priority: 1,
})),
];
}
17 changes: 17 additions & 0 deletions lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,20 @@ export const extractUniqueValues = <T, K extends keyof T>(
});
return Array.from(uniqueSet);
};

// This function takes a date string as input and returns a formatted date string.
/**
* Formats a date string into a localized long date format.
* @param dateString - The date string to format.
* @returns The formatted date string.
*/
export default function getFormattedDate(dateString: string): string {
// Create a new Date object from the input date string.
const date = new Date(dateString);

// Use the Intl.DateTimeFormat constructor to create a formatter.
const formatter = new Intl.DateTimeFormat("en-US", { dateStyle: "long" });

// Use the formatter to format the date string.
return formatter.format(date);
}
108 changes: 108 additions & 0 deletions lib/utils/blog/getPosts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import fs from "fs";
import path from "path";
import matter from "gray-matter";
import { remark } from "remark";
import html from "remark-html";

// The directory where blog post files are stored.
const postsDirectory = path.join(process.cwd(), "public/posts");

// This function retrieves and sorts data from Markdown files representing blog posts.
/**
* Gets all posts data sorted by date in descending order.
*
* Reads the Markdown files from the posts directory, parses the metadata,
* and returns an array of BlogPost objects sorted by date.
*/
export function getSortedPostsData() {
// Get file names under /posts directory
const fileNames = fs.readdirSync(postsDirectory);

// Create an array to store all post data
const allPostsData = fileNames.map((fileName) => {
// Remove ".md" extension from the file name to get the post ID
const id = fileName.replace(/\.md$/, "");

// Read the content of the Markdown file as a string
const fullPath = path.join(postsDirectory, fileName);
const fileContents = fs.readFileSync(fullPath, "utf8");

// Use gray-matter to parse the post's metadata section
const matterResult = matter(fileContents);

// Create a BlogPost object with extracted data
const blogPost: BlogPost = {
id,
title: matterResult.data.title,
date: matterResult.data.date,
description: matterResult.data.description,
author: matterResult.data.author,
keywords: matterResult.data.keywords,
};
//console.log(blogPost.date);
// Combine the data with the post ID
return blogPost;
});

// Sort the posts by date in descending order
return allPostsData.sort((a, b) => (a.date < b.date ? 1 : -1));
}

/**
* Filters the posts from getSortedPostsData() to only
* include posts with a date between the given start and end dates.
*
* @param startDate - The minimum date for posts to include
* @param endDate - The maximum date for posts to include
* @returns The filtered array of posts within the date range
*/
export const getPostsInDateRange = (startDate: any, endDate: any) => {
console.log("startDate: " + startDate + "\nendDate: " + endDate);
// Get all posts
const allPostsData = getSortedPostsData();

// Filter posts that fall within the specified date range
const postsInDateRange = allPostsData.filter((post) => {
const postDate = new Date(post.date);
return postDate >= startDate && postDate <= endDate;
});
return postsInDateRange;
};

// This asynchronous function retrieves and processes data for a specific blog post.
/**
* Retrieves the metadata and content for a blog post by ID, converts the content from Markdown to HTML,
* and returns a BlogPost object enriched with the generated HTML content.
*/
export async function getPostData(id: string) {
// Construct the full path to the Markdown file for the specified post
const fullPath = path.join(postsDirectory, `${id}.md`);

// Read the content of the Markdown file as a string
const fileContents = fs.readFileSync(fullPath, "utf8");

// Use gray-matter to parse the post's metadata section
const matterResult = matter(fileContents);

// Process the content using the remark and html plugins to convert Markdown to HTML
const processedContent = await remark()
.use(html)
.process(matterResult.content);

// Convert the processed content to a string of HTML
const contentHtml = processedContent.toString();

// Create a BlogPost object with HTML content
const blogPostWithHTML: BlogPost & { contentHtml: string } = {
id,
title: matterResult.data.title,
date: matterResult.data.date,
description: matterResult.data.description,
author: matterResult.data.author,
contentHtml,
keywords: matterResult.data.keywords,
};

// Combine the data with the post's ID and return it
return blogPostWithHTML;
}
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"clsx": "^2.1.0",
"cmdk": "^0.2.1",
"date-fns": "^3.3.1",
"gray-matter": "^4.0.3",
"install": "^0.13.0",
"lodash": "^4.17.21",
"lucide-react": "^0.335.0",
Expand All @@ -40,6 +41,9 @@
"react-hook-form": "^7.50.1",
"react-icons": "^5.0.1",
"react-json-view-lite": "^1.2.1",
"react-markdown": "^9.0.1",
"remark": "^15.0.1",
"remark-html": "^16.0.1",
"sonner": "^1.4.0",
"tailwind-merge": "^2.2.1",
"tailwindcss-animate": "^1.0.7",
Expand Down
Loading

1 comment on commit 2dfd1d7

@vercel
Copy link

@vercel vercel bot commented on 2dfd1d7 Mar 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.