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' import remarkMath from 'remark-math' import remarkGfm from 'remark-gfm' import rehypeKatex from 'rehype-katex' import rehypePrettyCode from 'rehype-pretty-code' 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 => { 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 => { 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, mdxOptions: { remarkPlugins: [remarkMath, remarkGfm], rehypePlugins: [ [rehypePrettyCode, { theme: 'github-dark' }], rehypeKatex, ], }, }, }) 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)) }