chore(react/collections/calendar): render calendar events

This commit is contained in:
Elian Doran
2025-09-05 17:59:51 +03:00
parent 5bb9117fde
commit b93d9a6b6e
5 changed files with 73 additions and 81 deletions

View File

@@ -47,27 +47,6 @@ function parseDate(str: string) {
}
}
// Source: https://stackoverflow.com/a/30465299/4898894
function getMonthsInDateRange(startDate: string, endDate: string) {
const start = startDate.split("-");
const end = endDate.split("-");
const startYear = parseInt(start[0]);
const endYear = parseInt(end[0]);
const dates: string[] = [];
for (let i = startYear; i <= endYear; i++) {
const endMonth = i != endYear ? 11 : parseInt(end[1]) - 1;
const startMon = i === startYear ? parseInt(start[1]) - 1 : 0;
for (let j = startMon; j <= endMonth; j = j > 12 ? j % 12 || 11 : j + 1) {
const month = j + 1;
const displayMonth = month < 10 ? "0" + month : month;
dates.push([i, displayMonth].join("-"));
}
}
return dates;
}
function padNum(num: number) {
return `${num <= 9 ? "0" : ""}${num}`;
}
@@ -835,7 +814,6 @@ export default {
restartDesktopApp,
reloadTray,
parseDate,
getMonthsInDateRange,
formatDateISO,
formatDateTime,
formatTimeInterval,

View File

@@ -1,7 +1,8 @@
import { EventInput, EventSourceInput } from "@fullcalendar/core/index.js";
import { EventInput, EventSourceFuncArg, EventSourceInput } from "@fullcalendar/core/index.js";
import froca from "../../../services/froca";
import { formatDateToLocalISO, getCustomisableLabel, offsetDate } from "./utils";
import { formatDateToLocalISO, getCustomisableLabel, getMonthsInDateRange, offsetDate } from "./utils";
import FNote from "../../../entities/fnote";
import server from "../../../services/server";
interface Event {
startDate: string,
@@ -30,6 +31,50 @@ export async function buildEvents(noteIds: string[]) {
return events.flat();
}
export async function buildEventsForCalendar(note: FNote, e: EventSourceFuncArg) {
const events: EventInput[] = [];
// Gather all the required date note IDs.
const dateRange = getMonthsInDateRange(e.startStr, e.endStr);
let allDateNoteIds: string[] = [];
for (const month of dateRange) {
// TODO: Deduplicate get type.
const dateNotesForMonth = await server.get<Record<string, string>>(`special-notes/notes-for-month/${month}?calendarRoot=${note.noteId}`);
const dateNoteIds = Object.values(dateNotesForMonth);
allDateNoteIds = [...allDateNoteIds, ...dateNoteIds];
}
// Request all the date notes.
const dateNotes = await froca.getNotes(allDateNoteIds);
const childNoteToDateMapping: Record<string, string> = {};
for (const dateNote of dateNotes) {
const startDate = dateNote.getLabelValue("dateNote");
if (!startDate) {
continue;
}
events.push(await buildEvent(dateNote, { startDate }));
if (dateNote.hasChildren()) {
const childNoteIds = await dateNote.getSubtreeNoteIds();
for (const childNoteId of childNoteIds) {
childNoteToDateMapping[childNoteId] = startDate;
}
}
}
// Request all child notes of date notes in a single run.
const childNoteIds = Object.keys(childNoteToDateMapping);
const childNotes = await froca.getNotes(childNoteIds);
for (const childNote of childNotes) {
const startDate = childNoteToDateMapping[childNote.noteId];
const event = await buildEvent(childNote, { startDate });
events.push(event);
}
return events.flat();
}
async function buildEvent(note: FNote, { startDate, endDate, startTime, endTime }: Event) {
const customTitleAttributeName = note.getLabelValue("calendar:title");
const titles = await parseCustomTitle(customTitleAttributeName, note);

View File

@@ -1,4 +1,4 @@
import { DateSelectArg, LocaleInput, PluginDef } from "@fullcalendar/core/index.js";
import { DateSelectArg, EventSourceFuncArg, LocaleInput, PluginDef } from "@fullcalendar/core/index.js";
import { ViewModeProps } from "../interface";
import Calendar from "./calendar";
import { useCallback, useEffect, useMemo, useRef, useState } from "preact/hooks";
@@ -12,7 +12,7 @@ import server from "../../../services/server";
import { parseStartEndDateFromEvent, parseStartEndTimeFromEvent } from "./utils";
import dialog from "../../../services/dialog";
import { t } from "../../../services/i18n";
import { buildEvents } from "./event_builder";
import { buildEvents, buildEventsForCalendar } from "./event_builder";
interface CalendarViewData {
@@ -58,6 +58,8 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
const eventBuilder = useMemo(() => {
if (!isCalendarRoot) {
return async () => await buildEvents(noteIds);
} else {
return async (e: EventSourceFuncArg) => await buildEventsForCalendar(note, e);
}
}, [isCalendarRoot, noteIds]);

View File

@@ -1,5 +1,6 @@
import { DateSelectArg } from "@fullcalendar/core/index.js";
import { EventImpl } from "@fullcalendar/core/internal";
import FNote from "../../../entities/fnote";
export function parseStartEndDateFromEvent(e: DateSelectArg | EventImpl) {
const startDate = formatDateToLocalISO(e.start);
@@ -79,3 +80,24 @@ export function getCustomisableLabel(note: FNote, defaultLabelName: string, cust
return note.getLabelValue(defaultLabelName);
}
// Source: https://stackoverflow.com/a/30465299/4898894
export function getMonthsInDateRange(startDate: string, endDate: string) {
const start = startDate.split("-");
const end = endDate.split("-");
const startYear = parseInt(start[0]);
const endYear = parseInt(end[0]);
const dates: string[] = [];
for (let i = startYear; i <= endYear; i++) {
const endMonth = i != endYear ? 11 : parseInt(end[1]) - 1;
const startMon = i === startYear ? parseInt(start[1]) - 1 : 0;
for (let j = startMon; j <= endMonth; j = j > 12 ? j % 12 || 11 : j + 1) {
const month = j + 1;
const displayMonth = month < 10 ? "0" + month : month;
dates.push([i, displayMonth].join("-"));
}
}
return dates;
}

View File

@@ -30,22 +30,11 @@ export default class CalendarView extends ViewMode<{}> {
this.$root = $(TPL);
this.$calendarContainer = this.$root.find(".calendar-container");
this.isCalendarRoot = false;
args.$parent.append(this.$root);
}
async renderList(): Promise<JQuery<HTMLElement> | undefined> {
const { Calendar } = await import("@fullcalendar/core");
let eventBuilder: EventSourceFunc;
if (!this.isCalendarRoot) {
eventBuilder =
} else {
eventBuilder = async (e: EventSourceFuncArg) => await this.#buildEventsForCalendar(e);
}
const calendar = new Calendar(this.$calendarContainer[0], {
events: eventBuilder,
select: (e) => this.#onCalendarSelection(e),
eventChange: (e) => this.#onEventMoved(e),
height: "100%",
@@ -183,50 +172,6 @@ export default class CalendarView extends ViewMode<{}> {
}
}
async #buildEventsForCalendar(e: EventSourceFuncArg) {
const events: EventInput[] = [];
// Gather all the required date note IDs.
const dateRange = utils.getMonthsInDateRange(e.startStr, e.endStr);
let allDateNoteIds: string[] = [];
for (const month of dateRange) {
// TODO: Deduplicate get type.
const dateNotesForMonth = await server.get<Record<string, string>>(`special-notes/notes-for-month/${month}?calendarRoot=${this.parentNote.noteId}`);
const dateNoteIds = Object.values(dateNotesForMonth);
allDateNoteIds = [...allDateNoteIds, ...dateNoteIds];
}
// Request all the date notes.
const dateNotes = await froca.getNotes(allDateNoteIds);
const childNoteToDateMapping: Record<string, string> = {};
for (const dateNote of dateNotes) {
const startDate = dateNote.getLabelValue("dateNote");
if (!startDate) {
continue;
}
events.push(await CalendarView.buildEvent(dateNote, { startDate }));
if (dateNote.hasChildren()) {
const childNoteIds = await dateNote.getSubtreeNoteIds();
for (const childNoteId of childNoteIds) {
childNoteToDateMapping[childNoteId] = startDate;
}
}
}
// Request all child notes of date notes in a single run.
const childNoteIds = Object.keys(childNoteToDateMapping);
const childNotes = await froca.getNotes(childNoteIds);
for (const childNote of childNotes) {
const startDate = childNoteToDateMapping[childNote.noteId];
const event = await CalendarView.buildEvent(childNote, { startDate });
events.push(event);
}
return events.flat();
}
buildTouchBarCommand({ TouchBar, buildIcon }: CommandListenerData<"buildTouchBar">) {
if (!this.calendar) {
return;