/** * rNotes → markwhen projection. * * Two strategies, applied in order: * 1. **Daily notes**: paths matching `YYYY-MM-DD` (anywhere in the path) * become all-day events at that date. Title = note title. * 2. **Frontmatter dates**: any note with `frontmatter.date` (ISO) or * `frontmatter.created` / `frontmatter.event_date` becomes a point * event at that moment. * * Notes without extractable dates are silently skipped — this is a view * layer, not a completeness contract. */ import type { MarkwhenSourceFactory, MwEvent, MwSource } from '../types'; import type { VaultDoc, VaultNoteMeta } from '../../../modules/rnotes/schemas'; import { vaultDocId } from '../../../modules/rnotes/schemas'; import { loadDoc, listDocIds } from '../doc-loader'; const DAILY_RE = /(\d{4}-\d{2}-\d{2})(?!\d)/; function extractDate(note: VaultNoteMeta): number | null { // Strategy 1: path-embedded YYYY-MM-DD const m = note.path.match(DAILY_RE); if (m) { const d = new Date(m[1] + 'T00:00:00Z'); if (!isNaN(d.getTime())) return d.getTime(); } // Strategy 2: frontmatter const fm = note.frontmatter ?? {}; for (const key of ['date', 'created', 'event_date', 'published']) { const v = fm[key]; if (typeof v === 'string' || typeof v === 'number') { const d = new Date(v); if (!isNaN(d.getTime())) return d.getTime(); } } return null; } function noteToMw(vaultId: string, note: VaultNoteMeta): MwEvent | null { const ts = extractDate(note); if (ts === null) return null; const tags = note.tags?.length ? [...note.tags] : undefined; return { start: ts, title: note.title || note.path, description: undefined, // content is on-demand from ZIP — don't inline tags, href: `rspace://rnotes/${vaultId}/${encodeURIComponent(note.path)}`, sourceId: `rnotes:${vaultId}:${note.path}`, }; } export const rnotesMarkwhenSource: MarkwhenSourceFactory = { module: 'rnotes', label: 'Notes', icon: '📓', async build({ space, from, to }): Promise { // Each vault is its own doc — fan out over all vaults in the space. const prefix = `${space}:rnotes:vaults:`; const docIds = await listDocIds(prefix); if (docIds.length === 0) return null; const events: MwEvent[] = []; for (const docId of docIds) { const vaultId = docId.slice(prefix.length); const doc = await loadDoc(vaultDocId(space, vaultId)); if (!doc) continue; for (const note of Object.values(doc.notes)) { const mw = noteToMw(vaultId, note); if (!mw) continue; if (from !== undefined && mw.start < from) continue; if (to !== undefined && mw.start > to) continue; events.push(mw); } } if (events.length === 0) return null; return { id: 'rnotes', label: 'Notes', tag: 'rnotes', color: 'green', events, }; }, };