import yaml from "js-yaml"; export interface Pair { key: string; value: string; } export function parseNote(text: string): { frontmatter: Record; body: string } { const stripped = text.replace(/^/, ""); const m = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(stripped); if (!m) return { frontmatter: {}, body: stripped }; const loaded = yaml.load(m[1]); const frontmatter = loaded && typeof loaded === "object" ? (loaded as Record) : {}; return { frontmatter, body: stripped.slice(m[0].length) }; } /** * Convert a parsed note's frontmatter object into ordered, stringified pairs * suitable for display (read-only) and composition. */ export function docFrontmatterToPairs(docFrontmatter: Record): Pair[] { return Object.entries(docFrontmatter).map(([key, v]) => ({ key, value: stringifyScalar(v) })); } /** * Build the final frontmatter for the post. * * Editable rows (the preset fields seeded from settings plus any custom rows * the user added) are authoritative: on a key collision they win over the * document's own merged fields, which appear first (and are shown read-only in * the modal). Blank-key rows are dropped; duplicate editable keys keep the last * value at the first position. */ export function composeFrontmatter(docPairs: Pair[], editableRows: Pair[], mergeDoc: boolean): Pair[] { const editable = editableRows.filter((r) => r.key.trim() !== ""); const editableKeys = new Set(editable.map((r) => r.key)); const out: Pair[] = []; const idx = new Map(); const put = (key: string, value: string) => { const at = idx.get(key); if (at !== undefined) out[at] = { key, value }; else { idx.set(key, out.length); out.push({ key, value }); } }; if (mergeDoc) for (const p of docPairs) if (!editableKeys.has(p.key)) put(p.key, p.value); for (const r of editable) put(r.key, r.value); return out; } function stringifyScalar(v: unknown): string { if (v == null) return ""; if (v instanceof Date) return v.toISOString().slice(0, 10); if (typeof v === "object") return yaml.dump(v).trim(); return String(v); } export function serializeFrontmatter(pairs: Pair[]): string { const lines = pairs.map((p) => `${p.key}: ${formatValue(p.value)}`); return `---\n${lines.join("\n")}\n---\n`; } function formatValue(v: string): string { if (v === "") return '""'; const yamlSpecial = /^(true|false|null|yes|no|on|off|y|n|~)$/i.test(v); const isoDate = /^\d{4}-\d{2}-\d{2}$/.test(v); const numericLike = !isoDate && /^[+-]?(\d|\.\d)/.test(v); if (yamlSpecial || numericLike) return JSON.stringify(v); const bareSafe = /^[A-Za-z0-9_./-][A-Za-z0-9_./ -]*$/.test(v) && !/^\s|\s$/.test(v); return bareSafe ? v : JSON.stringify(v); }