import fs from "fs" import path from "path" import { Part, Chapter, Section } from "./book-data" const BOOK_DIR = path.join(process.cwd(), "book") const CHINESE_NUM_MAP: Record = { 一: "01", 二: "02", 三: "03", 四: "04", 五: "05", 六: "06", 七: "07", 八: "08", 九: "09", 十: "10", } const SUBTITLES: Record = { "真实的人": "人性观察与社交逻辑", "真实的行业": "社会运作的底层规则", "真实的错误": "错过机会比失败更贵", "真实的赚钱": "所有行业的杠杆结构", "真实的社会": "人与系统的关系", } function parsePartFolderName(folderName: string): { number: string; title: string } | null { // Example: _第一篇|真实的人 const match = folderName.match(/_第([一二三四五六七八九十]+)篇|(.+)/) if (match) { return { number: CHINESE_NUM_MAP[match[1]] || match[1], title: match[2], } } return null } function parseChapterFolderName(folderName: string): { id: string; title: string } | null { // Example: 第1章|人与人之间的底层逻辑 const match = folderName.match(/第(\d+)章|(.+)/) if (match) { return { id: match[1], title: match[2], } } return null } function parseSectionFileName(fileName: string): { id: string; title: string } | null { // Example: 1.1 自行车荷总:一个行业做到极致是什么样.md if (!fileName.endsWith(".md")) return null const name = fileName.replace(".md", "") const match = name.match(/^([\d.]+)\s+(.+)/) if (match) { return { id: match[1], title: match[2], } } // Fallback for files without number prefix if any (though project seems to use them) return { id: fileName, title: name, } } export function getBookStructure(): Part[] { if (!fs.existsSync(BOOK_DIR)) { return [] } const partFolders = fs.readdirSync(BOOK_DIR).filter((f) => { const fullPath = path.join(BOOK_DIR, f) return fs.statSync(fullPath).isDirectory() && f.startsWith("_") }) const parts: Part[] = partFolders .map((folderName) => { const parsed = parsePartFolderName(folderName) if (!parsed) return null const partPath = path.join(BOOK_DIR, folderName) const chapterFolders = fs.readdirSync(partPath).filter((f) => { const fullPath = path.join(partPath, f) return fs.statSync(fullPath).isDirectory() && f.startsWith("第") }) const chapters: Chapter[] = chapterFolders .map((chapterFolderName) => { const parsedChapter = parseChapterFolderName(chapterFolderName) if (!parsedChapter) return null const chapterPath = path.join(partPath, chapterFolderName) const sectionFiles = fs.readdirSync(chapterPath).filter((f) => f.endsWith(".md")) const sections: Section[] = sectionFiles .map((fileName) => { const parsedSection = parseSectionFileName(fileName) if (!parsedSection) return null return { id: parsedSection.id, title: parsedSection.title, price: 1, // Default price isFree: parsedSection.id.endsWith(".1"), // Assuming first section of chapter is free logic for now, or use logic from requirement filePath: path.relative(process.cwd(), path.join(chapterPath, fileName)), } }) .filter((s): s is Section => s !== null) .sort((a, b) => { // Numeric sort for section IDs (1.1, 1.2, 1.10) const partsA = a.id.split('.').map(Number); const partsB = b.id.split('.').map(Number); for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) { const valA = partsA[i] || 0; const valB = partsB[i] || 0; if (valA !== valB) return valA - valB; } return 0; }) return { id: parsedChapter.id, title: parsedChapter.title, sections, } }) .filter((c): c is Chapter => c !== null) .sort((a, b) => Number(a.id) - Number(b.id)) return { id: parsed.number, number: parsed.number, title: parsed.title, subtitle: SUBTITLES[parsed.title] || "", chapters, } }) .filter((p): p is Part => p !== null) .sort((a, b) => Number(a.number) - Number(b.number)) return parts } export function getSectionBySlug(slug: string): Section | null { const parts = getBookStructure() for (const part of parts) { for (const chapter of part.chapters) { for (const section of chapter.sections) { if (section.id === slug) { return section } } } } return null } export function getChapterBySectionSlug(slug: string): { part: Part; chapter: Chapter } | null { const parts = getBookStructure() for (const part of parts) { for (const chapter of part.chapters) { for (const section of chapter.sections) { if (section.id === slug) { return { part, chapter } } } } } return null } export function getSectionContent(filePath: string): string { const fullPath = path.join(process.cwd(), filePath) if (!fs.existsSync(fullPath)) { return "" } return fs.readFileSync(fullPath, "utf-8") }