/** * rCal → markwhen projection. * * Maps `CalendarEvent` records to `MwEvent`. Handles: * - all-day events (normalized to date-only via midnight-UTC) * - per-event timezone (passes through to MwEvent.timezone) * - virtual vs physical events (location shown in description) * - tags: existing event tags + booking status + source name * * NOT handled yet (future work, flagged in markwhen_integration spec): * - RRULE expansion — only stored master events are emitted. * For recurring, expand the next N instances via rrule.js first. */ import type { MarkwhenSourceFactory, MwEvent, MwSource } from '../types'; import type { CalendarDoc, CalendarEvent } from '../../../modules/rcal/schemas'; import { calendarDocId } from '../../../modules/rcal/schemas'; import { loadDoc } from '../doc-loader'; // thin wrapper around local-first storage const SECTION_LABEL = 'Calendar'; const SECTION_TAG = 'rcal'; function eventToMw(ev: CalendarEvent): MwEvent | null { if (!ev.startTime) return null; const start = ev.allDay ? Math.floor(ev.startTime / 86_400_000) * 86_400_000 : ev.startTime; const end = ev.endTime && ev.endTime > ev.startTime ? (ev.allDay ? Math.floor(ev.endTime / 86_400_000) * 86_400_000 : ev.endTime) : undefined; const tags = [...(ev.tags ?? [])]; if (ev.bookingStatus) tags.push(ev.bookingStatus); if (ev.sourceType && ev.sourceType !== 'local') tags.push(ev.sourceType); if (ev.isVirtual) tags.push('virtual'); const descParts: string[] = []; if (ev.description) descParts.push(ev.description.slice(0, 500)); if (ev.locationName) descParts.push(`📍 ${ev.locationName}`); else if (ev.locationBreadcrumb) descParts.push(`📍 ${ev.locationBreadcrumb}`); if (ev.isVirtual && ev.virtualUrl) descParts.push(`🔗 ${ev.virtualUrl}`); if (ev.attendeeCount) descParts.push(`👥 ${ev.attendeeCount} attendee(s)`); return { start, end, title: ev.title || '(untitled)', description: descParts.length > 0 ? descParts.join('\n') : undefined, tags: tags.length > 0 ? tags : undefined, timezone: ev.timezone ?? undefined, sourceId: `rcal:ev:${ev.id}`, }; } export const rcalMarkwhenSource: MarkwhenSourceFactory = { module: 'rcal', label: 'Calendar', icon: '📅', async build({ space, from, to }): Promise { const doc = await loadDoc(calendarDocId(space)); if (!doc) return null; const events: MwEvent[] = []; for (const ev of Object.values(doc.events)) { if (from !== undefined && ev.endTime && ev.endTime < from) continue; if (to !== undefined && ev.startTime && ev.startTime > to) continue; const mw = eventToMw(ev); if (mw) events.push(mw); } return { id: 'rcal', label: SECTION_LABEL, tag: SECTION_TAG, color: 'blue', events, }; }, };