feat: add posts content layer with gray-matter + compileMDX
This commit is contained in:
77
lib/posts.ts
Normal file
77
lib/posts.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import fs from 'fs/promises'
|
||||
import path from 'path'
|
||||
import matter from 'gray-matter'
|
||||
import { compileMDX } from 'next-mdx-remote/rsc'
|
||||
import { cache } from 'react'
|
||||
import { useMDXComponents } from '@/mdx-components'
|
||||
|
||||
const postsDirectory = path.join(process.cwd(), 'content/posts')
|
||||
|
||||
export interface PostMeta {
|
||||
slug: string
|
||||
title: string
|
||||
date: string
|
||||
excerpt: string
|
||||
}
|
||||
|
||||
export interface Post extends PostMeta {
|
||||
source: string
|
||||
}
|
||||
|
||||
const getMdxFiles = cache(async () => {
|
||||
try {
|
||||
const files = await fs.readdir(postsDirectory)
|
||||
return files.filter((f) => f.endsWith('.mdx') || f.endsWith('.md'))
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
})
|
||||
|
||||
export const getPosts = cache(async (): Promise<PostMeta[]> => {
|
||||
const files = await getMdxFiles()
|
||||
return Promise.all(
|
||||
files.map(async (file) => {
|
||||
const filePath = path.join(postsDirectory, file)
|
||||
const raw = await fs.readFile(filePath, 'utf8')
|
||||
const { data } = matter(raw)
|
||||
return {
|
||||
slug: file.replace(/\.(mdx|md)$/, ''),
|
||||
title: data.title ?? file,
|
||||
date: data.date ?? 'Unknown',
|
||||
excerpt: data.excerpt ?? '',
|
||||
} as PostMeta
|
||||
})
|
||||
).then((posts) =>
|
||||
posts.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
|
||||
)
|
||||
})
|
||||
|
||||
export const getPost = async (slug: string): Promise<Post | null> => {
|
||||
const files = await getMdxFiles()
|
||||
const file = files.find((f) => f.replace(/\.(mdx|md)$/, '') === slug)
|
||||
if (!file) return null
|
||||
|
||||
const filePath = path.join(postsDirectory, file)
|
||||
const raw = await fs.readFile(filePath, 'utf8')
|
||||
const { data, content } = matter(raw)
|
||||
const components = useMDXComponents({})
|
||||
|
||||
const { content: compiledContent } = await compileMDX({
|
||||
source: content,
|
||||
components,
|
||||
options: { parseFrontmatter: true },
|
||||
})
|
||||
|
||||
return {
|
||||
slug,
|
||||
title: data.title ?? file,
|
||||
date: data.date ?? 'Unknown',
|
||||
excerpt: data.excerpt ?? '',
|
||||
source: compiledContent,
|
||||
} as unknown as Post
|
||||
}
|
||||
|
||||
export const getReadingTime = (content: string): number => {
|
||||
const words = content.replace(/<[^>]*>/g, '').trim().split(/\s+/).filter(Boolean).length
|
||||
return Math.max(1, Math.ceil(words / 200))
|
||||
}
|
||||
Reference in New Issue
Block a user