Files
Mycontent/lib/book-file-system.ts

183 lines
5.4 KiB
TypeScript
Raw Normal View History

2025-12-29 14:01:37 +08:00
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<string, string> = {
: "01",
: "02",
: "03",
: "04",
: "05",
: "06",
: "07",
: "08",
: "09",
: "10",
}
const SUBTITLES: Record<string, string> = {
"真实的人": "人性观察与社交逻辑",
"真实的行业": "社会运作的底层规则",
"真实的错误": "错过机会比失败更贵",
"真实的赚钱": "所有行业的杠杆结构",
"真实的社会": "人与系统的关系",
}
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")
}