Files
zhimingge/lib/content/zhouyi.ts
T
dekun a487b17165 Fix learn 404, coin animation, full regions, and AI key errors.
Use numeric /learn/{num} routes, register Tailwind coin animations with方孔铜钱 UI, expand to 34 provinces, and surface missing OPENAI_API_KEY clearly.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 21:32:20 +08:00

106 lines
2.8 KiB
TypeScript

import fs from "fs/promises";
import path from "path";
const DOCS_ROOT = path.join(process.cwd(), "content", "zhouyi", "docs");
const OTHER_ROOT = path.join(DOCS_ROOT, "other");
export type LearnVariant = "traditional" | "simplified";
function getVariantRoot(variant: LearnVariant): string {
return variant === "traditional" ? DOCS_ROOT : OTHER_ROOT;
}
export async function listGuaMarks(
variant: LearnVariant = "traditional",
): Promise<string[]> {
const dir = getVariantRoot(variant);
const entries = await fs.readdir(dir, { withFileTypes: true });
return entries
.filter((entry) => entry.isDirectory() && /^\d{2}\./.test(entry.name))
.map((entry) => entry.name)
.sort();
}
/** 序号 slug,如 "35" */
export function guaNumFromMark(guaMark: string): string {
return guaMark.split(".")[0];
}
/** 由序号解析目录名,如 "35" → "35.火地晋" */
export async function markFromNum(
num: string,
variant: LearnVariant = "traditional",
): Promise<string | null> {
const padded = num.padStart(2, "0").slice(-2);
const marks = await listGuaMarks(variant);
return marks.find((m) => m.startsWith(`${padded}.`)) ?? null;
}
export async function readGuaMarkdown(guaMark: string): Promise<string> {
const filePath = path.join(DOCS_ROOT, guaMark, "index.md");
return fs.readFile(filePath, "utf-8");
}
export async function readLearnMarkdown(
guaMark: string,
variant: LearnVariant = "traditional",
): Promise<string> {
const root = getVariantRoot(variant);
const filePath =
guaMark === "index"
? path.join(root, "index.md")
: path.join(root, guaMark, "index.md");
return fs.readFile(filePath, "utf-8");
}
export function stripFrontmatter(content: string): string {
if (!content.startsWith("---")) {
return content;
}
const end = content.indexOf("---", 3);
if (end === -1) {
return content;
}
return content.slice(end + 3).trimStart();
}
export function extractZhangMingRen(guaDetail: string): string | undefined {
return guaDetail
.match(/(\*\*台灣張銘仁[\s\S]*?)(?=周易第\d+卦)/)?.[1]
?.replaceAll("\n\n", "\n");
}
export function extractChangeDetails(
guaDetail: string,
guaChange: string,
guaTitle: string,
): string[] {
const changeList: string[] = [];
if (guaChange === "无变爻") {
return changeList;
}
guaChange
.split(":")[1]
.trim()
.split(",")
.forEach((change) => {
const detail = guaDetail
.match(`(\\*\\*${change}變卦[\\s\\S]*?)(?=${guaTitle}|$)`)?.[1]
?.replaceAll("\n\n", "\n");
if (detail) {
changeList.push(detail.trim());
}
});
return changeList;
}
export function getGuaNumber(guaMark: string): number {
return parseInt(guaMark.split(".")[0], 10);
}
export function getGuaName(guaMark: string): string {
return guaMark.split(".").slice(1).join(".");
}