mirror of
				https://github.com/zadam/trilium.git
				synced 2025-10-31 10:26:08 +01:00 
			
		
		
		
	refactor(views/calendar): use specific API for date notes for performance
This commit is contained in:
		| @@ -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,27 +292,35 @@ export default class CalendarView extends ViewMode { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             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: typeof events[0] = { | ||||
|             const eventData: EventInput = { | ||||
|                 title: title, | ||||
|                 start: startDate, | ||||
|                 url: `#${note.noteId}`, | ||||
|                 noteId: note.noteId, | ||||
|                     color: color, | ||||
|                 color: color ?? undefined, | ||||
|                 iconClass: note.getLabelValue("iconClass") | ||||
|             }; | ||||
|  | ||||
|                 const endDate = CalendarView.#offsetDate(note.getAttributeValue("label", "endDate") ?? startDate, 1); | ||||
|                 if (endDate) { | ||||
|                     eventData.end = CalendarView.#formatDateToLocalISO(endDate); | ||||
|             const endDateOffset = CalendarView.#offsetDate(endDate ?? startDate, 1); | ||||
|             if (endDateOffset) { | ||||
|                 eventData.end = CalendarView.#formatDateToLocalISO(endDateOffset); | ||||
|             } | ||||
|  | ||||
|             events.push(eventData); | ||||
|         } | ||||
|         } | ||||
|  | ||||
|         return events.flat(); | ||||
|         return events; | ||||
|     } | ||||
|  | ||||
|     static async #parseCustomTitle(customTitleValue: string | null, note: FNote, allowRelations = true): Promise<string[]> { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user