92 lines
2.8 KiB
TypeScript
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,
|
|
};
|
|
},
|
|
};
|