// Minimal front matter for the local Markdown drafts storebound writes (review replies): "---", one `key: value` per // line (strings, numbers, true / false, null; strings JSON-quoted when needed), "---", then the body. Pure. export type FmValue = string | number | boolean | null; export function parseFrontMatter(text: string): { data: Record; body: string } { const m = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(text); if (!m) return { data: {}, body: text }; const data: Record = {}; for (const line of m[1]!.split(/\r?\n/)) { const kv = /^([A-Za-z_][\w-]*):\s*(.*)$/.exec(line); if (!kv) continue; data[kv[1]!] = scalar(kv[2]!.trim()); } return { data, body: text.slice(m[0].length) }; } function scalar(v: string): FmValue { if (v === 'true') return true; if (v === 'false') return false; if (v === 'null' || v === '~' || v === '') return null; if (/^-?\d+(\.\d+)?$/.test(v)) return Number(v); if (v.startsWith('"')) { try { return JSON.parse(v) as string; } catch { return v; } } if (v.startsWith("'") && v.endsWith("'")) return v.slice(1, -1).replace(/''/g, "'"); return v; } const plain = (s: string) => /^[\w./@+-][\w ./@:+-]*$/.test(s) && !/^(true|false|null|~|-?\d+(\.\d+)?)$/.test(s) && !/ $/.test(s); export function stringifyFrontMatter(data: Record, body: string): string { const lines = Object.entries(data).filter(([, v]) => v !== undefined) .map(([k, v]) => `${k}: ${v === null ? 'null' : typeof v === 'string' ? (plain(v) ? v : JSON.stringify(v)) : String(v)}`); return `---\n${lines.join('\n')}\n---\n${body.startsWith('\n') ? body : '\n' + body}`; }