Files
MyNote/apps/desktop/src/main/safe/frontmatter-codec.ts
T

71 lines
2.7 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.
// FrontmatterCodec — 文件头 frontmatter 解析/序列化(FR-M1/M2)
// 仅 4 个业务字段:title? / tags? / star? / links?;空数组与默认值不落盘
import type { DocMeta } from './doc.js';
const FENCE = '---';
// 解析:返回 { meta, body };无 frontmatter 时 meta={} body=原文
export function splitFrontmatter(raw: string): { meta: DocMeta; body: string } {
// 必须以 --- 开头(允许前导空白行后紧跟围栏)
const m = raw.match(/^\s*---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
if (!m) return { meta: {}, body: raw };
const yaml = m[1];
const body = m[2];
return { meta: parseYaml(yaml), body };
}
// 序列化:把 meta + body 合并为完整文件文本;默认值与空数组不写入
export function joinFrontmatter(meta: DocMeta, body: string): string {
const yaml = serializeYaml(meta);
if (!yaml) return body;
return `${FENCE}\n${yaml}${FENCE}\n${body}`;
}
// ---- 极简 YAML 子集解析(只支持本项目 4 字段的形态)----
function parseYaml(yaml: string): DocMeta {
const meta: DocMeta = {};
const lines = yaml.split(/\r?\n/);
for (const line of lines) {
if (!line.trim() || line.trim().startsWith('#')) continue;
const kv = line.match(/^([a-zA-Z]+):\s*(.*)$/);
if (!kv) continue;
const [, key, val] = kv;
const v = val.trim();
if (key === 'title') meta.title = unquote(v);
else if (key === 'star') meta.star = v === 'true';
else if (key === 'tags') meta.tags = parseList(v);
else if (key === 'links') meta.links = parseList(v);
}
return meta;
}
function parseList(v: string): string[] {
// 内联数组 [a, b] 或流式;本项目只用内联
const m = v.match(/^\[\s*(.*)\s*\]$/);
if (!m) return [];
const inner = m[1].trim();
if (!inner) return [];
return inner.split(',').map(s => unquote(s.trim())).filter(Boolean);
}
function unquote(s: string): string {
const m = s.match(/^['"](.*)['"]$/);
return m ? m[1] : s;
}
// ---- 序列化:剔除默认值与空数组 ----
function serializeYaml(meta: DocMeta): string {
const lines: string[] = [];
if (meta.title !== undefined && meta.title !== '') lines.push(`title: ${quoteIfNeeded(meta.title)}`);
if (meta.tags && meta.tags.length > 0) lines.push(`tags: [${meta.tags.map(quoteIfNeeded).join(', ')}]`);
if (meta.star === true) lines.push('star: true');
if (meta.links && meta.links.length > 0) lines.push(`links: [${meta.links.map(quoteIfNeeded).join(', ')}]`);
return lines.length ? lines.join('\n') + '\n' : '';
}
function quoteIfNeeded(s: string): string {
// 含特殊字符(冒号/井号/方括号/引号/前后空格)则加引号
if (/[:#\[\]"']|^\s|\s$/.test(s)) return JSON.stringify(s);
return s;
}