mirror of
				https://github.com/TriliumNext/Notes.git
				synced 2025-10-31 13:01:31 +08:00 
			
		
		
		
	Merge branch 'develop' of https://github.com/TriliumNext/Notes into develop
This commit is contained in:
		
						commit
						a4a2e55415
					
				| @ -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, | ||||||
|  | |||||||
| @ -223,7 +223,9 @@ export default class EditableTextTypeWidget extends AbstractTextTypeWidget { | |||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 $classicToolbarWidget.empty(); |                 $classicToolbarWidget.empty(); | ||||||
|  |                 if ($classicToolbarWidget.length) { | ||||||
|                     $classicToolbarWidget[0].appendChild(editor.ui.view.toolbar.element); |                     $classicToolbarWidget[0].appendChild(editor.ui.view.toolbar.element); | ||||||
|  |                 } | ||||||
| 
 | 
 | ||||||
|                 if (utils.isMobile()) { |                 if (utils.isMobile()) { | ||||||
|                     $classicToolbarWidget.addClass("visible"); |                     $classicToolbarWidget.addClass("visible"); | ||||||
|  | |||||||
| @ -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"; | ||||||
| @ -10,6 +10,8 @@ import dialogService from "../../services/dialog.js"; | |||||||
| import attributes from "../../services/attributes.js"; | import attributes from "../../services/attributes.js"; | ||||||
| import type { EventData } from "../../components/app_context.js"; | import type { EventData } from "../../components/app_context.js"; | ||||||
| import utils from "../../services/utils.js"; | import utils from "../../services/utils.js"; | ||||||
|  | import date_notes from "../../services/date_notes.js"; | ||||||
|  | import appContext from "../../components/app_context.js"; | ||||||
| 
 | 
 | ||||||
| const TPL = ` | const TPL = ` | ||||||
| <div class="calendar-view"> | <div class="calendar-view"> | ||||||
| @ -67,6 +69,7 @@ export default class CalendarView extends ViewMode { | |||||||
|     private noteIds: string[]; |     private noteIds: string[]; | ||||||
|     private parentNote: FNote; |     private parentNote: FNote; | ||||||
|     private calendar?: Calendar; |     private calendar?: Calendar; | ||||||
|  |     private isCalendarRoot: boolean; | ||||||
| 
 | 
 | ||||||
|     constructor(args: ViewModeArgs) { |     constructor(args: ViewModeArgs) { | ||||||
|         super(args); |         super(args); | ||||||
| @ -75,7 +78,7 @@ export default class CalendarView extends ViewMode { | |||||||
|         this.$calendarContainer = this.$root.find(".calendar-container"); |         this.$calendarContainer = this.$root.find(".calendar-container"); | ||||||
|         this.noteIds = args.noteIds; |         this.noteIds = args.noteIds; | ||||||
|         this.parentNote = args.parentNote; |         this.parentNote = args.parentNote; | ||||||
|         console.log(args); |         this.isCalendarRoot = false; | ||||||
|         args.$parent.append(this.$root); |         args.$parent.append(this.$root); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -84,20 +87,27 @@ export default class CalendarView extends ViewMode { | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     async renderList(): Promise<JQuery<HTMLElement> | undefined> { |     async renderList(): Promise<JQuery<HTMLElement> | undefined> { | ||||||
|         const isEditable = true; |         this.isCalendarRoot = this.parentNote.hasLabel("calendarRoot") || this.parentNote.hasLabel("workspaceCalendarRoot"); | ||||||
|  |         const isEditable = !this.isCalendarRoot; | ||||||
| 
 | 
 | ||||||
|         const { Calendar } = await import("@fullcalendar/core"); |         const { Calendar } = await import("@fullcalendar/core"); | ||||||
|         const plugins: PluginDef[] = []; |         const plugins: PluginDef[] = []; | ||||||
|         plugins.push((await import("@fullcalendar/daygrid")).default); |         plugins.push((await import("@fullcalendar/daygrid")).default); | ||||||
| 
 |         if (isEditable || this.isCalendarRoot) { | ||||||
|         if (isEditable) { |  | ||||||
|             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 CalendarView.#buildEvents(this.noteIds), |             events: eventBuilder, | ||||||
|             editable: isEditable, |             editable: isEditable, | ||||||
|             selectable: isEditable, |             selectable: isEditable, | ||||||
|             select: (e) => this.#onCalendarSelection(e), |             select: (e) => this.#onCalendarSelection(e), | ||||||
| @ -117,7 +127,17 @@ export default class CalendarView extends ViewMode { | |||||||
| 
 | 
 | ||||||
|                 html += utils.escapeHtml(e.event.title); |                 html += utils.escapeHtml(e.event.title); | ||||||
|                 return { html }; |                 return { html }; | ||||||
|             }) |             }), | ||||||
|  |             dateClick: async (e) => { | ||||||
|  |                 if (!this.isCalendarRoot) { | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 const note = await date_notes.getDayNote(e.dateStr); | ||||||
|  |                 if (note) { | ||||||
|  |                     appContext.tabManager.getActiveContext().setNote(note.noteId); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|         }); |         }); | ||||||
|         calendar.render(); |         calendar.render(); | ||||||
|         this.calendar = calendar; |         this.calendar = calendar; | ||||||
| @ -210,39 +230,96 @@ export default class CalendarView extends ViewMode { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     static async #buildEvents(noteIds: 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) { | ||||||
|             const startDate = note.getAttributeValue("label", "startDate"); |             let startDate = note.getLabelValue("startDate"); | ||||||
|             const customTitle = note.getAttributeValue("label", "calendar:title"); | 
 | ||||||
|             const color = note.getAttributeValue("label", "calendar:color") ??  note.getAttributeValue("label", "color") ?? undefined; |             if (note.hasChildren()) { | ||||||
|  |                 const childrenEventData = await this.#buildEvents(note.getChildNoteIds()); | ||||||
|  |                 if (childrenEventData.length > 0) { | ||||||
|  |                     events.push(childrenEventData); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             if (!startDate) { |             if (!startDate) { | ||||||
|                 continue; |                 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 titles = await CalendarView.#parseCustomTitle(customTitle, note); | ||||||
|  |         const color = note.getLabelValue("calendar:color") ?? note.getLabelValue("color"); | ||||||
|  |         const events: EventInput[] = []; | ||||||
|         for (const title of titles) { |         for (const title of titles) { | ||||||
|                 const eventData: typeof events[0] = { |             const eventData: EventInput = { | ||||||
|                 title: title, |                 title: title, | ||||||
|                 start: startDate, |                 start: startDate, | ||||||
|                 url: `#${note.noteId}`, |                 url: `#${note.noteId}`, | ||||||
|                 noteId: note.noteId, |                 noteId: note.noteId, | ||||||
|                     color: color, |                 color: color ?? undefined, | ||||||
|                 iconClass: note.getLabelValue("iconClass") |                 iconClass: note.getLabelValue("iconClass") | ||||||
|             }; |             }; | ||||||
| 
 | 
 | ||||||
|                 const endDate = CalendarView.#offsetDate(note.getAttributeValue("label", "endDate") ?? startDate, 1); |             const endDateOffset = CalendarView.#offsetDate(endDate ?? startDate, 1); | ||||||
|                 if (endDate) { |             if (endDateOffset) { | ||||||
|                     eventData.end = CalendarView.#formatDateToLocalISO(endDate); |                 eventData.end = CalendarView.#formatDateToLocalISO(endDateOffset); | ||||||
|             } |             } | ||||||
| 
 |  | ||||||
|             events.push(eventData); |             events.push(eventData); | ||||||
|         } |         } | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return events; |         return events; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Adorian Doran
						Adorian Doran