import { parseYaml } from 'obsidian' export const FRONTMATTER_KEY = 'pm-project' export const TASK_FRONTMATTER_KEY = 'pm-task' export function parseFrontmatter(content: string): { frontmatter: Record | null body: string } { if (!content.startsWith('---')) return { frontmatter: null, body: content } const end = content.indexOf('\n---', 4) if (end === -1) return { frontmatter: null, body: content } const raw = content.slice(4, end) const body = content.slice(end + 4).trim() try { return { frontmatter: parseYaml(raw) as Record, body } } catch { return { frontmatter: null, body: content } } } /** Without this the generated wiki-link lines would be duplicated on every save. */ export function stripAutoGeneratedContent(body: string): string { let result = body result = result.replace(/^Project: \[\[.*\]\]$/gm, '') result = result.replace(/^Parent: \[\[.*\]\]$/gm, '') result = result.replace(/\n## Subtasks[\s\S]*$/, '') return result.trim() } /** Handles strings, numbers, booleans, arrays, and objects. Nothing else. */ export function appendYaml(lines: string[], obj: Record, indent: number): void { const pad = ' '.repeat(indent) for (const [key, val] of Object.entries(obj)) { if (val === null || val === undefined) { lines.push(`${pad}${key}:`) } else if (typeof val === 'boolean') { lines.push(`${pad}${key}: ${val}`) } else if (typeof val === 'number') { lines.push(`${pad}${key}: ${val}`) } else if (typeof val === 'string') { const escaped = val.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n') lines.push(`${pad}${key}: "${escaped}"`) } else if (Array.isArray(val)) { if (val.length === 0) { lines.push(`${pad}${key}: []`) } else if (typeof val[0] === 'object') { lines.push(`${pad}${key}:`) for (const item of val) { const entries = Object.entries(item as Record) if (entries.length === 0) continue const [firstKey, firstVal] = entries[0] lines.push(`${pad} - ${firstKey}: ${JSON.stringify(firstVal)}`) for (const [k, v] of entries.slice(1)) { lines.push(`${pad} ${k}: ${JSON.stringify(v)}`) } } } else { const items = val.map((v) => JSON.stringify(v)).join(', ') lines.push(`${pad}${key}: [${items}]`) } } else if (typeof val === 'object') { const keys = Object.keys(val) if (keys.length === 0) { lines.push(`${pad}${key}: {}`) } else { lines.push(`${pad}${key}:`) appendYaml(lines, val as Record, indent + 1) } } } } export function isOldFormat(frontmatter: Record): boolean { return Array.isArray(frontmatter.tasks) && frontmatter.tasks.length > 0 && !Array.isArray(frontmatter.taskIds) }