import { CalendarConfig, DataAdapter, GtdEvent, RawNote, RECURRING_TAG, } from "./types"; import resolveScopedFolderIds from "./folderScope"; import extractGtdBlock from "./gtdBlock"; import { resolveEventDate } from "./resolveDate"; export interface CollectResult { events: GtdEvent[]; warnings: string[]; scannedFolders: number; scannedNotes: number; } /** * Collect calendar events per SPEC.md: * - resolve folder scope from the calendar note's folder * - fetch notes/todos in scope * - apply the inclusion matrix (§3.2) * - resolve dates (§3.1) * - sort within the flat list (grouping happens at render time) * * The calendar note itself is always excluded. */ export default async function collectEvents( adapter: DataAdapter, calendarNoteId: string, calendarFolderId: string, config: CalendarConfig ): Promise { const warnings: string[] = []; const folders = await adapter.getFolders(); const folderIds = resolveScopedFolderIds( folders, calendarFolderId, config.scopeDepth ); let scannedNotes = 0; const events: GtdEvent[] = []; for (const folderId of folderIds) { const notes = await adapter.getNotesInFolder(folderId); for (const note of notes) { scannedNotes += 1; if (note.id === calendarNoteId) continue; const event = await noteToEvent(adapter, note, config, warnings); if (event) events.push(event); } } sortEvents(events, config); return { events, warnings, scannedFolders: folderIds.length, scannedNotes, }; } async function noteToEvent( adapter: DataAdapter, note: RawNote, config: CalendarConfig, warnings: string[] ): Promise { const mode = note.is_todo ? config.todos : config.notes; if (mode === "none") return null; const result = extractGtdBlock(note.body); if (mode === "gtd-only" && !result.found) return null; if (result.error) { warnings.push(`"${note.title}": gtd block problem — ${result.error}`); } const block = result.block; const date = resolveEventDate(note, block); if (block && block.date && !date) { warnings.push(`"${note.title}": unparseable date "${block.date}"`); } // Only to-dos can recur; skip the tag lookup for plain notes. let isRecurring = false; if (note.is_todo) { const tags = await adapter.getNoteTagTitles(note.id); isRecurring = tags.includes(RECURRING_TAG); } return { id: note.id, title: block && block.title ? block.title : note.title, date, isTodo: Boolean(note.is_todo), completed: Boolean(note.is_todo && note.todo_completed > 0), bgColour: block ? block.bgColour : null, fgColour: block ? block.fgColour : null, icon: block ? block.icon : null, text: block ? block.text : null, updatedTime: note.updated_time, isRecurring, }; } function sortEvents(events: GtdEvent[], config: CalendarConfig): void { const direction = config.sort === "desc" ? -1 : 1; events.sort((a, b) => { let comparison: number; if (config.sortType === "modified_date") { comparison = a.updatedTime - b.updatedTime; } else { comparison = a.title.localeCompare(b.title, undefined, { sensitivity: "base", }); } return comparison * direction; }); }