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) { | function padNum(num: number) { | ||||||
|     return `${num <= 9 ? "0" : ""}${num}`; |     return `${num <= 9 ? "0" : ""}${num}`; | ||||||
| } | } | ||||||
| @@ -621,6 +642,7 @@ export default { | |||||||
|     reloadFrontendApp, |     reloadFrontendApp, | ||||||
|     reloadTray, |     reloadTray, | ||||||
|     parseDate, |     parseDate, | ||||||
|  |     getMonthsInDateRange, | ||||||
|     formatDateISO, |     formatDateISO, | ||||||
|     formatDateTime, |     formatDateTime, | ||||||
|     formatTimeInterval, |     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 froca from "../../services/froca.js"; | ||||||
| import ViewMode, { type ViewModeArgs } from "./view_mode.js"; | import ViewMode, { type ViewModeArgs } from "./view_mode.js"; | ||||||
| import type FNote from "../../entities/fnote.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); |             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], { |         const calendar = new Calendar(this.$calendarContainer[0], { | ||||||
|             plugins, |             plugins, | ||||||
|             initialView: "dayGridMonth", |             initialView: "dayGridMonth", | ||||||
|             events: async () => await this.#buildEvents(this.noteIds), |             events: eventBuilder, | ||||||
|             editable: isEditable, |             editable: isEditable, | ||||||
|             selectable: isEditable, |             selectable: isEditable, | ||||||
|             select: (e) => this.#onCalendarSelection(e), |             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 notes = await froca.getNotes(noteIds); | ||||||
|         const events: EventSourceInput = []; |         const events: EventSourceInput = []; | ||||||
|  |  | ||||||
|         for (const note of notes) { |         for (const note of notes) { | ||||||
|             let startDate; |             let startDate = note.getLabelValue("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; |  | ||||||
|  |  | ||||||
|             if (note.hasChildren()) { |             if (note.hasChildren()) { | ||||||
|                 const dateNote = note.getLabelValue("dateNote"); |                 const childrenEventData = await this.#buildEvents(note.getChildNoteIds()); | ||||||
|                 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); |  | ||||||
|                 if (childrenEventData.length > 0) { |                 if (childrenEventData.length > 0) { | ||||||
|                     events.push(childrenEventData); |                     events.push(childrenEventData); | ||||||
|                 } |                 } | ||||||
| @@ -258,29 +292,37 @@ export default class CalendarView extends ViewMode { | |||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             const titles = await CalendarView.#parseCustomTitle(customTitle, note); |             const endDate = note.getAttributeValue("label", "endDate"); | ||||||
|             for (const title of titles) { |             events.push(await CalendarView.#buildEvent(note, startDate, endDate)); | ||||||
|                 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); |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         return events.flat(); |         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[]> { |     static async #parseCustomTitle(customTitleValue: string | null, note: FNote, allowRelations = true): Promise<string[]> { | ||||||
|         if (customTitleValue) { |         if (customTitleValue) { | ||||||
|             const attributeName = customTitleValue.substring(1); |             const attributeName = customTitleValue.substring(1); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user