78 lines
2.7 KiB
TypeScript
78 lines
2.7 KiB
TypeScript
/**
|
|
* 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<MwSource | null> {
|
|
const doc = await loadDoc<CalendarDoc>(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,
|
|
};
|
|
},
|
|
};
|