92 lines
2.7 KiB
TypeScript
92 lines
2.7 KiB
TypeScript
|
|
import fs from 'fs';
|
||
|
|
import path from 'path';
|
||
|
|
import matter from 'gray-matter';
|
||
|
|
|
||
|
|
export interface ContentMetadata {
|
||
|
|
title: string;
|
||
|
|
description: string;
|
||
|
|
date: string;
|
||
|
|
author: string;
|
||
|
|
category: string;
|
||
|
|
tags: string[];
|
||
|
|
draft?: boolean;
|
||
|
|
featured?: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
export interface ContentItem {
|
||
|
|
slug: string;
|
||
|
|
metadata: ContentMetadata;
|
||
|
|
content: string;
|
||
|
|
}
|
||
|
|
|
||
|
|
const contentDirectory = path.join(process.cwd(), 'content');
|
||
|
|
|
||
|
|
export function getContentData(lang: string, slug: string): ContentItem {
|
||
|
|
const fullPath = path.join(contentDirectory, lang, `${slug}.md`);
|
||
|
|
|
||
|
|
if (!fs.existsSync(fullPath)) {
|
||
|
|
throw new Error(`Content not found: ${fullPath}`);
|
||
|
|
}
|
||
|
|
|
||
|
|
const fileContents = fs.readFileSync(fullPath, 'utf8');
|
||
|
|
const { data, content } = matter(fileContents);
|
||
|
|
|
||
|
|
return {
|
||
|
|
slug,
|
||
|
|
metadata: data as ContentMetadata,
|
||
|
|
content,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getAllContent(lang: string): ContentItem[] {
|
||
|
|
const langDirectory = path.join(contentDirectory, lang);
|
||
|
|
|
||
|
|
if (!fs.existsSync(langDirectory)) {
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
|
||
|
|
const files = fs.readdirSync(langDirectory);
|
||
|
|
|
||
|
|
return files
|
||
|
|
.filter((file) => file.endsWith('.md'))
|
||
|
|
.map((file) => {
|
||
|
|
const slug = file.replace('.md', '');
|
||
|
|
return getContentData(lang, slug);
|
||
|
|
})
|
||
|
|
.filter((item) => !item.metadata.draft)
|
||
|
|
.sort((a, b) => new Date(b.metadata.date).getTime() - new Date(a.metadata.date).getTime());
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getContentByCategory(lang: string, category: string): ContentItem[] {
|
||
|
|
const allContent = getAllContent(lang);
|
||
|
|
return allContent.filter((item) => item.metadata.category === category);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getContentByTag(lang: string, tag: string): ContentItem[] {
|
||
|
|
const allContent = getAllContent(lang);
|
||
|
|
return allContent.filter((item) => item.metadata.tags && item.metadata.tags.includes(tag));
|
||
|
|
}
|
||
|
|
|
||
|
|
export function searchContent(lang: string, query: string): ContentItem[] {
|
||
|
|
const allContent = getAllContent(lang);
|
||
|
|
const searchQuery = query.toLowerCase();
|
||
|
|
|
||
|
|
return allContent.filter((item) => {
|
||
|
|
const searchText =
|
||
|
|
`${item.metadata.title} ${item.metadata.description} ${item.content}`.toLowerCase();
|
||
|
|
return searchText.includes(searchQuery);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getAllCategories(lang: string): string[] {
|
||
|
|
const allContent = getAllContent(lang);
|
||
|
|
const categories = allContent.map((item) => item.metadata.category);
|
||
|
|
return [...new Set(categories)].filter(Boolean);
|
||
|
|
}
|
||
|
|
|
||
|
|
export function getAllTags(lang: string): string[] {
|
||
|
|
const allContent = getAllContent(lang);
|
||
|
|
const tags = allContent.flatMap((item) => item.metadata.tags || []);
|
||
|
|
return [...new Set(tags)].filter(Boolean);
|
||
|
|
}
|