Skip to content

Commit b12edbd

Browse files
added reading time calc
1 parent 18e6992 commit b12edbd

3 files changed

Lines changed: 36 additions & 1 deletion

File tree

src/components/PostCard.astro

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
---
22
import type { CollectionEntry } from 'astro:content';
3+
import { getReadingTimeLabel } from '../utils/readingTime';
34
45
interface Props {
56
post: CollectionEntry<'blog'>;
67
}
78
89
const { post } = Astro.props;
910
const { title, description, pubDate } = post.data;
11+
const readingTime = getReadingTimeLabel(post.body);
1012
---
1113

1214
<a href={`/blog/${post.slug}`} class="group flex flex-col h-full bg-blog-code-bg rounded-2xl overflow-hidden shadow-sm hover:shadow-md duration-300 border border-blog-border hover:border-blog-accent transition-all duration-300">
@@ -17,14 +19,16 @@ const { title, description, pubDate } = post.data;
1719
</div>
1820

1921
<div class="p-6 flex flex-col flex-grow">
20-
<div class="text-sm text-blog-text mb-2">
22+
<div class="mb-2 flex items-center gap-2 text-sm text-blog-text">
2123
<time datetime={pubDate.toISOString()}>
2224
{pubDate.toLocaleDateString('en-us', {
2325
year: 'numeric',
2426
month: 'short',
2527
day: 'numeric',
2628
})}
2729
</time>
30+
<span aria-hidden="true">&middot;</span>
31+
<span>{readingTime}</span>
2832
</div>
2933
<p class="text-blog-text text-sm line-clamp-3">
3034
{description}

src/pages/blog/[...slug].astro

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import { type CollectionEntry, getCollection } from 'astro:content';
33
import Layout from '../../layouts/Layout.astro';
44
import PostHogLayout from '../../layouts/PostHogLayout.astro';
5+
import { getReadingTimeLabel } from '../../utils/readingTime';
56
67
export async function getStaticPaths() {
78
const posts = await getCollection('blog');
@@ -15,6 +16,7 @@ type Props = CollectionEntry<'blog'>;
1516
const post = Astro.props;
1617
const { Content, headings } = await post.render();
1718
const tocHeadings = headings.filter((heading) => heading.depth >= 2 && heading.depth <= 3);
19+
const readingTime = getReadingTimeLabel(post.body);
1820
---
1921

2022
<PostHogLayout>
@@ -36,6 +38,9 @@ const tocHeadings = headings.filter((heading) => heading.depth >= 2 && heading.d
3638
{post.data.title}
3739
</h1>
3840
<h2>{post.data.description}</h2>
41+
<h3 class="mx-auto mt-3 inline-flex rounded-full border border-blog-border bg-blog-code-bg/60 px-3 py-1 text-sm font-medium italic tracking-wide text-blog-link/90">
42+
{readingTime}
43+
</h3>
3944
</div>
4045

4146
{tocHeadings.length > 0 && (

src/utils/readingTime.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const WORDS_PER_MINUTE = 200;
2+
3+
function stripMarkdown(markdown: string) {
4+
return markdown
5+
.replace(/```[\s\S]*?```/g, ' ')
6+
.replace(/`[^`]*`/g, ' ')
7+
.replace(/!\[\[[^\]]+]]/g, ' ')
8+
.replace(/\[\[[^\]]+]]/g, ' ')
9+
.replace(/!\[[^\]]*]\([^)]*\)/g, ' ')
10+
.replace(/\[([^\]]+)]\([^)]*\)/g, '$1')
11+
.replace(/<[^>]+>/g, ' ')
12+
.replace(/[>#*_~`-]/g, ' ')
13+
.replace(/\s+/g, ' ')
14+
.trim();
15+
}
16+
17+
export function getReadingTimeMinutes(markdown: string, wordsPerMinute = WORDS_PER_MINUTE) {
18+
const plainText = stripMarkdown(markdown);
19+
const words = plainText ? plainText.split(' ').length : 0;
20+
return Math.max(1, Math.ceil(words / wordsPerMinute));
21+
}
22+
23+
export function getReadingTimeLabel(markdown: string, wordsPerMinute = WORDS_PER_MINUTE) {
24+
const minutes = getReadingTimeMinutes(markdown, wordsPerMinute);
25+
return `${minutes} min read`;
26+
}

0 commit comments

Comments
 (0)