Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: polish posts tests and documentation #531

Merged
merged 5 commits into from
Sep 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ needs.

### Create a Blog Post

1. Create a `.md` file in `/data/posts` with the filename as the slug of the
blog post URL. E.g. a file with path `/data/posts/hello-there.md` will have
1. Create a `.md` file in the [/posts](/posts) with the filename as the slug of
the blog post URL. E.g. a file with path `/posts/hello-there.md` will have
path `/blog/hello-there`.
1. Write the
[Front Matter](https://daily-dev-tips.com/posts/what-exactly-is-frontmatter/)
Expand All @@ -143,7 +143,7 @@ needs.
````md
---
title: This is my first blog post!
published_at: 2022-11-04T15:00:00.000Z
publishedAt: 2022-11-04T15:00:00.000Z
summary: This is an excerpt of my first blog post.
---

Expand All @@ -162,7 +162,7 @@ needs.
1. Navigate to the URL of the newly created blog post. E.g.
`http://localhost:8000/blog/hello-there`.

See other examples of blog post files in `/data/posts`.
See other examples of blog post files in [/posts](/posts).

### Themes

Expand Down
2 changes: 1 addition & 1 deletion components/Head.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2023 the Deno authors. All rights reserved. MIT license.
import { Head as _Head } from "$fresh/runtime.ts";
import Meta, { type MetaProps } from "./Meta.tsx";
import { SITE_DESCRIPTION, SITE_NAME } from "../utils/constants.ts";
import { SITE_DESCRIPTION, SITE_NAME } from "@/utils/constants.ts";
import { ComponentChildren } from "preact";

/**
Expand Down
2 changes: 1 addition & 1 deletion data/posts/first-post.md → posts/first-post.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: This is my first blog post!
published_at: 2022-11-04T15:00:00.000Z
publishedAt: 2022-11-04T15:00:00.000Z
summary: This is an excerpt of my first blog post.
---

Expand Down
2 changes: 1 addition & 1 deletion data/posts/second-post.md → posts/second-post.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Second post
published_at: 2022-11-04T15:00:00.000Z
publishedAt: 2022-11-04T15:00:00.000Z
summary: Lorem Ipsum is simply dummy text of the printing and typesetting industry.
---

Expand Down
2 changes: 1 addition & 1 deletion routes/blog/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ function PostCard(props: Post) {
</h2>
{props.publishedAt.toString() !== "Invalid Date" && (
<time class="text-gray-500">
{new Date(props.publishedAt).toLocaleDateString("en-US", {
{props.publishedAt.toLocaleDateString("en-US", {
dateStyle: "long",
})}
</time>
Expand Down
77 changes: 58 additions & 19 deletions utils/posts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@
import { extract } from "std/front_matter/yaml.ts";
import { join } from "std/path/mod.ts";

/**
* This code is based on the
* {@link https://deno.com/blog/build-a-blog-with-fresh|How to Build a Blog with Fresh}
* blog post.
*/

export interface Post {
slug: string;
title: string;
Expand All @@ -10,32 +16,65 @@ export interface Post {
summary: string;
}

export async function getPosts(): Promise<Post[]> {
const files = Deno.readDir("./data/posts");
const promises = [];
for await (const file of files) {
const slug = file.name.replace(".md", "");
promises.push(getPost(slug));
}
const posts = await Promise.all(promises) as Post[];
posts.sort((a, b) => b.publishedAt.getTime() - a.publishedAt.getTime());
return posts;
}

/**
* Returns a {@linkcode Post} object of by reading and parsing a file with the
* given slug in the `./posts` folder. Returns `null` if the given file is
* not a readable or parsable file.
*
* @see {@link https://deno.land/api?s=Deno.readTextFile}
*
* @example
* ```ts
* import { getPost } from "@/utils/posts.ts";
*
* const post = await getPost("first-post")!;
*
* post?.title; // Returns "This is my first blog post!"
* post?.publishedAt; // Returns 2022-11-04T15:00:00.000Z
* post?.slug; // Returns "This is an excerpt of my first blog post."
* post?.content; // Returns '# Heading 1\n\nHello, world!\n\n```javascript\nconsole.log("Hello World");\n```\n'
* ```
*/
export async function getPost(slug: string): Promise<Post | null> {
try {
const text = await Deno.readTextFile(
join("./data/posts", `${slug}.md`),
);
const { attrs, body } = extract(text);
const text = await Deno.readTextFile(join("./posts", `${slug}.md`));
const { attrs, body } = extract<Post>(text);
return {
...attrs,
slug,
title: attrs.title as string,
publishedAt: new Date(attrs.published_at as Date),
content: body,
summary: attrs.summary as string || "",
};
} catch {
return null;
}
}

/**
* Returns an array of {@linkcode Post} objects by reading and parsing files
* in the `./posts` folder.
*
* @see {@link https://deno.land/api?s=Deno.readDir}
*
* @example
* ```ts
* import { getPosts } from "@/utils/posts.ts";
*
* const posts = await getPosts();
*
* posts[0].title; // Returns "This is my first blog post!"
* posts[0].publishedAt; // Returns 2022-11-04T15:00:00.000Z
* posts[0].slug; // Returns "This is an excerpt of my first blog post."
* posts[0].content; // Returns '# Heading 1\n\nHello, world!\n\n```javascript\nconsole.log("Hello World");\n```\n'
* ```
*/
export async function getPosts(): Promise<Post[]> {
const files = Deno.readDir("./posts");
const promises = [];
for await (const file of files) {
const slug = file.name.replace(".md", "");
promises.push(getPost(slug));
}
const posts = await Promise.all(promises) as Post[];
posts.sort((a, b) => b.publishedAt.getTime() - a.publishedAt.getTime());
return posts;
}
19 changes: 2 additions & 17 deletions utils/posts_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,10 @@ Deno.test("[blog] getPost()", async () => {
assertEquals(post.publishedAt, new Date("2022-11-04T15:00:00.000Z"));
assertEquals(post.summary, "This is an excerpt of my first blog post.");
assertEquals(post.title, "This is my first blog post!");
assertEquals(await getPost("third-post"), null);
});

Deno.test("[blog] getPost() with missing frontmatter attributes", async () => {
const post = await getPost("second-post");
assert(post);
assertEquals(post.publishedAt, new Date("2022-11-04T15:00:00.000Z"));
assertEquals(
post.summary,
"Lorem Ipsum is simply dummy text of the printing and typesetting industry.",
);
assertEquals(post.title, "Second post");
});

Deno.test("[blog] getPost() for non-existent post", async () => {
const post = await getPost("third-post");
assertEquals(post, null);
});

Deno.test("[blog] getPosts() from data directory", async () => {
Deno.test("[blog] getPosts()", async () => {
const posts = await getPosts();
assert(posts);
assertEquals(posts.length, 2);
Expand Down