feat: add content system with MDX support, types, and meta files for 4 games

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
consultoria-as
2026-02-17 07:28:12 +00:00
parent d7fa2df2cb
commit d2d1809c24
6 changed files with 146 additions and 0 deletions

93
src/lib/content.ts Normal file
View File

@@ -0,0 +1,93 @@
import fs from "fs";
import path from "path";
import matter from "gray-matter";
import { GameMeta, Chapter, GameWithChapters } from "@/types";
const contentDir = path.join(process.cwd(), "content");
export function getAllGames(): GameMeta[] {
const gameDirs = fs.readdirSync(contentDir).filter((dir) => {
const fullPath = path.join(contentDir, dir);
return fs.statSync(fullPath).isDirectory();
});
return gameDirs.map((slug) => {
const metaPath = path.join(contentDir, slug, "meta.json");
const meta = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
return { slug, ...meta };
});
}
export function getGame(slug: string): GameWithChapters {
const metaPath = path.join(contentDir, slug, "meta.json");
const meta = JSON.parse(fs.readFileSync(metaPath, "utf-8"));
const gameDir = path.join(contentDir, slug);
const chapterFiles = fs
.readdirSync(gameDir)
.filter((f) => f.endsWith(".mdx"))
.sort();
const chapters = chapterFiles.map((file, index) => {
const filePath = path.join(gameDir, file);
const raw = fs.readFileSync(filePath, "utf-8");
const { data } = matter(raw);
const chapterSlug = file.replace(".mdx", "");
return {
slug: chapterSlug,
number: index + 1,
title: (data.title as string) || chapterSlug,
};
});
return { slug, ...meta, chapters };
}
export function getChapter(gameSlug: string, chapterSlug: string): Chapter {
const gameDir = path.join(contentDir, gameSlug);
const chapterFiles = fs
.readdirSync(gameDir)
.filter((f) => f.endsWith(".mdx"))
.sort();
const chapterIndex = chapterFiles.findIndex(
(f) => f.replace(".mdx", "") === chapterSlug
);
const filePath = path.join(gameDir, `${chapterSlug}.mdx`);
const raw = fs.readFileSync(filePath, "utf-8");
const { data, content } = matter(raw);
return {
slug: chapterSlug,
number: chapterIndex + 1,
title: (data.title as string) || chapterSlug,
content,
};
}
export function getAdjacentChapters(
gameSlug: string,
chapterSlug: string
): { prev: string | null; next: string | null } {
const gameDir = path.join(contentDir, gameSlug);
const chapterFiles = fs
.readdirSync(gameDir)
.filter((f) => f.endsWith(".mdx"))
.sort();
const currentIndex = chapterFiles.findIndex(
(f) => f.replace(".mdx", "") === chapterSlug
);
return {
prev:
currentIndex > 0
? chapterFiles[currentIndex - 1].replace(".mdx", "")
: null,
next:
currentIndex < chapterFiles.length - 1
? chapterFiles[currentIndex + 1].replace(".mdx", "")
: null,
};
}