diff --git a/src/lib/hugoParser.ts b/src/lib/hugoParser.ts new file mode 100644 index 0000000..0ccaba0 --- /dev/null +++ b/src/lib/hugoParser.ts @@ -0,0 +1,125 @@ +import { parse as parseToml } from 'smol-toml'; +import { parse as parseYaml } from 'yaml'; + +export interface HugoPostData { + title: string; + slug: string; + content: string; + cover: string; + published_at: string; + updated_at: string; + categories: string[]; + tags: string[]; +} + +/** + * 解析并转换时间,一旦解析失败或空缺,严格使用当前系统时间 + */ +function parseOrCurrentDate(val: unknown): string { + if (!val) return new Date().toISOString(); + try { + const dateObj = new Date(val as string | number | Date); + if (isNaN(dateObj.getTime())) { + return new Date().toISOString(); + } + return dateObj.toISOString(); + } catch { + return new Date().toISOString(); + } +} + +/** + * 清洗分类和标签列表(同时支持数组与单字符格式) + */ +function parseArrayField(val: unknown): string[] { + if (Array.isArray(val)) { + return val.map((item) => String(item).trim()).filter(Boolean); + } + if (typeof val === 'string' && val.trim() !== '') { + return [val.trim()]; + } + return []; +} + +/** + * 去掉文件名结尾的格式拓展名,提供纯文件名基础 + */ +function stripExtension(filename: string): string { + return filename.replace(/\.(md|markdown|txt)$/i, '').trim(); +} + +/** + * Hugo Markdown 头部元数据及正文解析器 + * 同时原生兼容 TOML (+++) 与 YAML (---) 语法,自动过滤并丢弃本项目无端对应参数 + */ +export function parseHugoMarkdown(rawContent: string, fileName = ''): HugoPostData { + // 剔除可能存在的 UTF-8 BOM + const text = rawContent.replace(/^\uFEFF/, '').trim(); + const defaultName = fileName ? stripExtension(fileName) : '新文章'; + + let meta: Record = {}; + let bodyContent = text; + + try { + // 1. 优先检识 TOML (以 +++ 开头包络) + if (text.startsWith('+++')) { + const endIndex = text.indexOf('\n+++', 3); + if (endIndex !== -1) { + const frontmatter = text.substring(3, endIndex).trim(); + bodyContent = text.substring(endIndex + 4).trim(); + meta = parseToml(frontmatter) as Record; + } + } + // 2. 检识常规 YAML (以 --- 开头包络) + else if (text.startsWith('---')) { + const endIndex = text.indexOf('\n---', 3); + if (endIndex !== -1) { + const frontmatter = text.substring(3, endIndex).trim(); + bodyContent = text.substring(endIndex + 4).trim(); + meta = parseYaml(frontmatter) as Record; + } + } + } catch (err) { + console.error('解析 Markdown 头部参数时出现异常,直接回货底稿正文并对时差退回初始系统时刻:', err); + // 当解析受卡出错,空字典依然不阻挠对下方日期与稿费正文字面的顺利捕拾 + } + + // 取值并完成映射:除以下对应参数处,诸如 draft/twemoji/lightgallery 等全在空载转储里随水蒸丢! + const title = typeof meta.title === 'string' && meta.title.trim() !== '' ? meta.title.trim() : defaultName; + + let slug = typeof meta.slug === 'string' ? meta.slug.trim() : ''; + if (!slug) { + // 没有声明明确的 slug 时,由名目精简转化出基础链接字符串 + slug = defaultName + .toLowerCase() + .replace(/[^\w\u4e00-\u9fa5\s-]/g, '') + .replace(/\s+/g, '-'); + if (!slug) { + slug = 'post-' + Date.now(); + } + } + + // 特色图片字段识别(首挑 featuredImage,也兼备处理 cover/image) + const cover = typeof meta.featuredImage === 'string' + ? meta.featuredImage.trim() + : typeof meta.cover === 'string' + ? meta.cover.trim() + : typeof meta.image === 'string' + ? meta.image.trim() + : ''; + + // 时间字段转化:严格按需求约定“一旦转换错漏或是全无,采用目前时刻填满” + const published_at = parseOrCurrentDate(meta.date || meta.published_at); + const updated_at = parseOrCurrentDate(meta.lastmod || meta.updated_at || meta.date || meta.published_at); + + return { + title: title || '无名小贴', + slug, + content: bodyContent, + cover, + published_at, + updated_at, + categories: parseArrayField(meta.categories || meta.category), + tags: parseArrayField(meta.tags || meta.tag) + }; +}