// 从 content/articles/*.md 读取文章列表和详情 import fs from 'node:fs' import path from 'node:path' import matter from 'gray-matter' import { marked } from 'marked' const DIR = path.join(process.cwd(), 'content', 'articles') function readAll() { if (!fs.existsSync(DIR)) return [] return fs.readdirSync(DIR) .filter((f) => f.endsWith('.md')) .map((fileName) => { const fullPath = path.join(DIR, fileName) const raw = fs.readFileSync(fullPath, 'utf8') const { data, content } = matter(raw) const slug = fileName.replace(/\.md$/, '') const dateMatch = fileName.match(/^(\d{4}-\d{2}-\d{2})-/) const date = dateMatch ? dateMatch[1] : '' return { slug, date, title: data.title || slug.replace(/^\d{4}-\d{2}-\d{2}-/, ''), excerpt: content.replace(/\s+/g, ' ').trim().slice(0, 150) + (content.length > 150 ? '...' : ''), } }) .sort((a, b) => (a.date < b.date ? 1 : -1)) } export function listArticles() { return readAll().map(({ slug, title, date, excerpt }) => ({ slug, title, date, excerpt })) } export function getArticle(slug) { // 防止路径穿越 if (slug.includes('/') || slug.includes('\\') || slug.includes('..')) return null const fullPath = path.join(DIR, slug + '.md') if (!fs.existsSync(fullPath)) return null const raw = fs.readFileSync(fullPath, 'utf8') const { data, content } = matter(raw) const dateMatch = slug.match(/^(\d{4}-\d{2}-\d{2})-/) const date = dateMatch ? dateMatch[1] : '' return { slug, date, title: data.title || slug.replace(/^\d{4}-\d{2}-\d{2}-/, ''), contentHtml: marked.parse(content), } }