web-font/backend/interface.ts
崮生(子虚) 19b14f58ea feat: 字体元数据三层架构 + 临时字体保留机制 + 列表页覆盖率排序
- font_meta.ts: cmap 解析覆盖率 + name 表提取字体信息(版权/作者/许可) + 人工配置(font-config.json)
- 三层缓存: 进程内存 → .meta.json 磁盘 → font-config.json stat mtime 热更新
- 临时字体保留机制: TEMP_RETENTION_HOURS(默认3h), 超时未使用自动删除
- 列表页: 覆盖率标签 + 按字符集覆盖率排序
- 详情页: 标签/开源链接/简介 + 字体信息面板(设计师/版权/许可)
- 首页: 红色警告提示切勿上传非商用字体 + 显示保留时限
- SSG noindex bug 修复
2026-07-30 21:19:09 +08:00

70 lines
1.7 KiB
TypeScript

export let stat: (path: string) => Promise<{
isFile: () => boolean;
size: number;
/** 最后修改时间戳(毫秒),用于文件变更检测 */
mtimeMs: number;
}>;
export let readFile: (path: string) => Promise<Uint8Array>;
export let writeFile: (path: string, data: Uint8Array) => Promise<void>;
export let readdir: (path: string) => Promise<{
isFile: () => boolean;
name: string;
}[]>;
export let mkdir: (path: string) => Promise<void>;
export let unlink: (path: string) => Promise<void>;
export const implInterface = (options: {
stat: typeof stat;
readFile: typeof readFile;
writeFile: typeof writeFile;
readdir: typeof readdir;
mkdir: typeof mkdir;
unlink: typeof unlink;
}) => {
stat = options.stat;
readFile = options.readFile;
writeFile = options.writeFile;
readdir = options.readdir;
mkdir = options.mkdir;
unlink = options.unlink;
};
export function path_join(...paths: string[]) {
const sep = "/";
function trimSlashes(p: string) {
return p.replace(/\/+$/, "").replace(/^\/+/, "");
}
/** 将路径按 / 分割并解析 . 和 .. 段 */
function normalizeSegments(segments: string[]) {
const resolved: string[] = [];
for (const seg of segments) {
if (seg === "..") {
resolved.pop();
} else if (seg !== "." && seg !== "") {
resolved.push(seg);
}
}
return resolved;
}
const isAbsolute = paths[0] && paths[0].startsWith(sep);
const segments = paths
.map((path) => trimSlashes(path))
.join(sep)
.split(sep);
const resolved = normalizeSegments(segments);
if (!resolved.length) return isAbsolute ? sep : ".";
const result = resolved.join(sep);
return isAbsolute ? sep + result : result;
}