mirror of
https://github.com/zadam/trilium.git
synced 2025-11-05 21:05:55 +01:00
chore(react/collections/calendar): create event from selection
This commit is contained in:
@@ -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 { ViewModeProps } from "../interface";
|
||||||
import Calendar from "./calendar";
|
import Calendar from "./calendar";
|
||||||
import { useEffect, useRef, useState } from "preact/hooks";
|
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
import { useNoteLabel, useNoteLabelBoolean, useResizeObserver, useSpacedUpdate, useTriliumOption, useTriliumOptionInt } from "../../react/hooks";
|
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 { Calendar as FullCalendar } from "@fullcalendar/core";
|
||||||
import { setLabel } from "../../../services/attributes";
|
import { setLabel } from "../../../services/attributes";
|
||||||
import { circle } from "leaflet";
|
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 {
|
interface CalendarViewData {
|
||||||
|
|
||||||
@@ -38,8 +42,9 @@ const LOCALE_MAPPINGS: Record<LOCALE_IDS, (() => Promise<{ default: LocaleInput
|
|||||||
export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarViewData>) {
|
export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarViewData>) {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const calendarRef = useRef<FullCalendar>(null);
|
const calendarRef = useRef<FullCalendar>(null);
|
||||||
const plugins = usePlugins(false, false);
|
|
||||||
const locale = useLocale();
|
const [ calendarRoot ] = useNoteLabelBoolean(note, "calendarRoot");
|
||||||
|
const [ workspaceCalendarRoot ] = useNoteLabelBoolean(note, "workspaceCalendarRoot");
|
||||||
const [ firstDayOfWeek ] = useTriliumOptionInt("firstDayOfWeek");
|
const [ firstDayOfWeek ] = useTriliumOptionInt("firstDayOfWeek");
|
||||||
const [ hideWeekends ] = useNoteLabelBoolean(note, "calendar:hideWeekends");
|
const [ hideWeekends ] = useNoteLabelBoolean(note, "calendar:hideWeekends");
|
||||||
const [ weekNumbers ] = useNoteLabelBoolean(note, "calendar:weekNumbers");
|
const [ weekNumbers ] = useNoteLabelBoolean(note, "calendar:weekNumbers");
|
||||||
@@ -47,6 +52,47 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
|
|||||||
const initialView = useRef(calendarView);
|
const initialView = useRef(calendarView);
|
||||||
const viewSpacedUpdate = useSpacedUpdate(() => setCalendarView(initialView.current));
|
const viewSpacedUpdate = useSpacedUpdate(() => setCalendarView(initialView.current));
|
||||||
useResizeObserver(containerRef, () => calendarRef.current?.updateSize());
|
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<CreateChildrenResponse>(`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 &&
|
return (plugins &&
|
||||||
<div className="calendar-view" ref={containerRef}>
|
<div className="calendar-view" ref={containerRef}>
|
||||||
@@ -64,6 +110,8 @@ export default function CalendarView({ note, noteIds }: ViewModeProps<CalendarVi
|
|||||||
weekNumbers={weekNumbers}
|
weekNumbers={weekNumbers}
|
||||||
handleWindowResize={false}
|
handleWindowResize={false}
|
||||||
locale={locale}
|
locale={locale}
|
||||||
|
editable={isEditable} selectable={isEditable}
|
||||||
|
select={onCalendarSelection}
|
||||||
viewDidMount={({ view }) => {
|
viewDidMount={({ view }) => {
|
||||||
if (initialView.current !== view.type) {
|
if (initialView.current !== view.type) {
|
||||||
initialView.current = view.type;
|
initialView.current = view.type;
|
||||||
|
|||||||
59
apps/client/src/widgets/collections/calendar/utils.ts
Normal file
59
apps/client/src/widgets/collections/calendar/utils.ts
Normal file
@@ -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);
|
||||||
|
}
|
||||||
@@ -50,9 +50,6 @@ export default class CalendarView extends ViewMode<{}> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async renderList(): Promise<JQuery<HTMLElement> | undefined> {
|
async renderList(): Promise<JQuery<HTMLElement> | undefined> {
|
||||||
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");
|
||||||
|
|
||||||
let eventBuilder: EventSourceFunc;
|
let eventBuilder: EventSourceFunc;
|
||||||
@@ -64,8 +61,6 @@ export default class CalendarView extends ViewMode<{}> {
|
|||||||
|
|
||||||
const calendar = new Calendar(this.$calendarContainer[0], {
|
const calendar = new Calendar(this.$calendarContainer[0], {
|
||||||
events: eventBuilder,
|
events: eventBuilder,
|
||||||
editable: isEditable,
|
|
||||||
selectable: isEditable,
|
|
||||||
select: (e) => this.#onCalendarSelection(e),
|
select: (e) => this.#onCalendarSelection(e),
|
||||||
eventChange: (e) => this.#onEventMoved(e),
|
eventChange: (e) => this.#onEventMoved(e),
|
||||||
height: "100%",
|
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<CreateChildResponse>(`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) {
|
async #onEventMoved(e: EventChangeArg) {
|
||||||
// Handle start and end date
|
// Handle start and end date
|
||||||
let { startDate, endDate } = this.#parseStartEndDateFromEvent(e.event);
|
let { startDate, endDate } = this.#parseStartEndDateFromEvent(e.event);
|
||||||
@@ -431,38 +365,6 @@ export default class CalendarView extends ViewMode<{}> {
|
|||||||
return [note.title];
|
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">) {
|
buildTouchBarCommand({ TouchBar, buildIcon }: CommandListenerData<"buildTouchBar">) {
|
||||||
if (!this.calendar) {
|
if (!this.calendar) {
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user