From 84d35c1a370f5909325d5c25893dbe7554d4596e Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Fri, 5 Sep 2025 17:44:24 +0300 Subject: [PATCH] chore(react/collections/calendar): create event from selection --- .../widgets/collections/calendar/index.tsx | 58 ++++++++++- .../src/widgets/collections/calendar/utils.ts | 59 +++++++++++ .../src/widgets/view_widgets/calendar_view.ts | 98 ------------------- 3 files changed, 112 insertions(+), 103 deletions(-) create mode 100644 apps/client/src/widgets/collections/calendar/utils.ts diff --git a/apps/client/src/widgets/collections/calendar/index.tsx b/apps/client/src/widgets/collections/calendar/index.tsx index 1df397f88..7f15876d3 100644 --- a/apps/client/src/widgets/collections/calendar/index.tsx +++ b/apps/client/src/widgets/collections/calendar/index.tsx @@ -1,13 +1,17 @@ -import { LocaleInput, PluginDef } from "@fullcalendar/core/index.js"; +import { DateSelectArg, LocaleInput, PluginDef } from "@fullcalendar/core/index.js"; import { ViewModeProps } from "../interface"; import Calendar from "./calendar"; -import { useEffect, useRef, useState } from "preact/hooks"; +import { useCallback, useEffect, useRef, useState } from "preact/hooks"; import "./index.css"; import { useNoteLabel, useNoteLabelBoolean, useResizeObserver, useSpacedUpdate, useTriliumOption, useTriliumOptionInt } from "../../react/hooks"; -import { LOCALE_IDS } from "@triliumnext/commons"; +import { CreateChildrenResponse, LOCALE_IDS } from "@triliumnext/commons"; import { Calendar as FullCalendar } from "@fullcalendar/core"; import { setLabel } from "../../../services/attributes"; import { circle } from "leaflet"; +import server from "../../../services/server"; +import { parseStartEndDateFromEvent, parseStartEndTimeFromEvent } from "./utils"; +import dialog from "../../../services/dialog"; +import { t } from "../../../services/i18n"; interface CalendarViewData { @@ -38,8 +42,9 @@ const LOCALE_MAPPINGS: Record Promise<{ default: LocaleInput export default function CalendarView({ note, noteIds }: ViewModeProps) { const containerRef = useRef(null); const calendarRef = useRef(null); - const plugins = usePlugins(false, false); - const locale = useLocale(); + + const [ calendarRoot ] = useNoteLabelBoolean(note, "calendarRoot"); + const [ workspaceCalendarRoot ] = useNoteLabelBoolean(note, "workspaceCalendarRoot"); const [ firstDayOfWeek ] = useTriliumOptionInt("firstDayOfWeek"); const [ hideWeekends ] = useNoteLabelBoolean(note, "calendar:hideWeekends"); const [ weekNumbers ] = useNoteLabelBoolean(note, "calendar:weekNumbers"); @@ -47,6 +52,47 @@ export default function CalendarView({ note, noteIds }: ViewModeProps setCalendarView(initialView.current)); useResizeObserver(containerRef, () => calendarRef.current?.updateSize()); + const isCalendarRoot = (calendarRoot || workspaceCalendarRoot); + const isEditable = !isCalendarRoot; + + const plugins = usePlugins(isEditable, isCalendarRoot); + const locale = useLocale(); + + const onCalendarSelection = useCallback(async (e: DateSelectArg) => { + // Handle start and end date + const { startDate, endDate } = parseStartEndDateFromEvent(e); + if (!startDate) { + return; + } + + // Handle start and end time. + const { startTime, endTime } = parseStartEndTimeFromEvent(e); + + // Ask for the title + const title = await dialog.prompt({ message: t("relation_map.enter_title_of_new_note"), defaultValue: t("relation_map.default_new_note_title") }); + if (!title?.trim()) { + return; + } + + // Create the note. + const { note: eventNote } = await server.post(`notes/${note.noteId}/children?target=into`, { + title, + content: "", + type: "text" + }); + + // Set the attributes. + setLabel(eventNote.noteId, "startDate", startDate); + if (endDate) { + setLabel(eventNote.noteId, "endDate", endDate); + } + if (startTime) { + setLabel(eventNote.noteId, "startTime", startTime); + } + if (endTime) { + setLabel(eventNote.noteId, "endTime", endTime); + } + }, []); return (plugins &&
@@ -64,6 +110,8 @@ export default function CalendarView({ note, noteIds }: ViewModeProps { if (initialView.current !== view.type) { initialView.current = view.type; diff --git a/apps/client/src/widgets/collections/calendar/utils.ts b/apps/client/src/widgets/collections/calendar/utils.ts new file mode 100644 index 000000000..f8b75386b --- /dev/null +++ b/apps/client/src/widgets/collections/calendar/utils.ts @@ -0,0 +1,59 @@ +import { DateSelectArg } from "@fullcalendar/core/index.js"; +import { EventImpl } from "@fullcalendar/core/internal"; + +export function parseStartEndDateFromEvent(e: DateSelectArg | EventImpl) { + const startDate = formatDateToLocalISO(e.start); + if (!startDate) { + return { startDate: null, endDate: null }; + } + let endDate; + if (e.allDay) { + endDate = formatDateToLocalISO(offsetDate(e.end, -1)); + } else { + endDate = formatDateToLocalISO(e.end); + } + return { startDate, endDate }; +} + +export function parseStartEndTimeFromEvent(e: DateSelectArg | EventImpl) { + let startTime: string | undefined | null = null; + let endTime: string | undefined | null = null; + if (!e.allDay) { + startTime = formatTimeToLocalISO(e.start); + endTime = formatTimeToLocalISO(e.end); + } + + return { startTime, endTime }; +} + +export function formatDateToLocalISO(date: Date | null | undefined) { + if (!date) { + return undefined; + } + + const offset = date.getTimezoneOffset(); + const localDate = new Date(date.getTime() - offset * 60 * 1000); + return localDate.toISOString().split("T")[0]; +} + +export function offsetDate(date: Date | string | null | undefined, offset: number) { + if (!date) { + return undefined; + } + + const newDate = new Date(date); + newDate.setDate(newDate.getDate() + offset); + return newDate; +} + +export function formatTimeToLocalISO(date: Date | null | undefined) { + if (!date) { + return undefined; + } + + const offset = date.getTimezoneOffset(); + const localDate = new Date(date.getTime() - offset * 60 * 1000); + return localDate.toISOString() + .split("T")[1] + .substring(0, 5); +} diff --git a/apps/client/src/widgets/view_widgets/calendar_view.ts b/apps/client/src/widgets/view_widgets/calendar_view.ts index b091126d0..186c4b980 100644 --- a/apps/client/src/widgets/view_widgets/calendar_view.ts +++ b/apps/client/src/widgets/view_widgets/calendar_view.ts @@ -50,9 +50,6 @@ export default class CalendarView extends ViewMode<{}> { } async renderList(): Promise | undefined> { - this.isCalendarRoot = this.parentNote.hasLabel("calendarRoot") || this.parentNote.hasLabel("workspaceCalendarRoot"); - const isEditable = !this.isCalendarRoot; - const { Calendar } = await import("@fullcalendar/core"); let eventBuilder: EventSourceFunc; @@ -64,8 +61,6 @@ export default class CalendarView extends ViewMode<{}> { const calendar = new Calendar(this.$calendarContainer[0], { events: eventBuilder, - editable: isEditable, - selectable: isEditable, select: (e) => this.#onCalendarSelection(e), eventChange: (e) => this.#onEventMoved(e), height: "100%", @@ -143,67 +138,6 @@ export default class CalendarView extends ViewMode<{}> { } } - async #onCalendarSelection(e: DateSelectArg) { - // Handle start and end date - const { startDate, endDate } = this.#parseStartEndDateFromEvent(e); - if (!startDate) { - return; - } - - // Handle start and end time. - const { startTime, endTime } = this.#parseStartEndTimeFromEvent(e); - - // Ask for the title - const title = await dialogService.prompt({ message: t("relation_map.enter_title_of_new_note"), defaultValue: t("relation_map.default_new_note_title") }); - if (!title?.trim()) { - return; - } - - // Create the note. - const { note } = await server.post(`notes/${this.parentNote.noteId}/children?target=into`, { - title, - content: "", - type: "text" - }); - - // Set the attributes. - attributes.setLabel(note.noteId, "startDate", startDate); - if (endDate) { - attributes.setLabel(note.noteId, "endDate", endDate); - } - if (startTime) { - attributes.setLabel(note.noteId, "startTime", startTime); - } - if (endTime) { - attributes.setLabel(note.noteId, "endTime", endTime); - } - } - - #parseStartEndDateFromEvent(e: DateSelectArg | EventImpl) { - const startDate = CalendarView.#formatDateToLocalISO(e.start); - if (!startDate) { - return { startDate: null, endDate: null }; - } - let endDate; - if (e.allDay) { - endDate = CalendarView.#formatDateToLocalISO(CalendarView.#offsetDate(e.end, -1)); - } else { - endDate = CalendarView.#formatDateToLocalISO(e.end); - } - return { startDate, endDate }; - } - - #parseStartEndTimeFromEvent(e: DateSelectArg | EventImpl) { - let startTime: string | undefined | null = null; - let endTime: string | undefined | null = null; - if (!e.allDay) { - startTime = CalendarView.#formatTimeToLocalISO(e.start); - endTime = CalendarView.#formatTimeToLocalISO(e.end); - } - - return { startTime, endTime }; - } - async #onEventMoved(e: EventChangeArg) { // Handle start and end date let { startDate, endDate } = this.#parseStartEndDateFromEvent(e.event); @@ -431,38 +365,6 @@ export default class CalendarView extends ViewMode<{}> { return [note.title]; } - static #formatDateToLocalISO(date: Date | null | undefined) { - if (!date) { - return undefined; - } - - const offset = date.getTimezoneOffset(); - const localDate = new Date(date.getTime() - offset * 60 * 1000); - return localDate.toISOString().split("T")[0]; - } - - static #formatTimeToLocalISO(date: Date | null | undefined) { - if (!date) { - return undefined; - } - - const offset = date.getTimezoneOffset(); - const localDate = new Date(date.getTime() - offset * 60 * 1000); - return localDate.toISOString() - .split("T")[1] - .substring(0, 5); - } - - static #offsetDate(date: Date | string | null | undefined, offset: number) { - if (!date) { - return undefined; - } - - const newDate = new Date(date); - newDate.setDate(newDate.getDate() + offset); - return newDate; - } - buildTouchBarCommand({ TouchBar, buildIcon }: CommandListenerData<"buildTouchBar">) { if (!this.calendar) { return;