Update remote soul-content with local content
This commit is contained in:
169
lib/book-file-system.ts
Normal file
169
lib/book-file-system.ts
Normal file
@@ -0,0 +1,169 @@
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
import type { 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 {
|
||||
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 {
|
||||
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 {
|
||||
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],
|
||||
}
|
||||
}
|
||||
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,
|
||||
isFree: parsedSection.id.endsWith(".1"),
|
||||
filePath: path.relative(process.cwd(), path.join(chapterPath, fileName)),
|
||||
}
|
||||
})
|
||||
.filter((s): s is Section => s !== null)
|
||||
.sort((a, b) => {
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user