rspace-online/shared/markwhen/sources/rnotes.ts

92 lines
2.8 KiB
TypeScript

/**
* 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<MwSource | null> {
// 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<VaultDoc>(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,
};
},
};