From 5c8445f3fec22d59289076019dad408e1825ef92 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 4 Dec 2025 19:03:28 +0200 Subject: [PATCH] chore(react/launch_bar): get calendar to render days --- apps/client/src/widgets/buttons/calendar.ts | 139 +-------------- .../src/widgets/containers/launcher.tsx | 4 +- .../launch_bar/CalendarWidget.css} | 0 .../src/widgets/launch_bar/CalendarWidget.tsx | 159 ++++++++++++++++++ .../widgets/launch_bar/launch_bar_widgets.tsx | 2 +- 5 files changed, 165 insertions(+), 139 deletions(-) rename apps/client/src/{stylesheets/calendar.css => widgets/launch_bar/CalendarWidget.css} (100%) create mode 100644 apps/client/src/widgets/launch_bar/CalendarWidget.tsx diff --git a/apps/client/src/widgets/buttons/calendar.ts b/apps/client/src/widgets/buttons/calendar.ts index 296699882..75e01af1b 100644 --- a/apps/client/src/widgets/buttons/calendar.ts +++ b/apps/client/src/widgets/buttons/calendar.ts @@ -8,7 +8,6 @@ import options from "../../services/options.js"; import { Dropdown } from "bootstrap"; import type { EventData } from "../../components/app_context.js"; import { dayjs, type Dayjs } from "@triliumnext/commons"; -import "../../stylesheets/calendar.css"; import type { AttributeRow, OptionDefinitions } from "@triliumnext/commons"; const MONTHS = [ @@ -28,12 +27,6 @@ const MONTHS = [ const DROPDOWN_TPL = `
- -
@@ -61,7 +54,6 @@ const DROPDOWN_TPL = `
-
`; const DAYS_OF_WEEK = [ @@ -74,9 +66,7 @@ const DAYS_OF_WEEK = [ t("calendar.sat") ]; -interface DateNotesForMonth { - [date: string]: string; -} + interface WeekCalculationOptions { firstWeekType: number; @@ -228,9 +218,6 @@ export default class CalendarWidget extends RightDropdownButtonWidget { // Store firstDayOfWeek as ISO (1–7) manageFirstDayOfWeek() { - const rawFirstDayOfWeek = options.getInt("firstDayOfWeek") || 0; - this.firstDayOfWeekISO = rawFirstDayOfWeek === 0 ? 7 : rawFirstDayOfWeek; - let localeDaysOfWeek = [...DAYS_OF_WEEK]; const shifted = localeDaysOfWeek.splice(0, rawFirstDayOfWeek); localeDaysOfWeek = ['', ...localeDaysOfWeek, ...shifted]; @@ -244,34 +231,13 @@ export default class CalendarWidget extends RightDropdownButtonWidget { }; } - getWeekStartDate(date: Dayjs): Dayjs { - const currentISO = date.isoWeekday(); - const diff = (currentISO - this.firstDayOfWeekISO + 7) % 7; - return date.clone().subtract(diff, "day").startOf("day"); - } - - getWeekNumber(date: Dayjs): number { - const weekStart = this.getWeekStartDate(date); - return weekStart.isoWeek(); - } - async dropdownShown() { await this.getWeekNoteEnable(); this.weekNotes = await server.get(`attribute-values/weekNote`); - this.init(appContext.tabManager.getActiveContextNote()?.getOwnedLabelValue("dateNote") ?? null); + this.init( ?? null); } - init(activeDate: string | null) { - this.activeDate = activeDate ? dayjs(`${activeDate}T12:00:00`) : null; - this.todaysDate = dayjs(); - this.date = dayjs(this.activeDate || this.todaysDate).startOf('month'); - this.createMonth(); - } - - createDay(dateNotesForMonth: DateNotesForMonth, num: number) { - const $newDay = $("") - .addClass("calendar-date") - .attr("data-calendar-date", this.date.local().format('YYYY-MM-DD')); + createDay() { const $date = $("").html(String(num)); const dateNoteId = dateNotesForMonth[this.date.local().format('YYYY-MM-DD')]; @@ -304,105 +270,6 @@ export default class CalendarWidget extends RightDropdownButtonWidget { return $newWeekNumber; } - // Use isoWeekday() consistently - private getPrevMonthDays(firstDayISO: number): { weekNumber: number, dates: Dayjs[] } { - const prevMonthLastDay = this.date.subtract(1, 'month').endOf('month'); - const daysToAdd = (firstDayISO - this.firstDayOfWeekISO + 7) % 7; - const dates: Dayjs[] = []; - - const firstDay = this.date.startOf('month'); - const weekNumber = this.getWeekNumber(firstDay); - - // Get dates from previous month - for (let i = daysToAdd - 1; i >= 0; i--) { - dates.push(prevMonthLastDay.subtract(i, 'day')); - } - - return { weekNumber, dates }; - } - - private getNextMonthDays(lastDayISO: number): Dayjs[] { - const nextMonthFirstDay = this.date.add(1, 'month').startOf('month'); - const dates: Dayjs[] = []; - - const lastDayOfUserWeek = ((this.firstDayOfWeekISO + 6 - 1) % 7) + 1; // ISO wrap - const daysToAdd = (lastDayOfUserWeek - lastDayISO + 7) % 7; - - for (let i = 0; i < daysToAdd; i++) { - dates.push(nextMonthFirstDay.add(i, 'day')); - } - return dates; - } - - async createMonth() { - const month = this.date.format('YYYY-MM'); - const dateNotesForMonth: DateNotesForMonth = await server.get(`special-notes/notes-for-month/${month}`); - - this.$month.empty(); - - const firstDay = this.date.startOf('month'); - const firstDayISO = firstDay.isoWeekday(); - - // Previous month filler - if (firstDayISO !== this.firstDayOfWeekISO) { - const { weekNumber, dates } = this.getPrevMonthDays(firstDayISO); - const prevMonth = this.date.subtract(1, 'month').format('YYYY-MM'); - const dateNotesForPrevMonth: DateNotesForMonth = await server.get(`special-notes/notes-for-month/${prevMonth}`); - - const $weekNumber = this.createWeekNumber(weekNumber); - this.$month.append($weekNumber); - - dates.forEach(date => { - const tempDate = this.date; - this.date = date; - const $day = this.createDay(dateNotesForPrevMonth, date.date()); - $day.addClass('calendar-date-prev-month'); - this.$month.append($day); - this.date = tempDate; - }); - } - - const currentMonth = this.date.month(); - - // Main month - while (this.date.month() === currentMonth) { - const weekNumber = this.getWeekNumber(this.date); - if (this.date.isoWeekday() === this.firstDayOfWeekISO) { - const $weekNumber = this.createWeekNumber(weekNumber); - this.$month.append($weekNumber); - } - - const $day = this.createDay(dateNotesForMonth, this.date.date()); - this.$month.append($day); - this.date = this.date.add(1, 'day'); - } - // while loop trips over and day is at 30/31, bring it back - this.date = this.date.startOf('month').subtract(1, 'month'); - - // Add dates from next month - const lastDayOfMonth = this.date.endOf('month'); - const lastDayISO = lastDayOfMonth.isoWeekday(); - const lastDayOfUserWeek = ((this.firstDayOfWeekISO + 6 - 1) % 7) + 1; - - if (lastDayISO !== lastDayOfUserWeek) { - const dates = this.getNextMonthDays(lastDayISO); - const nextMonth = this.date.add(1, 'month').format('YYYY-MM'); - const dateNotesForNextMonth: DateNotesForMonth = await server.get(`special-notes/notes-for-month/${nextMonth}`); - - dates.forEach(date => { - const tempDate = this.date; - this.date = date; - const $day = this.createDay(dateNotesForNextMonth, date.date()); - $day.addClass('calendar-date-next-month'); - this.$month.append($day); - this.date = tempDate; - }); - } - - this.$monthSelect.text(MONTHS[this.date.month()]); - this.$yearSelect.val(this.date.year()); - } - async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { const WEEK_OPTIONS: (keyof OptionDefinitions)[] = [ "firstDayOfWeek", diff --git a/apps/client/src/widgets/containers/launcher.tsx b/apps/client/src/widgets/containers/launcher.tsx index c61717cf2..2e9b6693f 100644 --- a/apps/client/src/widgets/containers/launcher.tsx +++ b/apps/client/src/widgets/containers/launcher.tsx @@ -1,4 +1,3 @@ -import CalendarWidget from "../buttons/calendar.js"; import SyncStatusWidget from "../sync_status.js"; import BasicWidget, { wrapReactWidgets } from "../basic_widget.js"; import utils, { isMobile } from "../../services/utils.js"; @@ -16,6 +15,7 @@ import QuickSearchWidget from "../quick_search.js"; import { ParentComponent } from "../react/react_utils.jsx"; import { useContext, useEffect, useMemo, useState } from "preact/hooks"; import { LaunchBarActionButton, useLauncherIconAndTitle } from "../launch_bar/launch_bar_widgets.jsx"; +import CalendarWidget from "../launch_bar/CalendarWidget.jsx"; interface InnerWidget extends BasicWidget { settings?: { @@ -98,7 +98,7 @@ export default class LauncherWidget extends BasicWidget { const builtinWidget = note.getLabelValue("builtinWidget"); switch (builtinWidget) { case "calendar": - return new CalendarWidget(note.title, note.getIcon()); + return case "spacer": // || has to be inside since 0 is a valid value const baseSize = parseInt(note.getLabelValue("baseSize") || "40"); diff --git a/apps/client/src/stylesheets/calendar.css b/apps/client/src/widgets/launch_bar/CalendarWidget.css similarity index 100% rename from apps/client/src/stylesheets/calendar.css rename to apps/client/src/widgets/launch_bar/CalendarWidget.css diff --git a/apps/client/src/widgets/launch_bar/CalendarWidget.tsx b/apps/client/src/widgets/launch_bar/CalendarWidget.tsx new file mode 100644 index 000000000..ca562bb38 --- /dev/null +++ b/apps/client/src/widgets/launch_bar/CalendarWidget.tsx @@ -0,0 +1,159 @@ +import { useEffect, useState } from "preact/hooks"; +import FNote from "../../entities/fnote"; +import { LaunchBarDropdownButton, useLauncherIconAndTitle } from "./launch_bar_widgets"; +import { Dayjs, dayjs } from "@triliumnext/commons"; +import appContext from "../../components/app_context"; +import { useTriliumOptionInt } from "../react/hooks"; +import { VNode } from "preact"; +import clsx from "clsx"; +import "./CalendarWidget.css"; +import server from "../../services/server"; + +interface DateNotesForMonth { + [date: string]: string; +} + +export default function CalendarWidget({ launcherNote }: { launcherNote: FNote }) { + const { title, icon } = useLauncherIconAndTitle(launcherNote); + const [ date, setDate ] = useState(); + const [ rawFirstDayOfWeek ] = useTriliumOptionInt("firstDayOfWeek") ?? 0; + const firstDayOfWeekISO = (rawFirstDayOfWeek === 0 ? 7 : rawFirstDayOfWeek); + + useEffect(() => { + + }) + + return ( + { + const dateNote = appContext.tabManager.getActiveContextNote()?.getOwnedLabelValue("dateNote"); + const activeDate = dateNote ? dayjs(`${dateNote}T12:00:00`) : null; + const todaysDate = dayjs(); + const date = dayjs(activeDate || todaysDate).startOf('month'); + setDate(date); + }} + > + {date &&
+ +
} +
+ ) +} + +function Calendar({ date, firstDayOfWeekISO }: { date: Dayjs, firstDayOfWeekISO: number }) { + const month = date.format('YYYY-MM'); + const firstDay = date.startOf('month'); + const firstDayISO = firstDay.isoWeekday(); + + return ( +
+ {firstDayISO !== firstDayOfWeekISO && } + + +
+ ) +} + +function PreviousMonthDays({ date, firstDayISO, firstDayOfWeekISO }: { date: Dayjs, firstDayISO: number, firstDayOfWeekISO: number }) { + const prevMonth = date.subtract(1, 'month').format('YYYY-MM'); + const { weekNumber, dates } = getPrevMonthDays(date, firstDayISO, firstDayOfWeekISO); + const [ dateNotesForPrevMonth, setDateNotesForPrevMonth ] = useState(); + + useEffect(() => { + server.get(`special-notes/notes-for-month/${prevMonth}`).then(setDateNotesForPrevMonth); + }, [ date ]); + + return dates.map(date => ( + + )); +} + +function CurrentMonthDays({ date, firstDayOfWeekISO }: { date: Dayjs, firstDayOfWeekISO: number }) { + const dates = getCurMonthDays(date, firstDayOfWeekISO); + + return dates.map(date => ( + + )); +} + +function NextMonthDays({ date, firstDayOfWeekISO }: { date: Dayjs, firstDayOfWeekISO: number }) { + const lastDayOfMonth = date.endOf('month'); + const lastDayISO = lastDayOfMonth.isoWeekday(); + const lastDayOfUserWeek = ((firstDayOfWeekISO + 6 - 1) % 7) + 1; + const nextMonth = date.add(1, 'month').format('YYYY-MM'); + const [ dateNotesForNextMonth, setDateNotesForNextMonth ] = useState(); + + useEffect(() => { + server.get(`special-notes/notes-for-month/${nextMonth}`).then(setDateNotesForNextMonth); + }, [ date ]); + + const dates = lastDayISO !== lastDayOfUserWeek ? getNextMonthDays(date, lastDayISO, firstDayOfWeekISO) : []; + return dates.map(date => ( + + )); +} + +function CalendarDay({ date, dateNotesForMonth, className }: { date: Dayjs, dateNotesForMonth?: DateNotesForMonth, className?: string }) { + return ( +
+ + {date.date()} + + + ); +} + +function getPrevMonthDays(date: Dayjs, firstDayISO: number, firstDayOfWeekISO: number): { weekNumber: number, dates: Dayjs[] } { + const prevMonthLastDay = date.subtract(1, 'month').endOf('month'); + const daysToAdd = (firstDayISO - firstDayOfWeekISO + 7) % 7; + const dates: Dayjs[] = []; + + const firstDay = date.startOf('month'); + const weekNumber = getWeekNumber(firstDay, firstDayOfWeekISO); + + // Get dates from previous month + for (let i = daysToAdd - 1; i >= 0; i--) { + dates.push(prevMonthLastDay.subtract(i, 'day')); + } + + return { weekNumber, dates }; +} + +function getCurMonthDays(date: Dayjs, firstDayOfWeekISO: number) { + let dateCursor = date; + const currentMonth = date.month(); + const dates: Dayjs[] = []; + while (dateCursor.month() === currentMonth) { + dates.push(dateCursor); + dateCursor = dateCursor.add(1, "day"); + } + return dates; +} + +function getNextMonthDays(date: Dayjs, lastDayISO: number, firstDayOfWeekISO): Dayjs[] { + const nextMonthFirstDay = date.add(1, 'month').startOf('month'); + const dates: Dayjs[] = []; + + const lastDayOfUserWeek = ((firstDayOfWeekISO + 6 - 1) % 7) + 1; // ISO wrap + const daysToAdd = (lastDayOfUserWeek - lastDayISO + 7) % 7; + + for (let i = 0; i < daysToAdd; i++) { + dates.push(nextMonthFirstDay.add(i, 'day')); + } + return dates; +} + +function getWeekNumber(date: Dayjs, firstDayOfWeekISO: number): number { + const weekStart = getWeekStartDate(date, firstDayOfWeekISO); + return weekStart.isoWeek(); +} + +function getWeekStartDate(date: Dayjs, firstDayOfWeekISO: number): Dayjs { + const currentISO = date.isoWeekday(); + const diff = (currentISO - firstDayOfWeekISO + 7) % 7; + return date.clone().subtract(diff, "day").startOf("day"); +} diff --git a/apps/client/src/widgets/launch_bar/launch_bar_widgets.tsx b/apps/client/src/widgets/launch_bar/launch_bar_widgets.tsx index 5a72e0f54..715b5dbe5 100644 --- a/apps/client/src/widgets/launch_bar/launch_bar_widgets.tsx +++ b/apps/client/src/widgets/launch_bar/launch_bar_widgets.tsx @@ -20,7 +20,7 @@ export function LaunchBarActionButton(props: Omit & { icon: string }) { +export function LaunchBarDropdownButton({ children, icon, ...props }: Pick & { icon: string }) { return (