import fs from 'fs'; import path from 'path'; import matter from 'gray-matter'; import { mapSlugToFileSlug } from './slugs'; export interface PostFrontmatter { title: string; date: string; excerpt?: string; featuredImage?: string | null; category?: string; locale: string; } export interface PostMdx { slug: string; frontmatter: PostFrontmatter; content: string; } export async function getPostBySlug(slug: string, locale: string): Promise { // Map translated slug to file slug const fileSlug = await mapSlugToFileSlug(slug, locale); const postsDir = path.join(process.cwd(), 'data', 'blog', locale); const filePath = path.join(postsDir, `${fileSlug}.mdx`); if (!fs.existsSync(filePath)) { return null; } const fileContent = fs.readFileSync(filePath, 'utf8'); const { data, content } = matter(fileContent); return { slug: fileSlug, frontmatter: data as PostFrontmatter, content, }; } export async function getAllPosts(locale: string): Promise { const postsDir = path.join(process.cwd(), 'data', 'blog', locale); if (!fs.existsSync(postsDir)) return []; const files = fs.readdirSync(postsDir); const posts = files .filter((file) => file.endsWith('.mdx')) .map((file) => { const filePath = path.join(postsDir, file); const fileContent = fs.readFileSync(filePath, 'utf8'); const { data, content } = matter(fileContent); return { slug: file.replace(/\.mdx$/, ''), frontmatter: data as PostFrontmatter, content, }; }) .sort( (a, b) => new Date(b.frontmatter.date).getTime() - new Date(a.frontmatter.date).getTime(), ); return posts; } export async function getAllPostsMetadata(locale: string): Promise[]> { const postsDir = path.join(process.cwd(), 'data', 'blog', locale); if (!fs.existsSync(postsDir)) return []; const files = fs.readdirSync(postsDir); return files .filter((file) => file.endsWith('.mdx')) .map((file) => { const filePath = path.join(postsDir, file); const fileContent = fs.readFileSync(filePath, 'utf8'); const { data } = matter(fileContent); return { slug: file.replace(/\.mdx$/, ''), frontmatter: data as PostFrontmatter, }; }) .sort( (a, b) => new Date(b.frontmatter.date as string).getTime() - new Date(a.frontmatter.date as string).getTime(), ); } export async function getAdjacentPosts( slug: string, locale: string, ): Promise<{ prev: PostMdx | null; next: PostMdx | null }> { const posts = await getAllPosts(locale); const currentIndex = posts.findIndex((post) => post.slug === slug); if (currentIndex === -1) { return { prev: null, next: null }; } // Posts are sorted by date descending (newest first) // So "next" post (newer) is at index - 1 // And "previous" post (older) is at index + 1 const next = currentIndex > 0 ? posts[currentIndex - 1] : null; const prev = currentIndex < posts.length - 1 ? posts[currentIndex + 1] : null; return { prev, next }; } export function getReadingTime(content: string): number { const wordsPerMinute = 200; const noOfWords = content.split(/\s/g).length; const minutes = noOfWords / wordsPerMinute; return Math.ceil(minutes); } export function getHeadings(content: string): { id: string; text: string; level: number }[] { const headingLines = content.split('\n').filter((line) => line.match(/^#{2,3}\s/)); return headingLines.map((line) => { const level = line.match(/^#+/)?.[0].length || 0; const text = line.replace(/^#+\s/, '').trim(); const id = text .toLowerCase() .replace(/[^\w\s-]/g, '') .replace(/\s+/g, '-'); return { id, text, level }; }); }