Files
cmkiss/js/frontmatter.js
biosiasandClaude Sonnet 5 c3d8f0171c Fix blank line silently dropping YAML block arrays (AI)
A blank line between a block-array key and its "- item" lines matched
neither the item nor the key regex, resetting arrayKey to null and
causing the following items to be silently skipped. Blank lines are
now ignored instead of ending the array.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 16:02:38 +02:00

53 lines
2.4 KiB
JavaScript

// Small YAML-frontmatter reader for the canonical blog schema. It supports
// quoted/unquoted scalars, booleans, numbers and scalar arrays in inline or
// block form. The editor remains the authoritative validator and writer.
function parseScalar(raw) {
const value = raw.trim();
if (!value) return '';
if (value === 'true') return true;
if (value === 'false') return false;
if (value === 'null' || value === '~') return null;
if (/^-?\d+(?:\.\d+)?$/.test(value)) return Number(value);
if (value.startsWith('"') && value.endsWith('"')) {
try { return JSON.parse(value); } catch { return value.slice(1, -1); }
}
if (value.startsWith("'") && value.endsWith("'")) return value.slice(1, -1).replace(/''/g, "'");
if (value.startsWith('[') && value.endsWith(']')) {
const inside = value.slice(1, -1).trim();
if (!inside) return [];
const values = [];
let token = '';
let quote = '';
for (const character of inside) {
if ((character === '"' || character === "'") && (!quote || quote === character)) quote = quote ? '' : character;
if (character === ',' && !quote) { values.push(parseScalar(token)); token = ''; }
else token += character;
}
values.push(parseScalar(token));
return values;
}
return value;
}
export function parseFrontmatter(raw) {
// The closing "---" doesn't require a trailing newline of its own — a
// file that's nothing but frontmatter (no body, no trailing newline)
// still parses instead of falling through to "no frontmatter at all".
const match = raw.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n([\s\S]*))?$/);
if (!match) return { meta: {}, content: raw };
const meta = {};
let arrayKey = null;
for (const line of match[1].split(/\r?\n/)) {
if (!line.trim()) continue; // blank line — doesn't close a block array
const item = line.match(/^\s*-\s+(.*)$/);
if (item && arrayKey) { meta[arrayKey].push(parseScalar(item[1])); continue; }
const field = line.match(/^([A-Za-z][A-Za-z0-9_-]*):(?:\s*(.*))?$/);
if (!field) { arrayKey = null; continue; }
const [, key, rawValue = ''] = field;
if (!rawValue.trim()) { meta[key] = []; arrayKey = key; }
else { meta[key] = parseScalar(rawValue); arrayKey = null; }
}
return { meta, content: match[2] ?? '' };
}