From 0ed957dd77e21843b9bc60ffe2f3c6b2c3fff1c8 Mon Sep 17 00:00:00 2001 From: Jin <22962980+JYC333@users.noreply.github.com> Date: Mon, 31 Mar 2025 01:09:57 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20=F0=9F=8E=B8=20add=20week=20numbers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/public/app/widgets/buttons/calendar.ts | 86 ++++++++++++++++++++-- src/public/stylesheets/calendar.css | 31 +++++++- 2 files changed, 109 insertions(+), 8 deletions(-) diff --git a/src/public/app/widgets/buttons/calendar.ts b/src/public/app/widgets/buttons/calendar.ts index 11e9092bc..97445a507 100644 --- a/src/public/app/widgets/buttons/calendar.ts +++ b/src/public/app/widgets/buttons/calendar.ts @@ -29,7 +29,7 @@ const DROPDOWN_TPL = `
@@ -59,10 +59,13 @@ 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")]; @@ -71,9 +74,15 @@ interface DateNotesForMonth { [date: string]: string; } +interface WeekCalculationOptions { + firstWeekType: number; + minDaysInFirstWeek: number; +} + export default class CalendarWidget extends RightDropdownButtonWidget { private $month!: JQuery; private $weekHeader!: JQuery; + private $weekNumbers!: JQuery; private $monthSelect!: JQuery; private $yearSelect!: JQuery; private $next!: JQuery; @@ -82,6 +91,7 @@ export default class CalendarWidget extends RightDropdownButtonWidget { private $previousYear!: JQuery; private monthDropdown!: Dropdown; private firstDayOfWeek!: number; + private weekCalculationOptions!: WeekCalculationOptions; private activeDate: Date | null = null; private todaysDate!: Date; private date!: Date; @@ -95,8 +105,10 @@ export default class CalendarWidget extends RightDropdownButtonWidget { this.$month = this.$dropdownContent.find('[data-calendar-area="month"]'); this.$weekHeader = this.$dropdownContent.find(".calendar-week"); + this.$weekNumbers = this.$dropdownContent.find(".calendar-week-numbers"); this.manageFirstDayOfWeek(); + this.initWeekCalculation(); // Month navigation this.$monthSelect = this.$dropdownContent.find('[data-calendar-input="month"]'); @@ -187,6 +199,52 @@ export default class CalendarWidget extends RightDropdownButtonWidget { this.$weekHeader.html(localeDaysOfWeek.map((el) => `${el}`).join('')); } + initWeekCalculation() { + this.weekCalculationOptions = { + firstWeekType: options.getInt("firstWeekOfYear") || 0, + minDaysInFirstWeek: options.getInt("minDaysInFirstWeek") || 4 + }; + } + + getWeekNumber(date: Date): number { + const utcDate = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())); + + const year = utcDate.getUTCFullYear(); + const jan1 = new Date(Date.UTC(year, 0, 1)); + const jan1Day = jan1.getUTCDay(); + + let firstWeekStart = new Date(jan1); + + let dayOffset = (jan1Day - this.firstDayOfWeek + 7) % 7; + firstWeekStart.setUTCDate(firstWeekStart.getUTCDate() - dayOffset); + + switch (this.weekCalculationOptions.firstWeekType) { + case 1: { + const thursday = new Date(firstWeekStart); + const day = thursday.getUTCDay(); + const offset = (4 - day + 7) % 7; + thursday.setUTCDate(thursday.getUTCDate() + offset); + if (thursday.getUTCFullYear() < year) { + firstWeekStart.setUTCDate(firstWeekStart.getUTCDate() + 7); + } + break; + } + case 2: { + const daysInFirstWeek = 7 - dayOffset; + if (daysInFirstWeek < this.weekCalculationOptions.minDaysInFirstWeek) { + firstWeekStart.setUTCDate(firstWeekStart.getUTCDate() + 7); + } + break; + } + // case 0 is default: week containing Jan 1 + } + + const diffMillis = utcDate.getTime() - firstWeekStart.getTime(); + const diffDays = Math.floor(diffMillis / (24 * 60 * 60 * 1000)); + + return Math.floor(diffDays / 7) + 1; + } + async dropdownShown() { this.init(appContext.tabManager.getActiveContextNote()?.getOwnedLabelValue("dateNote") ?? null); } @@ -246,13 +304,19 @@ export default class CalendarWidget extends RightDropdownButtonWidget { } async createMonth() { - const month = utils.formatDateISO(this.date).substr(0, 7); + const month = utils.formatDateISO(this.date).substring(0, 7); const dateNotesForMonth: DateNotesForMonth = await server.get(`special-notes/notes-for-month/${month}`); this.$month.empty(); + this.$weekNumbers.empty(); const currentMonth = this.date.getMonth(); + const weekNumbers: Set = new Set(); + while (this.date.getMonth() === currentMonth) { + const safeDate = new Date(Date.UTC(this.date.getFullYear(), this.date.getMonth(), this.date.getDate())); + weekNumbers.add(this.getWeekNumber(safeDate)); + const $day = this.createDay(dateNotesForMonth, this.date.getDate(), this.date.getDay()); this.$month.append($day); @@ -265,14 +329,24 @@ export default class CalendarWidget extends RightDropdownButtonWidget { this.$monthSelect.text(MONTHS[this.date.getMonth()]); this.$yearSelect.val(this.date.getFullYear()); + + for (const weekNumber of weekNumbers) { + const $weekNumber = $("
") + .addClass("calendar-week-number") + .text(weekNumber); + this.$weekNumbers.append($weekNumber); + } } async entitiesReloadedEvent({ loadResults }: EventData<"entitiesReloaded">) { - if (!loadResults.getOptionNames().includes("firstDayOfWeek")) { + if (!loadResults.getOptionNames().includes("firstDayOfWeek") && + !loadResults.getOptionNames().includes("firstWeekOfYear") && + !loadResults.getOptionNames().includes("minDaysInFirstWeek")) { return; } this.manageFirstDayOfWeek(); + this.initWeekCalculation(); this.createMonth(); } } diff --git a/src/public/stylesheets/calendar.css b/src/public/stylesheets/calendar.css index e1c39e52a..85b73381e 100644 --- a/src/public/stylesheets/calendar.css +++ b/src/public/stylesheets/calendar.css @@ -35,7 +35,7 @@ padding: 0 0.5rem 0.5rem 0.5rem; } -.calendar-dropdown-widget .calendar-header > div { +.calendar-dropdown-widget .calendar-header>div { display: flex; justify-content: center; flex-grow: 1; @@ -67,7 +67,34 @@ } .calendar-dropdown-widget .calendar-header .dropdown-toggle::after { - border: unset; /* Disable the dropdown arrow */ + border: unset; + /* Disable the dropdown arrow */ +} + +.calendar-container { + display: flex; +} + +.calendar-dropdown-widget .calendar-week-numbers { + width: 30px; + border-right: 1px solid var(--main-border-color); + padding-right: 5px; + margin-right: 5px; + padding-top: 2.4rem; +} + +.calendar-dropdown-widget .calendar-week-number { + /* height: 2.4rem; */ + /* line-height: 2.4rem; */ + text-align: center; + color: var(--muted-text-color); + font-size: 0.9em; +} + +.calendar-main { + flex: 1; + display: flex; + flex-direction: column; } .calendar-dropdown-widget .calendar-week {