diff --git a/apps/client/src/translations/en/translation.json b/apps/client/src/translations/en/translation.json index d5d8286a627..0435239c155 100644 --- a/apps/client/src/translations/en/translation.json +++ b/apps/client/src/translations/en/translation.json @@ -658,6 +658,9 @@ "october": "October", "november": "November", "december": "December", + "day": "Day", + "day_previous": "Previous Day", + "day_next": "Next Day", "week": "Week", "week_previous": "Previous week", "week_next": "Next week", @@ -2216,7 +2219,9 @@ "slide-overview": "Toggle an overview of the slides" }, "calendar_view": { - "delete_note": "Delete note..." + "delete_note": "Delete note...", + "set_slot_duration": "Set slot duration", + "set_slot_label_interval": "Set slot label interval" }, "ocr": { "extracted_text": "Extracted Text (OCR)", diff --git a/apps/client/src/widgets/collections/calendar/index.tsx b/apps/client/src/widgets/collections/calendar/index.tsx index 4594d645647..cc79ed8e40e 100644 --- a/apps/client/src/widgets/collections/calendar/index.tsx +++ b/apps/client/src/widgets/collections/calendar/index.tsx @@ -3,7 +3,7 @@ import "./index.css"; import { Calendar as FullCalendar } from "@fullcalendar/core"; import { DateSelectArg, EventChangeArg, EventMountArg, EventSourceFuncArg, LocaleInput, PluginDef } from "@fullcalendar/core/index.js"; import { DateClickArg } from "@fullcalendar/interaction"; -import { DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons"; +import { dayjs,DISPLAYABLE_LOCALE_IDS } from "@triliumnext/commons"; import { RefObject } from "preact"; import { useCallback, useContext, useEffect, useMemo, useRef, useState } from "preact/hooks"; @@ -40,7 +40,15 @@ interface CalendarViewData { nextText: string; } + const CALENDAR_VIEWS = [ + { + type: "timeGridDay", + name: t("calendar.day"), + icon: "bx bx-calendar-event", + previousText: t("calendar.day_previous"), + nextText: t("calendar.day_next") + }, { type: "timeGridWeek", name: t("calendar.week"), @@ -73,6 +81,10 @@ const CALENDAR_VIEWS = [ const SUPPORTED_CALENDAR_VIEW_TYPE = CALENDAR_VIEWS.map(v => v.type); +const DEFAULT_SLOT_DURATION = "00:15:00"; +const DEFAULT_SLOT_LABEL_INTERVAL = "01:00:00"; + + // Here we hard-code the imports in order to ensure that they are embedded by webpack without having to load all the languages. export const LOCALE_MAPPINGS: Record Promise<{ default: LocaleInput }>) | null> = { de: () => import("@fullcalendar/core/locales/de"), @@ -109,6 +121,8 @@ export default function CalendarView({ note, noteIds }: ViewModeProps setCalendarView(initialView.current)); useResizeObserver(containerRef, () => calendarRef.current?.updateSize()); @@ -127,6 +141,19 @@ export default function CalendarView({ note, noteIds }: ViewModeProps { + if (!/^(\d{2}):([0-5]\d):([0-5]\d)$/.test(str)) return false; + + const [hours, minutes, seconds] = str.split(':').map(Number); + const d = dayjs.duration({ hours, minutes, seconds }); + + const totalMs = d.asMilliseconds(); + const oneMinute = dayjs.duration(1, 'minute').asMilliseconds(); + const twentyFourHours = dayjs.duration(24, 'hours').asMilliseconds(); + + return totalMs >= oneMinute && totalMs <= twentyFourHours; + }; + // React to changes. useTriliumEvent("entitiesReloaded", ({ loadResults }) => { const api = calendarRef.current; @@ -165,6 +192,8 @@ export default function CalendarView({ note, noteIds }: ViewModeProps = { icon: "bx bx-hash", type: "checkbox", bindToLabel: "calendar:weekNumbers" - } + }, + { + label: t("calendar_view.set_slot_duration"), + icon: "bx bx-time", + type: "combobox", + bindToLabel: "calendar:slotDuration", + options: [ + { value: "00:01:00", label: "1 Minute" }, + { value: "00:05:00", label: "5 Minutes" }, + { value: "00:10:00", label: "10 Minutes" }, + { value: "00:20:00", label: "20 Minutes" }, + { value: "00:30:00", label: "30 Minutes" }, + { value: "01:00:00", label: "1 Hour" } + ] + }, + { + label: t("calendar_view.set_slot_label_interval"), + icon: "bx bx-time", + type: "combobox", + bindToLabel: "calendar:slotLabelInterval", + options: [ + { value: "00:01:00", label: "1 Minute" }, + { value: "00:05:00", label: "5 Minutes" }, + { value: "00:10:00", label: "10 Minutes" }, + { value: "00:20:00", label: "20 Minutes" }, + { value: "00:30:00", label: "30 Minutes" }, + { value: "01:00:00", label: "1 Hour" } + ] + }, ] }, geoMap: { diff --git a/docs/User Guide/User Guide/Collections/Calendar.md b/docs/User Guide/User Guide/Collections/Calendar.md index 82bb59b5d82..d3a7bd51efb 100644 --- a/docs/User Guide/User Guide/Collections/Calendar.md +++ b/docs/User Guide/User Guide/Collections/Calendar.md @@ -58,7 +58,55 @@ In the _Collections_ tab in the #calendar:hideWeekendsWhen present (regardless of value), it will hide Saturday and Sundays from the calendar.#calendar:weekNumbersWhen present (regardless of value), it will show the number of the week on the calendar.#calendar:initialDateChange the date the calendar opens on. When not present, the calendar opens on the current date.#calendar:view

Which view to display in the calendar:

  • timeGridWeek for the week view;
  • dayGridMonth for the month view;
  • multiMonthYear for the year view;
  • listMonth for the list view.

Any other value will be dismissed and the default view (month) will be used instead.

The value of this label is automatically updated when changing the view using the UI buttons.

~child:templateDefines the template for newly created notes in the calendar (via dragging or clicking). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescription
#calendar:hideWeekendsWhen present (regardless of value), it will hide Saturday and Sundays from the calendar.
#calendar:weekNumbersWhen present (regardless of value), it will show the number of the week on the calendar.
#calendar:initialDateChange the date the calendar opens on. When not present, the calendar opens on the current date.
#calendar:view +

Which view to display in the calendar:

+
    +
  • timeGridDay for the day view;
  • +
  • timeGridWeek for the week view;
  • +
  • dayGridMonth for the month view;
  • +
  • multiMonthYear for the year view;
  • +
  • listMonth for the list view.
  • +
+

Any other value will be dismissed and the default view (month) will be used instead.

+

The value of this label is automatically updated when changing the view using the UI buttons.

+
#calendar:slotDurationSets how long each timeslot is on the calendar. Defaults to 00:30:00 (30 minutes). Must have the format "HH:MM:SS". For example, to create timeslots for every 10 minutes, you would set #calendar:slotDuration="00:10:00".
#calendar:slotLabelIntervalSets how often the timeslots on the calendar should be labeled. Defaults to 01:00:00 (1 hour). Must have the format "HH:MM:SS". For example, to label timeslots every 30 minutes, you would set #calendar:slotLabelInterval="00:30:00".
~child:templateDefines the template for newly created notes in the calendar (via dragging or clicking).
+ In addition, the first day of the week can be either Sunday or Monday and can be adjusted from the application settings. @@ -129,6 +177,108 @@ Also note that the recurrence label can be made promoted as with the start and e > [!WARNING] > If the recurrence string is not valid, a toast will be shown with the note ID and title of the note with the erroneous recurrence message. This note will not be added to the calendar + +## Slot Duration & Slot Label Interval + +Trilium's calendar view is powered by FullCalendar, which gives you fine-grained control over how the time grid looks and behaves for day and week views. Two labels you can use to configure these views are `#slotDuration` and `#slotLabelInterval`. Understanding what each one does — and how they interact — lets you tailor the calendar to match your workflow, whether you're scheduling in 15-minute increments or planning out your day in broad hourly blocks. + +### `slotDuration` + +Controls how tall each time slot is on the calendar — essentially the smallest unit of time the grid is divided into. A shorter duration means more rows and finer granularity; a longer one means fewer, chunkier rows. The default is one row every 15 minutes + +**Examples:** + +| Value | Result | +|---|---| +| `#slotDuration="00:15:00"` | One row every 15 minutes | +| `#slotDuration="00:30:00"` | One row every 30 minutes | +| `#slotDuration="01:00:00"` | One row every hour | + + +### `slotLabelInterval` + +Controls how often a time label appears on the left-hand axis. This is independent of the slot size — you can have very small slots but only label every hour to keep the axis readable. The default is a time label shown every hour. + +**Examples:** + +| Value | Result | +|---|---| +| `slotLabelInterval="00:30:00"` | Show a time label every 30 minutes | +| `slotLabelInterval="01:00:00"` | Show a time label every hour | + + + +### Useful combinations + +| `slotDuration` | `slotLabelInterval` | Result | +|---|---|---| +| `00:15:00` | `01:00:00` | Fine grid, clean axis — good for busy schedules | +| `00:30:00` | `01:00:00` | Standard calendar feel | +| `01:00:00` | `01:00:00` | Simple hourly grid — good for day planning | +| `00:15:00` | `00:30:00` | Fine grid, labels every 30 min — balanced detail | + +### Format + +Both values use `HH:mm:ss` format. Hours can go up to `24` (`24:00:00`), while minutes and seconds must be between `00` and `59`. The minimum meaningful duration is 1 minute (`00:01:00`). + +### Examples + +#### `#slotDuration="00:05:00"` — `#slotLabelInterval="00:30:00"` + +Slots every 5 minutes, but only labelled every 30 minutes. Useful for very precise scheduling without a cluttered axis. + +| Axis | Time | +|---|---| +| **9:00** | 9:00 | +| | 9:05 | +| | 9:10 | +| | 9:15 | +| | 9:20 | +| | 9:25 | +| **9:30** | 9:30 | +| | 9:35 | +| | 9:40 | +| | 9:45 | +| | 9:50 | +| | 9:55 | +| **10:00** | 10:00 | +| | 10:05 | +| | 10:10 | +| | 10:15 | +| | 10:20 | +| | 10:25 | +| **10:30** | 10:30 | +| | 10:35 | +| | 10:40 | +| | 10:45 | +| | 10:50 | +| | 10:55 | + +--- + +#### `#slotDuration="00:15:00"` — `#slotLabelInterval="01:00:00"` + +Slots every 15 minutes, but only labelled on the hour. A good balance between granularity and readability. + +| Axis | Time | +|---|---| +| **9:00** | 9:00 | +| | 9:15 | +| | 9:30 | +| | 9:45 | +| **10:00** | 10:00 | +| | 10:15 | +| | 10:30 | +| | 10:45 | +| **11:00** | 11:00 | +| | 11:15 | +| | 11:30 | +| | 11:45 | +| **12:00** | 12:00 | +| | 12:15 | +| | 12:30 | + + ## Use-cases ### Using with the Journal / calendar diff --git a/packages/commons/src/lib/attribute_names.ts b/packages/commons/src/lib/attribute_names.ts index e5081f6c392..7d8b9b5cf63 100644 --- a/packages/commons/src/lib/attribute_names.ts +++ b/packages/commons/src/lib/attribute_names.ts @@ -48,9 +48,11 @@ type Labels = { geolocation: string; expanded: string; "calendar:hideWeekends": boolean; + "calendar:initialDate": string; + "calendar:slotDuration": string; + "calendar:slotLabelInterval": string; "calendar:weekNumbers": boolean; "calendar:view": string; - "calendar:initialDate": string; "map:style": string; "map:scale": boolean; "map:hideLabels": boolean;