From 1af62006550d47c35797b67b2afe1359361b4d3c Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 4 Dec 2025 13:24:47 +0200 Subject: [PATCH 01/29] chore(react/launch_bar): get launch bar to render React widgets --- .../src/widgets/containers/{launcher.ts => launcher.tsx} | 8 ++++---- apps/client/src/widgets/launch_bar/BookmarkButtons.tsx | 5 +++++ .../client/src/widgets/launch_bar/RightDropdownButton.tsx | 3 +++ apps/client/src/widgets/launch_bar/launch_bar_widget.ts | 3 +++ 4 files changed, 15 insertions(+), 4 deletions(-) rename apps/client/src/widgets/containers/{launcher.ts => launcher.tsx} (94%) create mode 100644 apps/client/src/widgets/launch_bar/BookmarkButtons.tsx create mode 100644 apps/client/src/widgets/launch_bar/RightDropdownButton.tsx create mode 100644 apps/client/src/widgets/launch_bar/launch_bar_widget.ts diff --git a/apps/client/src/widgets/containers/launcher.ts b/apps/client/src/widgets/containers/launcher.tsx similarity index 94% rename from apps/client/src/widgets/containers/launcher.ts rename to apps/client/src/widgets/containers/launcher.tsx index e1bfc5a8b..41d7fee9d 100644 --- a/apps/client/src/widgets/containers/launcher.ts +++ b/apps/client/src/widgets/containers/launcher.tsx @@ -1,9 +1,8 @@ import CalendarWidget from "../buttons/calendar.js"; import SpacerWidget from "../spacer.js"; -import BookmarkButtons from "../bookmark_buttons.js"; import ProtectedSessionStatusWidget from "../buttons/protected_session_status.js"; import SyncStatusWidget from "../sync_status.js"; -import BasicWidget from "../basic_widget.js"; +import BasicWidget, { wrapReactWidgets } from "../basic_widget.js"; import NoteLauncher from "../buttons/launcher/note_launcher.js"; import ScriptLauncher from "../buttons/launcher/script_launcher.js"; import CommandButtonWidget from "../buttons/command_button.js"; @@ -14,6 +13,7 @@ import QuickSearchLauncherWidget from "../quick_search_launcher.js"; import type FNote from "../../entities/fnote.js"; import type { CommandNames } from "../../components/app_context.js"; import AiChatButton from "../buttons/ai_chat_button.js"; +import BookmarkButtons from "../launch_bar/BookmarkButtons.jsx"; interface InnerWidget extends BasicWidget { settings?: { @@ -64,7 +64,7 @@ export default class LauncherWidget extends BasicWidget { } else if (launcherType === "customWidget") { widget = await this.initCustomWidget(note); } else if (launcherType === "builtinWidget") { - widget = this.initBuiltinWidget(note); + widget = wrapReactWidgets([ this.initBuiltinWidget(note) ])[0]; } else { throw new Error(`Unrecognized launcher type '${launcherType}' for launcher '${note.noteId}' title '${note.title}'`); } @@ -111,7 +111,7 @@ export default class LauncherWidget extends BasicWidget { return new SpacerWidget(baseSize, growthFactor); case "bookmarks": - return new BookmarkButtons(this.isHorizontalLayout); + return case "protectedSession": return new ProtectedSessionStatusWidget(); case "syncStatus": diff --git a/apps/client/src/widgets/launch_bar/BookmarkButtons.tsx b/apps/client/src/widgets/launch_bar/BookmarkButtons.tsx new file mode 100644 index 000000000..4d6abec7d --- /dev/null +++ b/apps/client/src/widgets/launch_bar/BookmarkButtons.tsx @@ -0,0 +1,5 @@ +import type { LaunchBarWidgetProps } from "./launch_bar_widget"; + +export default function BookmarkButtons({ }: LaunchBarWidgetProps) { + return

Bookmarks goes here.

; +} diff --git a/apps/client/src/widgets/launch_bar/RightDropdownButton.tsx b/apps/client/src/widgets/launch_bar/RightDropdownButton.tsx new file mode 100644 index 000000000..e47b5624c --- /dev/null +++ b/apps/client/src/widgets/launch_bar/RightDropdownButton.tsx @@ -0,0 +1,3 @@ +export default function RightDropdownButton() { + return

Button goes here.

; +} diff --git a/apps/client/src/widgets/launch_bar/launch_bar_widget.ts b/apps/client/src/widgets/launch_bar/launch_bar_widget.ts new file mode 100644 index 000000000..36284797d --- /dev/null +++ b/apps/client/src/widgets/launch_bar/launch_bar_widget.ts @@ -0,0 +1,3 @@ +export interface LaunchBarWidgetProps { + isHorizontalLayout: boolean; +} From 48cbb80e790b4032a53656ab12d2ed6ca621f389 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 4 Dec 2025 14:18:04 +0200 Subject: [PATCH 02/29] chore(react/launch_bar): port open_note_button_widget --- apps/client/src/services/utils.ts | 2 +- apps/client/src/widgets/bookmark_buttons.ts | 8 +-- .../buttons/open_note_button_widget.ts | 49 ------------- .../widgets/launch_bar/BookmarkButtons.tsx | 70 ++++++++++++++++++- .../client/src/widgets/react/ActionButton.tsx | 8 +-- apps/client/src/widgets/react/hooks.tsx | 13 ++++ 6 files changed, 87 insertions(+), 63 deletions(-) delete mode 100644 apps/client/src/widgets/buttons/open_note_button_widget.ts diff --git a/apps/client/src/services/utils.ts b/apps/client/src/services/utils.ts index 2045cd4d7..e6da60ae5 100644 --- a/apps/client/src/services/utils.ts +++ b/apps/client/src/services/utils.ts @@ -150,7 +150,7 @@ export function isMac() { export const hasTouchBar = (isMac() && isElectron()); -function isCtrlKey(evt: KeyboardEvent | MouseEvent | JQuery.ClickEvent | JQuery.ContextMenuEvent | JQuery.TriggeredEvent | React.PointerEvent | JQueryEventObject) { +export function isCtrlKey(evt: KeyboardEvent | MouseEvent | JQuery.ClickEvent | JQuery.ContextMenuEvent | JQuery.TriggeredEvent | React.PointerEvent | JQueryEventObject) { return (!isMac() && evt.ctrlKey) || (isMac() && evt.metaKey); } diff --git a/apps/client/src/widgets/bookmark_buttons.ts b/apps/client/src/widgets/bookmark_buttons.ts index b32393c9a..88ff3c7e9 100644 --- a/apps/client/src/widgets/bookmark_buttons.ts +++ b/apps/client/src/widgets/bookmark_buttons.ts @@ -1,5 +1,4 @@ import FlexContainer from "./containers/flex_container.js"; -import OpenNoteButtonWidget from "./buttons/open_note_button_widget.js"; import BookmarkFolderWidget from "./buttons/bookmark_folder.js"; import froca from "../services/froca.js"; import utils from "../services/utils.js"; @@ -23,10 +22,6 @@ export default class BookmarkButtons extends FlexContainer { } async refresh(): Promise { - this.$widget.empty(); - this.children = []; - this.noteIds = []; - const bookmarkParentNote = await froca.getNote("_lbBookmarks"); if (!bookmarkParentNote) { @@ -37,8 +32,7 @@ export default class BookmarkButtons extends FlexContainer { this.noteIds.push(note.noteId); let buttonWidget: OpenNoteButtonWidget | BookmarkFolderWidget = note.isLabelTruthy("bookmarkFolder") - ? new BookmarkFolderWidget(note) - : new OpenNoteButtonWidget(note).class("launcher-button"); + ? new BookmarkFolderWidget(note); if (this.settings.titlePlacement) { if (!("settings" in buttonWidget)) { diff --git a/apps/client/src/widgets/buttons/open_note_button_widget.ts b/apps/client/src/widgets/buttons/open_note_button_widget.ts deleted file mode 100644 index c0a4c6334..000000000 --- a/apps/client/src/widgets/buttons/open_note_button_widget.ts +++ /dev/null @@ -1,49 +0,0 @@ -import OnClickButtonWidget from "./onclick_button.js"; -import linkContextMenuService from "../../menus/link_context_menu.js"; -import utils from "../../services/utils.js"; -import appContext from "../../components/app_context.js"; -import type FNote from "../../entities/fnote.js"; - -export default class OpenNoteButtonWidget extends OnClickButtonWidget { - - private noteToOpen: FNote; - - constructor(noteToOpen: FNote) { - super(); - - this.noteToOpen = noteToOpen; - - this.title(() => utils.escapeHtml(this.noteToOpen.title)) - .icon(() => this.noteToOpen.getIcon()) - .onClick((widget, evt) => this.launch(evt)) - .onAuxClick((widget, evt) => this.launch(evt)) - .onContextMenu((evt) => { - if (evt) { - linkContextMenuService.openContextMenu(this.noteToOpen.noteId, evt); - } - }); - } - - async launch(evt: JQuery.ClickEvent | JQuery.TriggeredEvent | JQuery.ContextMenuEvent) { - if (evt.which === 3) { - return; - } - const hoistedNoteId = this.getHoistedNoteId(); - const ctrlKey = utils.isCtrlKey(evt); - - if ((evt.which === 1 && ctrlKey) || evt.which === 2) { - const activate = evt.shiftKey ? true : false; - await appContext.tabManager.openInNewTab(this.noteToOpen.noteId, hoistedNoteId, activate); - } else { - await appContext.tabManager.openInSameTab(this.noteToOpen.noteId); - } - } - - getHoistedNoteId() { - return this.noteToOpen.getRelationValue("hoistedNote") || appContext.tabManager.getActiveContext()?.hoistedNoteId; - } - - initialRenderCompleteEvent() { - // we trigger refresh above - } -} diff --git a/apps/client/src/widgets/launch_bar/BookmarkButtons.tsx b/apps/client/src/widgets/launch_bar/BookmarkButtons.tsx index 4d6abec7d..45941c699 100644 --- a/apps/client/src/widgets/launch_bar/BookmarkButtons.tsx +++ b/apps/client/src/widgets/launch_bar/BookmarkButtons.tsx @@ -1,5 +1,71 @@ +import { useMemo } from "preact/hooks"; import type { LaunchBarWidgetProps } from "./launch_bar_widget"; +import { CSSProperties } from "preact"; +import type FNote from "../../entities/fnote"; +import { useChildNotes, useNoteLabel, useNoteProperty } from "../react/hooks"; +import Dropdown from "../react/Dropdown"; +import ActionButton from "../react/ActionButton"; +import appContext from "../../components/app_context"; +import { escapeHtml, isCtrlKey } from "../../services/utils"; +import link_context_menu from "../../menus/link_context_menu"; -export default function BookmarkButtons({ }: LaunchBarWidgetProps) { - return

Bookmarks goes here.

; +const PARENT_NOTE_ID = "_lbBookmarks"; + +export default function BookmarkButtons({ isHorizontalLayout }: LaunchBarWidgetProps) { + const style = useMemo(() => ({ + display: "flex", + flexDirection: isHorizontalLayout ? "row" : "column", + contain: "none" + }), [ isHorizontalLayout ]); + const childNotes = useChildNotes(PARENT_NOTE_ID); + + return ( +
+ {childNotes?.map(childNote => )} +
+ ) +} + +function SingleBookmark({ note }: { note: FNote }) { + return +} + +function OpenNoteButtonWidget({ note }: { note: FNote }) { + const [ iconClass ] = useNoteLabel(note, "iconClass"); + const title = useNoteProperty(note, "title"); + + async function launch(evt: MouseEvent) { + if (evt.which === 3) { + return; + } + const hoistedNoteId = getHoistedNoteId(note); + const ctrlKey = isCtrlKey(evt); + + if ((evt.which === 1 && ctrlKey) || evt.which === 2) { + const activate = evt.shiftKey ? true : false; + await appContext.tabManager.openInNewTab(note.noteId, hoistedNoteId, activate); + } else { + await appContext.tabManager.openInSameTab(note.noteId); + } + } + + return title && iconClass && ( + { + evt.preventDefault(); + link_context_menu.openContextMenu(note.noteId, evt); + }} + /> + ) +} + +function getHoistedNoteId(noteToOpen: FNote) { + return noteToOpen.getRelationValue("hoistedNote") || appContext.tabManager.getActiveContext()?.hoistedNoteId; } diff --git a/apps/client/src/widgets/react/ActionButton.tsx b/apps/client/src/widgets/react/ActionButton.tsx index 28489005d..a37f34514 100644 --- a/apps/client/src/widgets/react/ActionButton.tsx +++ b/apps/client/src/widgets/react/ActionButton.tsx @@ -2,13 +2,13 @@ import { useEffect, useRef, useState } from "preact/hooks"; import { CommandNames } from "../../components/app_context"; import { useStaticTooltip } from "./hooks"; import keyboard_actions from "../../services/keyboard_actions"; +import { HTMLAttributes } from "preact"; -export interface ActionButtonProps { +export interface ActionButtonProps extends Pick, "onClick" | "onAuxClick" | "onContextMenu"> { text: string; titlePosition?: "top" | "right" | "bottom" | "left"; icon: string; className?: string; - onClick?: (e: MouseEvent) => void; triggerCommand?: CommandNames; noIconActionClass?: boolean; frame?: boolean; @@ -16,7 +16,7 @@ export interface ActionButtonProps { disabled?: boolean; } -export default function ActionButton({ text, icon, className, onClick, triggerCommand, titlePosition, noIconActionClass, frame, active, disabled }: ActionButtonProps) { +export default function ActionButton({ text, icon, className, triggerCommand, titlePosition, noIconActionClass, frame, active, disabled, ...restProps }: ActionButtonProps) { const buttonRef = useRef(null); const [ keyboardShortcut, setKeyboardShortcut ] = useState(); @@ -35,8 +35,8 @@ export default function ActionButton({ text, icon, className, onClick, triggerCo return @@ -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 ( Date: Thu, 4 Dec 2025 19:22:28 +0200 Subject: [PATCH 22/29] refactor(react/launch_bar): use different mechanism for gathering calendar info --- .../src/widgets/launch_bar/CalendarWidget.tsx | 71 ++--------------- .../widgets/launch_bar/CalendarWidgetUtils.ts | 76 +++++++++++++++++++ 2 files changed, 84 insertions(+), 63 deletions(-) create mode 100644 apps/client/src/widgets/launch_bar/CalendarWidgetUtils.ts diff --git a/apps/client/src/widgets/launch_bar/CalendarWidget.tsx b/apps/client/src/widgets/launch_bar/CalendarWidget.tsx index ca562bb38..1e149041c 100644 --- a/apps/client/src/widgets/launch_bar/CalendarWidget.tsx +++ b/apps/client/src/widgets/launch_bar/CalendarWidget.tsx @@ -8,6 +8,7 @@ import { VNode } from "preact"; import clsx from "clsx"; import "./CalendarWidget.css"; import server from "../../services/server"; +import { getMonthInformation } from "./CalendarWidgetUtils"; interface DateNotesForMonth { [date: string]: string; @@ -45,19 +46,19 @@ function Calendar({ date, firstDayOfWeekISO }: { date: Dayjs, firstDayOfWeekISO: const month = date.format('YYYY-MM'); const firstDay = date.startOf('month'); const firstDayISO = firstDay.isoWeekday(); + const monthInfo = getMonthInformation(date, firstDayISO, firstDayOfWeekISO); return (
- {firstDayISO !== firstDayOfWeekISO && } - - + {firstDayISO !== firstDayOfWeekISO && } + +
) } -function PreviousMonthDays({ date, firstDayISO, firstDayOfWeekISO }: { date: Dayjs, firstDayISO: number, firstDayOfWeekISO: number }) { +function PreviousMonthDays({ date, dates }: { date: Dayjs, dates: Dayjs[] }) { const prevMonth = date.subtract(1, 'month').format('YYYY-MM'); - const { weekNumber, dates } = getPrevMonthDays(date, firstDayISO, firstDayOfWeekISO); const [ dateNotesForPrevMonth, setDateNotesForPrevMonth ] = useState(); useEffect(() => { @@ -69,18 +70,13 @@ function PreviousMonthDays({ date, firstDayISO, firstDayOfWeekISO }: { date: Day )); } -function CurrentMonthDays({ date, firstDayOfWeekISO }: { date: Dayjs, firstDayOfWeekISO: number }) { - const dates = getCurMonthDays(date, firstDayOfWeekISO); - +function CurrentMonthDays({ date, dates }: { date: Dayjs, dates: Dayjs[] }) { 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; +function NextMonthDays({ date, dates }: { date: Dayjs, dates: Dayjs[] }) { const nextMonth = date.add(1, 'month').format('YYYY-MM'); const [ dateNotesForNextMonth, setDateNotesForNextMonth ] = useState(); @@ -88,7 +84,6 @@ function NextMonthDays({ date, firstDayOfWeekISO }: { date: Dayjs, firstDayOfWee server.get(`special-notes/notes-for-month/${nextMonth}`).then(setDateNotesForNextMonth); }, [ date ]); - const dates = lastDayISO !== lastDayOfUserWeek ? getNextMonthDays(date, lastDayISO, firstDayOfWeekISO) : []; return dates.map(date => ( )); @@ -107,53 +102,3 @@ function CalendarDay({ date, dateNotesForMonth, className }: { date: Dayjs, 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/CalendarWidgetUtils.ts b/apps/client/src/widgets/launch_bar/CalendarWidgetUtils.ts new file mode 100644 index 000000000..16d057107 --- /dev/null +++ b/apps/client/src/widgets/launch_bar/CalendarWidgetUtils.ts @@ -0,0 +1,76 @@ +import { Dayjs } from "@triliumnext/commons"; + +interface DateRangeInfo { + weekNumbers: number[]; + dates: Dayjs[]; +} + +export function getMonthInformation(date: Dayjs, firstDayISO: number, firstDayOfWeekISO: number) { + return { + prevMonth: getPrevMonthDays(date, firstDayISO, firstDayOfWeekISO), + currentMonth: getCurMonthDays(date, firstDayOfWeekISO), + nextMonth: getNextMonthDays(date, firstDayOfWeekISO) + } +} + +function getPrevMonthDays(date: Dayjs, firstDayISO: number, firstDayOfWeekISO: number): DateRangeInfo { + 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 { weekNumbers: [ weekNumber ], dates }; +} + +function getCurMonthDays(date: Dayjs, firstDayOfWeekISO: number): DateRangeInfo { + let dateCursor = date; + const currentMonth = date.month(); + const dates: Dayjs[] = []; + const weekNumbers: number[] = []; + while (dateCursor.month() === currentMonth) { + const weekNumber = getWeekNumber(date, firstDayOfWeekISO); + if (date.isoWeekday() === firstDayOfWeekISO) { + weekNumbers.push(weekNumber); + } + + dates.push(dateCursor); + dateCursor = dateCursor.add(1, "day"); + + } + return { weekNumbers, dates }; +} + +function getNextMonthDays(date: Dayjs, firstDayOfWeekISO: number): DateRangeInfo { + const lastDayOfMonth = date.endOf('month'); + const lastDayISO = lastDayOfMonth.isoWeekday(); + const lastDayOfUserWeek = ((firstDayOfWeekISO + 6 - 1) % 7) + 1; + const nextMonthFirstDay = date.add(1, 'month').startOf('month'); + const dates: Dayjs[] = []; + + if (lastDayISO !== lastDayOfUserWeek) { + const daysToAdd = (lastDayOfUserWeek - lastDayISO + 7) % 7; + + for (let i = 0; i < daysToAdd; i++) { + dates.push(nextMonthFirstDay.add(i, 'day')); + } + } + return { weekNumbers: [], 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"); +} From 0d8127140f0bc6aa3c70ac0eaee1aa5585175f8f Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 4 Dec 2025 19:38:56 +0200 Subject: [PATCH 23/29] chore(react/launch_bar): get week numbers to render --- apps/client/src/widgets/buttons/calendar.ts | 5 +-- .../src/widgets/launch_bar/CalendarWidget.tsx | 43 +++++++++++++------ .../widgets/launch_bar/CalendarWidgetUtils.ts | 23 +--------- 3 files changed, 35 insertions(+), 36 deletions(-) diff --git a/apps/client/src/widgets/buttons/calendar.ts b/apps/client/src/widgets/buttons/calendar.ts index 75e01af1b..286b94a14 100644 --- a/apps/client/src/widgets/buttons/calendar.ts +++ b/apps/client/src/widgets/buttons/calendar.ts @@ -53,7 +53,7 @@ const DROPDOWN_TPL = ` -
+ `; const DAYS_OF_WEEK = [ @@ -262,11 +262,10 @@ export default class CalendarWidget extends RightDropdownButtonWidget { $newWeekNumber.addClass("calendar-date-exists").attr("data-href", `#root/${weekNoteId}`); } } else { - $newWeekNumber = $("").addClass("calendar-week-number-disabled"); + } $newWeekNumber.addClass("calendar-week-number").attr("data-calendar-week-number", weekNoteId); - $newWeekNumber.append($("").html(String(weekNumber))); return $newWeekNumber; } diff --git a/apps/client/src/widgets/launch_bar/CalendarWidget.tsx b/apps/client/src/widgets/launch_bar/CalendarWidget.tsx index 1e149041c..b8b94ba73 100644 --- a/apps/client/src/widgets/launch_bar/CalendarWidget.tsx +++ b/apps/client/src/widgets/launch_bar/CalendarWidget.tsx @@ -4,11 +4,11 @@ import { LaunchBarDropdownButton, useLauncherIconAndTitle } from "./launch_bar_w 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"; -import { getMonthInformation } from "./CalendarWidgetUtils"; +import { DateRangeInfo, getMonthInformation, getWeekNumber } from "./CalendarWidgetUtils"; +import { VNode } from "preact"; interface DateNotesForMonth { [date: string]: string; @@ -50,14 +50,14 @@ function Calendar({ date, firstDayOfWeekISO }: { date: Dayjs, firstDayOfWeekISO: return (
- {firstDayISO !== firstDayOfWeekISO && } - + {firstDayISO !== firstDayOfWeekISO && } +
) } -function PreviousMonthDays({ date, dates }: { date: Dayjs, dates: Dayjs[] }) { +function PreviousMonthDays({ date, info: { dates, weekNumbers } }: { date: Dayjs, info: DateRangeInfo }) { const prevMonth = date.subtract(1, 'month').format('YYYY-MM'); const [ dateNotesForPrevMonth, setDateNotesForPrevMonth ] = useState(); @@ -65,15 +65,29 @@ function PreviousMonthDays({ date, dates }: { date: Dayjs, dates: Dayjs[] }) { server.get(`special-notes/notes-for-month/${prevMonth}`).then(setDateNotesForPrevMonth); }, [ date ]); - return dates.map(date => ( - - )); + return ( + <> + + {dates.map(date => )} + + ) } -function CurrentMonthDays({ date, dates }: { date: Dayjs, dates: Dayjs[] }) { - return dates.map(date => ( - - )); +function CurrentMonthDays({ date, firstDayOfWeekISO }: { date: Dayjs, firstDayOfWeekISO: number }) { + let dateCursor = date; + const currentMonth = date.month(); + const items: VNode[] = []; + while (dateCursor.month() === currentMonth) { + const weekNumber = getWeekNumber(dateCursor, firstDayOfWeekISO); + if (dateCursor.isoWeekday() === firstDayOfWeekISO) { + items.push() + } + + items.push() + dateCursor = dateCursor.add(1, "day"); + } + + return items; } function NextMonthDays({ date, dates }: { date: Dayjs, dates: Dayjs[] }) { @@ -102,3 +116,8 @@ function CalendarDay({ date, dateNotesForMonth, className }: { date: Dayjs, date ); } +function CalendarWeek({ weekNumber }: { weekNumber: number }) { + return ( + {weekNumber} + ) +} diff --git a/apps/client/src/widgets/launch_bar/CalendarWidgetUtils.ts b/apps/client/src/widgets/launch_bar/CalendarWidgetUtils.ts index 16d057107..e7a59aa4a 100644 --- a/apps/client/src/widgets/launch_bar/CalendarWidgetUtils.ts +++ b/apps/client/src/widgets/launch_bar/CalendarWidgetUtils.ts @@ -1,6 +1,6 @@ import { Dayjs } from "@triliumnext/commons"; -interface DateRangeInfo { +export interface DateRangeInfo { weekNumbers: number[]; dates: Dayjs[]; } @@ -8,7 +8,6 @@ interface DateRangeInfo { export function getMonthInformation(date: Dayjs, firstDayISO: number, firstDayOfWeekISO: number) { return { prevMonth: getPrevMonthDays(date, firstDayISO, firstDayOfWeekISO), - currentMonth: getCurMonthDays(date, firstDayOfWeekISO), nextMonth: getNextMonthDays(date, firstDayOfWeekISO) } } @@ -29,24 +28,6 @@ function getPrevMonthDays(date: Dayjs, firstDayISO: number, firstDayOfWeekISO: n return { weekNumbers: [ weekNumber ], dates }; } -function getCurMonthDays(date: Dayjs, firstDayOfWeekISO: number): DateRangeInfo { - let dateCursor = date; - const currentMonth = date.month(); - const dates: Dayjs[] = []; - const weekNumbers: number[] = []; - while (dateCursor.month() === currentMonth) { - const weekNumber = getWeekNumber(date, firstDayOfWeekISO); - if (date.isoWeekday() === firstDayOfWeekISO) { - weekNumbers.push(weekNumber); - } - - dates.push(dateCursor); - dateCursor = dateCursor.add(1, "day"); - - } - return { weekNumbers, dates }; -} - function getNextMonthDays(date: Dayjs, firstDayOfWeekISO: number): DateRangeInfo { const lastDayOfMonth = date.endOf('month'); const lastDayISO = lastDayOfMonth.isoWeekday(); @@ -64,7 +45,7 @@ function getNextMonthDays(date: Dayjs, firstDayOfWeekISO: number): DateRangeInfo return { weekNumbers: [], dates }; } -function getWeekNumber(date: Dayjs, firstDayOfWeekISO: number): number { +export function getWeekNumber(date: Dayjs, firstDayOfWeekISO: number): number { const weekStart = getWeekStartDate(date, firstDayOfWeekISO); return weekStart.isoWeek(); } From 62fd07258ec5f5f9916eac5ca7aed71a2ce22dac Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 4 Dec 2025 19:43:47 +0200 Subject: [PATCH 24/29] chore(react/launch_bar): get days of the week to render --- apps/client/src/widgets/buttons/calendar.ts | 21 ------------ .../src/widgets/launch_bar/CalendarWidget.tsx | 34 ++++++++++++++----- .../widgets/launch_bar/CalendarWidgetUtils.ts | 11 ++++++ 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/apps/client/src/widgets/buttons/calendar.ts b/apps/client/src/widgets/buttons/calendar.ts index 286b94a14..0d993540e 100644 --- a/apps/client/src/widgets/buttons/calendar.ts +++ b/apps/client/src/widgets/buttons/calendar.ts @@ -56,18 +56,6 @@ const DROPDOWN_TPL = ` `; -const DAYS_OF_WEEK = [ - t("calendar.sun"), - t("calendar.mon"), - t("calendar.tue"), - t("calendar.wed"), - t("calendar.thu"), - t("calendar.fri"), - t("calendar.sat") -]; - - - interface WeekCalculationOptions { firstWeekType: number; minDaysInFirstWeek: number; @@ -100,7 +88,6 @@ export default class CalendarWidget extends RightDropdownButtonWidget { super.doRender(); this.$month = this.$dropdownContent.find('[data-calendar-area="month"]'); - this.$weekHeader = this.$dropdownContent.find(".calendar-week"); this.manageFirstDayOfWeek(); this.initWeekCalculation(); @@ -216,14 +203,6 @@ export default class CalendarWidget extends RightDropdownButtonWidget { this.weekNoteEnable = noteAttributes.some(a => a.name === 'enableWeekNote'); } - // Store firstDayOfWeek as ISO (1–7) - manageFirstDayOfWeek() { - let localeDaysOfWeek = [...DAYS_OF_WEEK]; - const shifted = localeDaysOfWeek.splice(0, rawFirstDayOfWeek); - localeDaysOfWeek = ['', ...localeDaysOfWeek, ...shifted]; - this.$weekHeader.html(localeDaysOfWeek.map((el) => `${el}`).join('')); - } - initWeekCalculation() { this.weekCalculationOptions = { firstWeekType: options.getInt("firstWeekOfYear") || 0, diff --git a/apps/client/src/widgets/launch_bar/CalendarWidget.tsx b/apps/client/src/widgets/launch_bar/CalendarWidget.tsx index b8b94ba73..f8b20c2f0 100644 --- a/apps/client/src/widgets/launch_bar/CalendarWidget.tsx +++ b/apps/client/src/widgets/launch_bar/CalendarWidget.tsx @@ -7,7 +7,7 @@ import { useTriliumOptionInt } from "../react/hooks"; import clsx from "clsx"; import "./CalendarWidget.css"; import server from "../../services/server"; -import { DateRangeInfo, getMonthInformation, getWeekNumber } from "./CalendarWidgetUtils"; +import { DateRangeInfo, DAYS_OF_WEEK, getMonthInformation, getWeekNumber } from "./CalendarWidgetUtils"; import { VNode } from "preact"; interface DateNotesForMonth { @@ -17,8 +17,6 @@ interface DateNotesForMonth { 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(() => { @@ -36,23 +34,41 @@ export default function CalendarWidget({ launcherNote }: { launcherNote: FNote } }} > {date &&
- +
} ) } -function Calendar({ date, firstDayOfWeekISO }: { date: Dayjs, firstDayOfWeekISO: number }) { +function Calendar({ date }: { date: Dayjs }) { + const [ rawFirstDayOfWeek ] = useTriliumOptionInt("firstDayOfWeek") ?? 0; + const firstDayOfWeekISO = (rawFirstDayOfWeek === 0 ? 7 : rawFirstDayOfWeek); + const month = date.format('YYYY-MM'); const firstDay = date.startOf('month'); const firstDayISO = firstDay.isoWeekday(); const monthInfo = getMonthInformation(date, firstDayISO, firstDayOfWeekISO); return ( -
- {firstDayISO !== firstDayOfWeekISO && } - - + <> + +
+ {firstDayISO !== firstDayOfWeekISO && } + + +
+ + ) +} + +function CalendarWeekHeader({ rawFirstDayOfWeek }: { rawFirstDayOfWeek: number }) { + let localeDaysOfWeek = [...DAYS_OF_WEEK]; + const shifted = localeDaysOfWeek.splice(0, rawFirstDayOfWeek); + localeDaysOfWeek = ['', ...localeDaysOfWeek, ...shifted]; + + return ( +
+ {localeDaysOfWeek.map(dayOfWeek => {dayOfWeek})}
) } diff --git a/apps/client/src/widgets/launch_bar/CalendarWidgetUtils.ts b/apps/client/src/widgets/launch_bar/CalendarWidgetUtils.ts index e7a59aa4a..3b598bbe9 100644 --- a/apps/client/src/widgets/launch_bar/CalendarWidgetUtils.ts +++ b/apps/client/src/widgets/launch_bar/CalendarWidgetUtils.ts @@ -1,4 +1,15 @@ import { Dayjs } from "@triliumnext/commons"; +import { t } from "../../services/i18n"; + +export const DAYS_OF_WEEK = [ + t("calendar.sun"), + t("calendar.mon"), + t("calendar.tue"), + t("calendar.wed"), + t("calendar.thu"), + t("calendar.fri"), + t("calendar.sat") +]; export interface DateRangeInfo { weekNumbers: number[]; From e0aed26f637b4dafc8f3c52ab77b8fd23db2b566 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 4 Dec 2025 19:46:25 +0200 Subject: [PATCH 25/29] refactor(react/launch_bar): extract calendar impl into single file --- .../src/widgets/launch_bar/Calendar.tsx | 175 ++++++++++++++++++ .../src/widgets/launch_bar/CalendarWidget.tsx | 110 +---------- .../widgets/launch_bar/CalendarWidgetUtils.ts | 68 ------- 3 files changed, 176 insertions(+), 177 deletions(-) create mode 100644 apps/client/src/widgets/launch_bar/Calendar.tsx delete mode 100644 apps/client/src/widgets/launch_bar/CalendarWidgetUtils.ts diff --git a/apps/client/src/widgets/launch_bar/Calendar.tsx b/apps/client/src/widgets/launch_bar/Calendar.tsx new file mode 100644 index 000000000..b1753a038 --- /dev/null +++ b/apps/client/src/widgets/launch_bar/Calendar.tsx @@ -0,0 +1,175 @@ +import { useTriliumOptionInt } from "../react/hooks"; +import clsx from "clsx"; +import server from "../../services/server"; +import { VNode } from "preact"; +import { useEffect, useState } from "preact/hooks"; +import { Dayjs } from "@triliumnext/commons"; +import { t } from "../../services/i18n"; + +interface DateNotesForMonth { + [date: string]: string; +} + +const DAYS_OF_WEEK = [ + t("calendar.sun"), + t("calendar.mon"), + t("calendar.tue"), + t("calendar.wed"), + t("calendar.thu"), + t("calendar.fri"), + t("calendar.sat") +]; + +interface DateRangeInfo { + weekNumbers: number[]; + dates: Dayjs[]; +} + +export default function Calendar({ date }: { date: Dayjs }) { + const [ rawFirstDayOfWeek ] = useTriliumOptionInt("firstDayOfWeek") ?? 0; + const firstDayOfWeekISO = (rawFirstDayOfWeek === 0 ? 7 : rawFirstDayOfWeek); + + const month = date.format('YYYY-MM'); + const firstDay = date.startOf('month'); + const firstDayISO = firstDay.isoWeekday(); + const monthInfo = getMonthInformation(date, firstDayISO, firstDayOfWeekISO); + + return ( + <> + +
+ {firstDayISO !== firstDayOfWeekISO && } + + +
+ + ) +} + +function CalendarWeekHeader({ rawFirstDayOfWeek }: { rawFirstDayOfWeek: number }) { + let localeDaysOfWeek = [...DAYS_OF_WEEK]; + const shifted = localeDaysOfWeek.splice(0, rawFirstDayOfWeek); + localeDaysOfWeek = ['', ...localeDaysOfWeek, ...shifted]; + + return ( +
+ {localeDaysOfWeek.map(dayOfWeek => {dayOfWeek})} +
+ ) +} + +function PreviousMonthDays({ date, info: { dates, weekNumbers } }: { date: Dayjs, info: DateRangeInfo }) { + const prevMonth = date.subtract(1, 'month').format('YYYY-MM'); + 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 }) { + let dateCursor = date; + const currentMonth = date.month(); + const items: VNode[] = []; + while (dateCursor.month() === currentMonth) { + const weekNumber = getWeekNumber(dateCursor, firstDayOfWeekISO); + if (dateCursor.isoWeekday() === firstDayOfWeekISO) { + items.push() + } + + items.push() + dateCursor = dateCursor.add(1, "day"); + } + + return items; +} + +function NextMonthDays({ date, dates }: { date: Dayjs, dates: Dayjs[] }) { + 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 ]); + + return dates.map(date => ( + + )); +} + +function CalendarDay({ date, dateNotesForMonth, className }: { date: Dayjs, dateNotesForMonth?: DateNotesForMonth, className?: string }) { + return ( + + + {date.date()} + + + ); +} + +function CalendarWeek({ weekNumber }: { weekNumber: number }) { + return ( + {weekNumber} + ) +} + +export function getMonthInformation(date: Dayjs, firstDayISO: number, firstDayOfWeekISO: number) { + return { + prevMonth: getPrevMonthDays(date, firstDayISO, firstDayOfWeekISO), + nextMonth: getNextMonthDays(date, firstDayOfWeekISO) + } +} + +function getPrevMonthDays(date: Dayjs, firstDayISO: number, firstDayOfWeekISO: number): DateRangeInfo { + 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 { weekNumbers: [ weekNumber ], dates }; +} + +function getNextMonthDays(date: Dayjs, firstDayOfWeekISO: number): DateRangeInfo { + const lastDayOfMonth = date.endOf('month'); + const lastDayISO = lastDayOfMonth.isoWeekday(); + const lastDayOfUserWeek = ((firstDayOfWeekISO + 6 - 1) % 7) + 1; + const nextMonthFirstDay = date.add(1, 'month').startOf('month'); + const dates: Dayjs[] = []; + + if (lastDayISO !== lastDayOfUserWeek) { + const daysToAdd = (lastDayOfUserWeek - lastDayISO + 7) % 7; + + for (let i = 0; i < daysToAdd; i++) { + dates.push(nextMonthFirstDay.add(i, 'day')); + } + } + return { weekNumbers: [], dates }; +} + +export 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/CalendarWidget.tsx b/apps/client/src/widgets/launch_bar/CalendarWidget.tsx index f8b20c2f0..873567b29 100644 --- a/apps/client/src/widgets/launch_bar/CalendarWidget.tsx +++ b/apps/client/src/widgets/launch_bar/CalendarWidget.tsx @@ -3,25 +3,13 @@ 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 clsx from "clsx"; import "./CalendarWidget.css"; -import server from "../../services/server"; -import { DateRangeInfo, DAYS_OF_WEEK, getMonthInformation, getWeekNumber } from "./CalendarWidgetUtils"; -import { VNode } from "preact"; - -interface DateNotesForMonth { - [date: string]: string; -} +import Calendar from "./Calendar"; export default function CalendarWidget({ launcherNote }: { launcherNote: FNote }) { const { title, icon } = useLauncherIconAndTitle(launcherNote); const [ date, setDate ] = useState(); - useEffect(() => { - - }) - return ( - -
- {firstDayISO !== firstDayOfWeekISO && } - - -
- - ) -} - -function CalendarWeekHeader({ rawFirstDayOfWeek }: { rawFirstDayOfWeek: number }) { - let localeDaysOfWeek = [...DAYS_OF_WEEK]; - const shifted = localeDaysOfWeek.splice(0, rawFirstDayOfWeek); - localeDaysOfWeek = ['', ...localeDaysOfWeek, ...shifted]; - - return ( -
- {localeDaysOfWeek.map(dayOfWeek => {dayOfWeek})} -
- ) -} - -function PreviousMonthDays({ date, info: { dates, weekNumbers } }: { date: Dayjs, info: DateRangeInfo }) { - const prevMonth = date.subtract(1, 'month').format('YYYY-MM'); - 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 }) { - let dateCursor = date; - const currentMonth = date.month(); - const items: VNode[] = []; - while (dateCursor.month() === currentMonth) { - const weekNumber = getWeekNumber(dateCursor, firstDayOfWeekISO); - if (dateCursor.isoWeekday() === firstDayOfWeekISO) { - items.push() - } - - items.push() - dateCursor = dateCursor.add(1, "day"); - } - - return items; -} - -function NextMonthDays({ date, dates }: { date: Dayjs, dates: Dayjs[] }) { - 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 ]); - - return dates.map(date => ( - - )); -} - -function CalendarDay({ date, dateNotesForMonth, className }: { date: Dayjs, dateNotesForMonth?: DateNotesForMonth, className?: string }) { - return ( - - - {date.date()} - - - ); -} - -function CalendarWeek({ weekNumber }: { weekNumber: number }) { - return ( - {weekNumber} - ) -} diff --git a/apps/client/src/widgets/launch_bar/CalendarWidgetUtils.ts b/apps/client/src/widgets/launch_bar/CalendarWidgetUtils.ts deleted file mode 100644 index 3b598bbe9..000000000 --- a/apps/client/src/widgets/launch_bar/CalendarWidgetUtils.ts +++ /dev/null @@ -1,68 +0,0 @@ -import { Dayjs } from "@triliumnext/commons"; -import { t } from "../../services/i18n"; - -export const DAYS_OF_WEEK = [ - t("calendar.sun"), - t("calendar.mon"), - t("calendar.tue"), - t("calendar.wed"), - t("calendar.thu"), - t("calendar.fri"), - t("calendar.sat") -]; - -export interface DateRangeInfo { - weekNumbers: number[]; - dates: Dayjs[]; -} - -export function getMonthInformation(date: Dayjs, firstDayISO: number, firstDayOfWeekISO: number) { - return { - prevMonth: getPrevMonthDays(date, firstDayISO, firstDayOfWeekISO), - nextMonth: getNextMonthDays(date, firstDayOfWeekISO) - } -} - -function getPrevMonthDays(date: Dayjs, firstDayISO: number, firstDayOfWeekISO: number): DateRangeInfo { - 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 { weekNumbers: [ weekNumber ], dates }; -} - -function getNextMonthDays(date: Dayjs, firstDayOfWeekISO: number): DateRangeInfo { - const lastDayOfMonth = date.endOf('month'); - const lastDayISO = lastDayOfMonth.isoWeekday(); - const lastDayOfUserWeek = ((firstDayOfWeekISO + 6 - 1) % 7) + 1; - const nextMonthFirstDay = date.add(1, 'month').startOf('month'); - const dates: Dayjs[] = []; - - if (lastDayISO !== lastDayOfUserWeek) { - const daysToAdd = (lastDayOfUserWeek - lastDayISO + 7) % 7; - - for (let i = 0; i < daysToAdd; i++) { - dates.push(nextMonthFirstDay.add(i, 'day')); - } - } - return { weekNumbers: [], dates }; -} - -export 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"); -} From e1cce220b30abdaba1777fe7b0e67049f6f51e08 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 4 Dec 2025 19:56:34 +0200 Subject: [PATCH 26/29] chore(react/launch_bar): add back icons for previous/next month --- apps/client/src/widgets/buttons/calendar.ts | 13 ------ .../src/widgets/launch_bar/CalendarWidget.tsx | 43 ++++++++++++++++++- 2 files changed, 42 insertions(+), 14 deletions(-) diff --git a/apps/client/src/widgets/buttons/calendar.ts b/apps/client/src/widgets/buttons/calendar.ts index 0d993540e..a8f507fd1 100644 --- a/apps/client/src/widgets/buttons/calendar.ts +++ b/apps/client/src/widgets/buttons/calendar.ts @@ -29,8 +29,6 @@ const DROPDOWN_TPL = `
- - -
diff --git a/apps/client/src/widgets/launch_bar/CalendarWidget.tsx b/apps/client/src/widgets/launch_bar/CalendarWidget.tsx index 557d05f5e..cda7b8444 100644 --- a/apps/client/src/widgets/launch_bar/CalendarWidget.tsx +++ b/apps/client/src/widgets/launch_bar/CalendarWidget.tsx @@ -1,4 +1,4 @@ -import { Dispatch, StateUpdater, useEffect, useState } from "preact/hooks"; +import { Dispatch, StateUpdater, useEffect, useMemo, useState } from "preact/hooks"; import FNote from "../../entities/fnote"; import { LaunchBarDropdownButton, useLauncherIconAndTitle } from "./launch_bar_widgets"; import { Dayjs, dayjs } from "@triliumnext/commons"; @@ -6,6 +6,24 @@ import appContext from "../../components/app_context"; import "./CalendarWidget.css"; import Calendar from "./Calendar"; import ActionButton from "../react/ActionButton"; +import Dropdown from "../react/Dropdown"; +import { t } from "../../services/i18n"; +import FormDropdownList from "../react/FormDropdownList"; + +const MONTHS = [ + t("calendar.january"), + t("calendar.february"), + t("calendar.march"), + t("calendar.april"), + t("calendar.may"), + t("calendar.june"), + t("calendar.july"), + t("calendar.august"), + t("calendar.september"), + t("calendar.october"), + t("calendar.november"), + t("calendar.december") +]; export default function CalendarWidget({ launcherNote }: { launcherNote: FNote }) { const { title, icon } = useLauncherIconAndTitle(launcherNote); @@ -21,6 +39,9 @@ export default function CalendarWidget({ launcherNote }: { launcherNote: FNote } const date = dayjs(activeDate || todaysDate).startOf('month'); setDate(date); }} + dropdownOptions={{ + autoClose: "outside" + }} > {date &&
@@ -44,9 +65,23 @@ function CalendarHeader(props: CalendarHeaderProps) { } function CalendarMonthSelector({ date, setDate }: CalendarHeaderProps) { + const months = useMemo(() => ( + Array.from(MONTHS.entries().map(([ index, text ]) => ({ + index: index.toString(), text + }))) + ), []); + console.log("Got months ", months); + return (
+ { + + }} + />
); 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 715b5dbe5..d5302adc0 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 ( , "id" | "c forceShown?: boolean; onShown?: () => void; onHidden?: () => void; + dropdownOptions?: Partial; } -export default function Dropdown({ id, className, buttonClassName, isStatic, children, title, text, dropdownContainerStyle, dropdownContainerClassName, hideToggleArrow, iconAction, disabled, noSelectButtonStyle, noDropdownListStyle, forceShown, onShown: externalOnShown, onHidden: externalOnHidden }: DropdownProps) { +export default function Dropdown({ id, className, buttonClassName, isStatic, children, title, text, dropdownContainerStyle, dropdownContainerClassName, hideToggleArrow, iconAction, disabled, noSelectButtonStyle, noDropdownListStyle, forceShown, onShown: externalOnShown, onHidden: externalOnHidden, dropdownOptions }: DropdownProps) { const dropdownRef = useRef(null); const triggerRef = useRef(null); @@ -32,7 +33,7 @@ export default function Dropdown({ id, className, buttonClassName, isStatic, chi useEffect(() => { if (!triggerRef.current) return; - const dropdown = BootstrapDropdown.getOrCreateInstance(triggerRef.current); + const dropdown = BootstrapDropdown.getOrCreateInstance(triggerRef.current, dropdownOptions); if (forceShown) { dropdown.show(); setShown(true); From a65d2a1bbab6fb15f1147623f157df768bdcddca Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Thu, 4 Dec 2025 21:24:25 +0200 Subject: [PATCH 28/29] chore(react/launch_bar): reintroduce year selector --- apps/client/src/widgets/buttons/calendar.ts | 26 ------------------- .../src/widgets/launch_bar/CalendarWidget.css | 4 +++ .../src/widgets/launch_bar/CalendarWidget.tsx | 26 +++++++++++++++++-- 3 files changed, 28 insertions(+), 28 deletions(-) diff --git a/apps/client/src/widgets/buttons/calendar.ts b/apps/client/src/widgets/buttons/calendar.ts index 22a62e193..731678cd1 100644 --- a/apps/client/src/widgets/buttons/calendar.ts +++ b/apps/client/src/widgets/buttons/calendar.ts @@ -10,32 +10,6 @@ import type { EventData } from "../../components/app_context.js"; import { dayjs, type Dayjs } from "@triliumnext/commons"; import type { AttributeRow, OptionDefinitions } from "@triliumnext/commons"; - - -const DROPDOWN_TPL = ` -
-
-
- - - -
- -
- - - - - -
-
- - -
`; - interface WeekCalculationOptions { firstWeekType: number; minDaysInFirstWeek: number; diff --git a/apps/client/src/widgets/launch_bar/CalendarWidget.css b/apps/client/src/widgets/launch_bar/CalendarWidget.css index 314439846..72249b997 100644 --- a/apps/client/src/widgets/launch_bar/CalendarWidget.css +++ b/apps/client/src/widgets/launch_bar/CalendarWidget.css @@ -174,4 +174,8 @@ background-color: var(--hover-item-background-color); color: var(--hover-item-text-color); text-decoration: underline; +} + +.calendar-dropdown-widget .form-control { + padding: 0; } \ No newline at end of file diff --git a/apps/client/src/widgets/launch_bar/CalendarWidget.tsx b/apps/client/src/widgets/launch_bar/CalendarWidget.tsx index cda7b8444..f1cdfe20b 100644 --- a/apps/client/src/widgets/launch_bar/CalendarWidget.tsx +++ b/apps/client/src/widgets/launch_bar/CalendarWidget.tsx @@ -9,6 +9,7 @@ import ActionButton from "../react/ActionButton"; import Dropdown from "../react/Dropdown"; import { t } from "../../services/i18n"; import FormDropdownList from "../react/FormDropdownList"; +import FormTextBox from "../react/FormTextBox"; const MONTHS = [ t("calendar.january"), @@ -60,6 +61,7 @@ function CalendarHeader(props: CalendarHeaderProps) { return (
+
) } @@ -70,7 +72,6 @@ function CalendarMonthSelector({ date, setDate }: CalendarHeaderProps) { index: index.toString(), text }))) ), []); - console.log("Got months ", months); return (
@@ -87,9 +88,30 @@ function CalendarMonthSelector({ date, setDate }: CalendarHeaderProps) { ); } +function CalendarYearSelector({ date, setDate }: CalendarHeaderProps) { + return ( +
+ + { + const year = parseInt(newValue, 10); + if (!Number.isNaN(year)) { + setDate(date.set("year", year)); + } + }} + data-calendar-input="year" + /> + +
+ ) +} + function AdjustDateButton({ date, setDate, unit, direction }: CalendarHeaderProps & { direction: "prev" | "next", - unit: "month" + unit: "month" | "year" }) { return ( Date: Thu, 4 Dec 2025 21:36:04 +0200 Subject: [PATCH 29/29] chore(react/launch_bar): fix style for month selector --- apps/client/src/widgets/buttons/calendar.ts | 20 ------------------- .../src/widgets/launch_bar/CalendarWidget.tsx | 2 +- apps/client/src/widgets/react/Dropdown.tsx | 10 ++++++++-- 3 files changed, 9 insertions(+), 23 deletions(-) diff --git a/apps/client/src/widgets/buttons/calendar.ts b/apps/client/src/widgets/buttons/calendar.ts index 731678cd1..60b372fee 100644 --- a/apps/client/src/widgets/buttons/calendar.ts +++ b/apps/client/src/widgets/buttons/calendar.ts @@ -62,26 +62,6 @@ export default class CalendarWidget extends RightDropdownButtonWidget { } }); - // Year navigation - this.$yearSelect = this.$dropdownContent.find('[data-calendar-input="year"]'); - this.$yearSelect.on("input", (e) => { - const target = e.target as HTMLInputElement; - this.date = this.date.year(parseInt(target.value)); - this.createMonth(); - }); - - this.$nextYear = this.$dropdownContent.find('[data-calendar-toggle="nextYear"]'); - this.$nextYear.on("click", () => { - this.date = this.date.add(1, 'year'); - this.createMonth(); - }); - - this.$previousYear = this.$dropdownContent.find('[data-calendar-toggle="previousYear"]'); - this.$previousYear.on("click", () => { - this.date = this.date.subtract(1, 'year'); - this.createMonth(); - }); - // Date click this.$dropdownContent.on("click", ".calendar-date", async (ev) => { const date = $(ev.target).closest(".calendar-date").attr("data-calendar-date"); diff --git a/apps/client/src/widgets/launch_bar/CalendarWidget.tsx b/apps/client/src/widgets/launch_bar/CalendarWidget.tsx index f1cdfe20b..2fcceb571 100644 --- a/apps/client/src/widgets/launch_bar/CalendarWidget.tsx +++ b/apps/client/src/widgets/launch_bar/CalendarWidget.tsx @@ -6,7 +6,6 @@ import appContext from "../../components/app_context"; import "./CalendarWidget.css"; import Calendar from "./Calendar"; import ActionButton from "../react/ActionButton"; -import Dropdown from "../react/Dropdown"; import { t } from "../../services/i18n"; import FormDropdownList from "../react/FormDropdownList"; import FormTextBox from "../react/FormTextBox"; @@ -82,6 +81,7 @@ function CalendarMonthSelector({ date, setDate }: CalendarHeaderProps) { onChange={value => { }} + buttonProps={{ "data-calendar-input": "month" }} />
diff --git a/apps/client/src/widgets/react/Dropdown.tsx b/apps/client/src/widgets/react/Dropdown.tsx index c0f5f5585..fbafda25e 100644 --- a/apps/client/src/widgets/react/Dropdown.tsx +++ b/apps/client/src/widgets/react/Dropdown.tsx @@ -1,11 +1,16 @@ import { Dropdown as BootstrapDropdown } from "bootstrap"; -import { ComponentChildren } from "preact"; +import { ComponentChildren, HTMLAttributes } from "preact"; import { CSSProperties, HTMLProps } from "preact/compat"; import { useCallback, useEffect, useRef, useState } from "preact/hooks"; import { useUniqueName } from "./hooks"; +type DataAttributes = { + [key: `data-${string}`]: string | number | boolean | undefined; +}; + export interface DropdownProps extends Pick, "id" | "className"> { buttonClassName?: string; + buttonProps?: Partial & DataAttributes>; isStatic?: boolean; children: ComponentChildren; title?: string; @@ -24,7 +29,7 @@ export interface DropdownProps extends Pick, "id" | "c dropdownOptions?: Partial; } -export default function Dropdown({ id, className, buttonClassName, isStatic, children, title, text, dropdownContainerStyle, dropdownContainerClassName, hideToggleArrow, iconAction, disabled, noSelectButtonStyle, noDropdownListStyle, forceShown, onShown: externalOnShown, onHidden: externalOnHidden, dropdownOptions }: DropdownProps) { +export default function Dropdown({ id, className, buttonClassName, isStatic, children, title, text, dropdownContainerStyle, dropdownContainerClassName, hideToggleArrow, iconAction, disabled, noSelectButtonStyle, noDropdownListStyle, forceShown, onShown: externalOnShown, onHidden: externalOnHidden, dropdownOptions, buttonProps }: DropdownProps) { const dropdownRef = useRef(null); const triggerRef = useRef(null); @@ -80,6 +85,7 @@ export default function Dropdown({ id, className, buttonClassName, isStatic, chi title={title} id={id ?? ariaId} disabled={disabled} + {...buttonProps} > {text}