mirror of
https://github.com/TriliumNext/Notes.git
synced 2025-09-27 23:41:31 +08:00
refactor(views/calendar): use specific API for date notes for performance
This commit is contained in:
parent
43f79ca813
commit
a9cebe312f
@ -30,6 +30,27 @@ 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 = [];
|
||||
|
||||
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}`;
|
||||
}
|
||||
@ -621,6 +642,7 @@ export default {
|
||||
reloadFrontendApp,
|
||||
reloadTray,
|
||||
parseDate,
|
||||
getMonthsInDateRange,
|
||||
formatDateISO,
|
||||
formatDateTime,
|
||||
formatTimeInterval,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import type { Calendar, DateSelectArg, EventChangeArg, EventDropArg, EventSourceInput, PluginDef } from "@fullcalendar/core";
|
||||
import type { Calendar, DateSelectArg, EventChangeArg, EventDropArg, EventInput, EventSourceFunc, EventSourceFuncArg, EventSourceInput, PluginDef } from "@fullcalendar/core";
|
||||
import froca from "../../services/froca.js";
|
||||
import ViewMode, { type ViewModeArgs } from "./view_mode.js";
|
||||
import type FNote from "../../entities/fnote.js";
|
||||
@ -97,10 +97,17 @@ export default class CalendarView extends ViewMode {
|
||||
plugins.push((await import("@fullcalendar/interaction")).default);
|
||||
}
|
||||
|
||||
let eventBuilder: EventSourceFunc;
|
||||
if (!this.isCalendarRoot) {
|
||||
eventBuilder = async () => await this.#buildEvents(this.noteIds)
|
||||
} else {
|
||||
eventBuilder = async (e: EventSourceFuncArg) => await this.#buildEventsForCalendar(e);
|
||||
}
|
||||
|
||||
const calendar = new Calendar(this.$calendarContainer[0], {
|
||||
plugins,
|
||||
initialView: "dayGridMonth",
|
||||
events: async () => await this.#buildEvents(this.noteIds),
|
||||
events: eventBuilder,
|
||||
editable: isEditable,
|
||||
selectable: isEditable,
|
||||
select: (e) => this.#onCalendarSelection(e),
|
||||
@ -223,32 +230,59 @@ export default class CalendarView extends ViewMode {
|
||||
}
|
||||
}
|
||||
|
||||
async #buildEvents(noteIds: string[], enforcedStartDate?: string) {
|
||||
async #buildEventsForCalendar(e: EventSourceFuncArg) {
|
||||
const events = [];
|
||||
|
||||
// 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}`);
|
||||
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 = dateNote.getChildNoteIds();
|
||||
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();
|
||||
}
|
||||
|
||||
async #buildEvents(noteIds: string[]) {
|
||||
const notes = await froca.getNotes(noteIds);
|
||||
const events: EventSourceInput = [];
|
||||
|
||||
for (const note of notes) {
|
||||
let startDate;
|
||||
|
||||
if (enforcedStartDate) {
|
||||
startDate = enforcedStartDate;
|
||||
} else if (!this.isCalendarRoot) {
|
||||
startDate = note.getLabelValue("startDate");
|
||||
} else {
|
||||
startDate = note.getLabelValue("dateNote");
|
||||
}
|
||||
const customTitle = note.getAttributeValue("label", "calendar:title");
|
||||
const color = note.getAttributeValue("label", "calendar:color") ?? note.getAttributeValue("label", "color") ?? undefined;
|
||||
let startDate = note.getLabelValue("startDate");
|
||||
|
||||
if (note.hasChildren()) {
|
||||
const dateNote = note.getLabelValue("dateNote");
|
||||
let enforcedStartDate = undefined;
|
||||
if (dateNote) {
|
||||
// This is a day note which can have children. Make sure the children are added to the calendar even if they themselves don't have it.
|
||||
enforcedStartDate = dateNote;
|
||||
}
|
||||
|
||||
const childrenEventData = await this.#buildEvents(note.getChildNoteIds(), enforcedStartDate);
|
||||
const childrenEventData = await this.#buildEvents(note.getChildNoteIds());
|
||||
if (childrenEventData.length > 0) {
|
||||
events.push(childrenEventData);
|
||||
}
|
||||
@ -258,29 +292,37 @@ export default class CalendarView extends ViewMode {
|
||||
continue;
|
||||
}
|
||||
|
||||
const titles = await CalendarView.#parseCustomTitle(customTitle, note);
|
||||
for (const title of titles) {
|
||||
const eventData: typeof events[0] = {
|
||||
title: title,
|
||||
start: startDate,
|
||||
url: `#${note.noteId}`,
|
||||
noteId: note.noteId,
|
||||
color: color,
|
||||
iconClass: note.getLabelValue("iconClass")
|
||||
};
|
||||
|
||||
const endDate = CalendarView.#offsetDate(note.getAttributeValue("label", "endDate") ?? startDate, 1);
|
||||
if (endDate) {
|
||||
eventData.end = CalendarView.#formatDateToLocalISO(endDate);
|
||||
}
|
||||
|
||||
events.push(eventData);
|
||||
}
|
||||
const endDate = note.getAttributeValue("label", "endDate");
|
||||
events.push(await CalendarView.#buildEvent(note, startDate, endDate));
|
||||
}
|
||||
|
||||
return events.flat();
|
||||
}
|
||||
|
||||
static async #buildEvent(note: FNote, startDate: string, endDate?: string | null) {
|
||||
const customTitle = note.getLabelValue("calendar:title");
|
||||
const titles = await CalendarView.#parseCustomTitle(customTitle, note);
|
||||
const color = note.getLabelValue("calendar:color") ?? note.getLabelValue("color");
|
||||
const events: EventInput[] = [];
|
||||
for (const title of titles) {
|
||||
const eventData: EventInput = {
|
||||
title: title,
|
||||
start: startDate,
|
||||
url: `#${note.noteId}`,
|
||||
noteId: note.noteId,
|
||||
color: color ?? undefined,
|
||||
iconClass: note.getLabelValue("iconClass")
|
||||
};
|
||||
|
||||
const endDateOffset = CalendarView.#offsetDate(endDate ?? startDate, 1);
|
||||
if (endDateOffset) {
|
||||
eventData.end = CalendarView.#formatDateToLocalISO(endDateOffset);
|
||||
}
|
||||
events.push(eventData);
|
||||
}
|
||||
return events;
|
||||
}
|
||||
|
||||
static async #parseCustomTitle(customTitleValue: string | null, note: FNote, allowRelations = true): Promise<string[]> {
|
||||
if (customTitleValue) {
|
||||
const attributeName = customTitleValue.substring(1);
|
||||
|
Loading…
x
Reference in New Issue
Block a user