From e71f0cb091cfea01a348e5b9181458e7f21cc8a4 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Sat, 15 Feb 2025 12:05:35 +0200 Subject: [PATCH] feat(calendar_view): draggable events --- package-lock.json | 10 +++ package.json | 1 + .../app/widgets/view_widgets/calendar_view.ts | 61 +++++++++++++++++-- 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6ecbf9a9b..123acb09e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@excalidraw/excalidraw": "0.17.6", "@fullcalendar/core": "6.1.15", "@fullcalendar/daygrid": "6.1.15", + "@fullcalendar/interaction": "6.1.15", "@highlightjs/cdn-assets": "11.11.1", "@joplin/turndown-plugin-gfm": "1.0.61", "@mermaid-js/layout-elk": "0.1.7", @@ -2155,6 +2156,15 @@ "@fullcalendar/core": "~6.1.15" } }, + "node_modules/@fullcalendar/interaction": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.15.tgz", + "integrity": "sha512-DOTSkofizM7QItjgu7W68TvKKvN9PSEEvDJceyMbQDvlXHa7pm/WAVtAc6xSDZ9xmB1QramYoWGLHkCYbTW1rQ==", + "license": "MIT", + "peerDependencies": { + "@fullcalendar/core": "~6.1.15" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", diff --git a/package.json b/package.json index d44202f15..ad05b0216 100644 --- a/package.json +++ b/package.json @@ -64,6 +64,7 @@ "@excalidraw/excalidraw": "0.17.6", "@fullcalendar/core": "6.1.15", "@fullcalendar/daygrid": "6.1.15", + "@fullcalendar/interaction": "6.1.15", "@highlightjs/cdn-assets": "11.11.1", "@joplin/turndown-plugin-gfm": "1.0.61", "@mermaid-js/layout-elk": "0.1.7", diff --git a/src/public/app/widgets/view_widgets/calendar_view.ts b/src/public/app/widgets/view_widgets/calendar_view.ts index f485f763c..31572e2f3 100644 --- a/src/public/app/widgets/view_widgets/calendar_view.ts +++ b/src/public/app/widgets/view_widgets/calendar_view.ts @@ -1,7 +1,9 @@ -import type { EventSourceInput } from "@fullcalendar/core"; +import type { 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"; +import server from "../../services/server.js"; +import ws from "../../services/ws.js"; const TPL = `
@@ -40,13 +42,47 @@ export default class CalendarView extends ViewMode { } async renderList(): Promise | undefined> { + const editable = true; + const { Calendar } = await import("@fullcalendar/core"); - const dayGridPlugin = (await import("@fullcalendar/daygrid")).default; + const plugins: PluginDef[] = []; + plugins.push((await import("@fullcalendar/daygrid")).default); + + if (editable) { + plugins.push((await import("@fullcalendar/interaction")).default); + } const calendar = new Calendar(this.$calendarContainer[0], { - plugins: [ dayGridPlugin ], + plugins, initialView: "dayGridMonth", - events: await CalendarView.#buildEvents(this.noteIds) + events: await CalendarView.#buildEvents(this.noteIds), + editable, + eventDragStop: async (e) => { + const startDate = e.event.start?.toISOString().substring(0, 10); + let endDate = e.event.end?.toISOString().substring(0, 10); + const noteId = e.event.extendedProps.noteId; + + // Fullcalendar end date is exclusive, not inclusive but we store it the other way around. + if (endDate) { + const endDateParsed = new Date(endDate); + endDateParsed.setDate(endDateParsed.getDate() - 1); + endDate = endDateParsed.toISOString().substring(0, 10); + } + + // Don't store the end date if it's empty. + if (endDate === startDate) { + endDate = undefined; + } + + // Update start date + const note = await froca.getNote(noteId); + if (!note) { + return; + } + + CalendarView.#setAttribute(note, "label", "startDate", startDate); + CalendarView.#setAttribute(note, "label", "endDate", endDate); + } }); calendar.render(); @@ -70,7 +106,8 @@ export default class CalendarView extends ViewMode { const eventData: typeof events[0] = { title: title, start: startDate, - url: `#${note.noteId}` + url: `#${note.noteId}`, + noteId: note.noteId }; const endDate = new Date(note.getAttributeValue("label", "endDate") ?? startDate); @@ -116,4 +153,18 @@ export default class CalendarView extends ViewMode { return [ note.title ]; } + static async #setAttribute(note: FNote, type: "label" | "relation", name: string, value: string | null | undefined) { + if (value) { + // Create or update the attribute. + await server.put(`notes/${note.noteId}/set-attribute`, { type, name, value }); + } else { + // Remove the attribute if it exists on the server but we don't define a value for it. + const attributeId = note.getAttribute(type, name); + if (attributeId) { + await server.remove(`notes/${note.noteId}/attributes/${attributeId}`); + } + } + await ws.waitForMaxKnownEntityChangeId(); + } + }