126 lines
3.8 KiB
TypeScript
126 lines
3.8 KiB
TypeScript
|
|
import fs from 'fs';
|
|||
|
|
import path from 'path';
|
|||
|
|
import matter from 'gray-matter';
|
|||
|
|
import { Locale } from './i18n';
|
|||
|
|
|
|||
|
|
export interface NewsArticle {
|
|||
|
|
id: string;
|
|||
|
|
title: string;
|
|||
|
|
category: string;
|
|||
|
|
date: string;
|
|||
|
|
excerpt: string;
|
|||
|
|
author: string;
|
|||
|
|
readTime: string;
|
|||
|
|
tags: string[];
|
|||
|
|
featured: boolean;
|
|||
|
|
content: string;
|
|||
|
|
locale: Locale;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const DOCS_PATH = path.join(process.cwd(), 'docs', 'news');
|
|||
|
|
|
|||
|
|
export async function getNewsArticle(id: string, locale: Locale): Promise<NewsArticle | null> {
|
|||
|
|
try {
|
|||
|
|
const filePath = path.join(DOCS_PATH, locale, `${id}.md`);
|
|||
|
|
|
|||
|
|
if (!fs.existsSync(filePath)) {
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const fileContents = fs.readFileSync(filePath, 'utf8');
|
|||
|
|
const { data, content } = matter(fileContents);
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
id,
|
|||
|
|
title: data.title,
|
|||
|
|
category: data.category,
|
|||
|
|
date: data.date,
|
|||
|
|
excerpt: data.excerpt,
|
|||
|
|
author: data.author,
|
|||
|
|
readTime: data.readTime,
|
|||
|
|
tags: data.tags || [],
|
|||
|
|
featured: data.featured || false,
|
|||
|
|
content,
|
|||
|
|
locale,
|
|||
|
|
};
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error(`Error reading article ${id} for locale ${locale}:`, error);
|
|||
|
|
return null;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export async function getAllNewsArticles(locale: Locale): Promise<NewsArticle[]> {
|
|||
|
|
try {
|
|||
|
|
const articlesDir = path.join(DOCS_PATH, locale);
|
|||
|
|
|
|||
|
|
if (!fs.existsSync(articlesDir)) {
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const filenames = fs.readdirSync(articlesDir);
|
|||
|
|
const articles: NewsArticle[] = [];
|
|||
|
|
|
|||
|
|
for (const filename of filenames) {
|
|||
|
|
if (filename.endsWith('.md')) {
|
|||
|
|
const id = filename.replace(/\.md$/, '');
|
|||
|
|
const article = await getNewsArticle(id, locale);
|
|||
|
|
if (article) {
|
|||
|
|
articles.push(article);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Sort by date (newest first)
|
|||
|
|
// Handle Chinese date format (e.g., "2024年1月15日")
|
|||
|
|
const parseChineseDate = (dateStr: string): Date => {
|
|||
|
|
// Try to parse Chinese date format first
|
|||
|
|
const chineseDateMatch = dateStr.match(/(\d{4})年(\d{1,2})月(\d{1,2})日/);
|
|||
|
|
if (chineseDateMatch) {
|
|||
|
|
const [, year, month, day] = chineseDateMatch;
|
|||
|
|
return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));
|
|||
|
|
}
|
|||
|
|
// Fallback to regular Date parsing
|
|||
|
|
return new Date(dateStr);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return articles.sort((a, b) => {
|
|||
|
|
const dateA = parseChineseDate(a.date);
|
|||
|
|
const dateB = parseChineseDate(b.date);
|
|||
|
|
return dateB.getTime() - dateA.getTime();
|
|||
|
|
});
|
|||
|
|
} catch (error) {
|
|||
|
|
console.error(`Error reading articles for locale ${locale}:`, error);
|
|||
|
|
return [];
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export async function getAvailableLocalesForArticle(id: string): Promise<Locale[]> {
|
|||
|
|
const availableLocales: Locale[] = [];
|
|||
|
|
const locales: Locale[] = ['zh-CN', 'zh-TW', 'en'];
|
|||
|
|
|
|||
|
|
for (const locale of locales) {
|
|||
|
|
const filePath = path.join(DOCS_PATH, locale, `${id}.md`);
|
|||
|
|
if (fs.existsSync(filePath)) {
|
|||
|
|
availableLocales.push(locale);
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return availableLocales;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function checkArticleExists(id: string, locale: Locale): boolean {
|
|||
|
|
const filePath = path.join(DOCS_PATH, locale, `${id}.md`);
|
|||
|
|
return fs.existsSync(filePath);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 获取最新的新闻文章(用于首页显示)
|
|||
|
|
* @param locale 语言
|
|||
|
|
* @param limit 限制文章数量,默认为3
|
|||
|
|
* @returns 最新的新闻文章数组
|
|||
|
|
*/
|
|||
|
|
export async function getLatestNewsArticles(locale: Locale, limit: number = 3): Promise<NewsArticle[]> {
|
|||
|
|
const allArticles = await getAllNewsArticles(locale);
|
|||
|
|
return allArticles.slice(0, limit);
|
|||
|
|
}
|