Files
Mycontent/lib/book-file-system.ts
2025-12-29 14:01:37 +08:00

183 lines
5.4 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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")
}