From 1432a1b966d0f3f47558ca2469b846eb7854f85f Mon Sep 17 00:00:00 2001 From: Jamie Brynes Date: Sun, 24 Nov 2024 19:47:30 +0000 Subject: [PATCH 1/4] refactor: replace all usages of momentjs --- plugin/src/data/dueDateFormatter.test.ts | 214 ++++++++++++++++++ plugin/src/data/dueDateFormatter.ts | 92 ++++++++ plugin/src/data/dueDateInfo.test.ts | 9 +- plugin/src/data/dueDateInfo.ts | 154 +++++++++++-- .../src/data/transformations/grouping.test.ts | 7 +- plugin/src/data/transformations/grouping.ts | 14 +- .../src/data/transformations/sorting.test.ts | 40 ++-- plugin/src/data/transformations/sorting.ts | 25 +- plugin/src/i18n/langs/en.ts | 11 + plugin/src/i18n/translation.ts | 7 + plugin/src/infra/locale.ts | 3 + plugin/src/infra/time.ts | 19 ++ plugin/src/now.ts | 3 - .../ui/createTaskModal/DueDateSelector.tsx | 25 +- plugin/src/ui/createTaskModal/index.tsx | 5 +- plugin/src/ui/query/task/TaskMetadata.tsx | 19 +- 16 files changed, 538 insertions(+), 109 deletions(-) create mode 100644 plugin/src/data/dueDateFormatter.test.ts create mode 100644 plugin/src/data/dueDateFormatter.ts create mode 100644 plugin/src/infra/locale.ts create mode 100644 plugin/src/infra/time.ts delete mode 100644 plugin/src/now.ts diff --git a/plugin/src/data/dueDateFormatter.test.ts b/plugin/src/data/dueDateFormatter.test.ts new file mode 100644 index 0000000..7c612a1 --- /dev/null +++ b/plugin/src/data/dueDateFormatter.test.ts @@ -0,0 +1,214 @@ +import { formatAsHeader, formatDueDate } from "@/data/dueDateFormatter"; +import { DueDateInfo } from "@/data/dueDateInfo"; +import { CalendarDate, ZonedDateTime } from "@internationalized/date"; +import { describe, expect, test, vi } from "vitest"; + +vi.mock("../infra/time.ts", () => { + return { + today: () => new CalendarDate(2024, 6, 1), + now: () => new ZonedDateTime(2024, 6, 1, "Etc/UTC", 0, 12), // 2024-06-01T12:00:00Z + timezone: () => "Etc/UTC", + }; +}); + +vi.mock("../infra/locale.ts", () => { + return { + locale: () => "en-US", + }; +}); + +describe("formatDueDate", () => { + type Testcase = { + description: string; + dueDate: DueDateInfo; + expected: string; + }; + + const testcases: Testcase[] = [ + { + description: "Today, no time", + dueDate: new DueDateInfo({ + recurring: false, + date: "2024-06-01", + }), + expected: "Today", + }, + { + description: "Today, with time", + dueDate: new DueDateInfo({ + recurring: false, + date: "2024-06-01", + datetime: "2024-06-01T12:00:00", + }), + expected: "Today at 12:00 PM", + }, + { + description: "Tomorrow, no time", + dueDate: new DueDateInfo({ + recurring: false, + date: "2024-06-02", + }), + expected: "Tomorrow", + }, + { + description: "Tomorrow, with time", + dueDate: new DueDateInfo({ + recurring: false, + date: "2024-06-02", + datetime: "2024-06-02T12:00:00", + }), + expected: "Tomorrow at 12:00 PM", + }, + { + description: "Next week, no time", + dueDate: new DueDateInfo({ + recurring: false, + date: "2024-06-05", + }), + expected: "Wednesday", + }, + { + description: "Next week, with time", + dueDate: new DueDateInfo({ + recurring: false, + date: "2024-06-05", + datetime: "2024-06-05T12:00:00", + }), + expected: "Wednesday at 12:00 PM", + }, + { + description: "Future date, no time", + dueDate: new DueDateInfo({ + recurring: false, + date: "2024-06-30", + }), + expected: "Jun 30", + }, + { + description: "Future date, with time", + dueDate: new DueDateInfo({ + recurring: false, + date: "2024-06-30", + datetime: "2024-06-30T12:00:00", + }), + expected: "Jun 30 at 12:00 PM", + }, + { + description: "Yesterday, no time", + dueDate: new DueDateInfo({ + recurring: false, + date: "2024-05-31", + }), + expected: "Yesterday", + }, + { + description: "Yesterday, with time", + dueDate: new DueDateInfo({ + recurring: false, + date: "2024-05-31", + datetime: "2024-05-31T12:00:00", + }), + expected: "Yesterday at 12:00 PM", + }, + { + description: "Last week, no time", + dueDate: new DueDateInfo({ + recurring: false, + date: "2024-05-29", + }), + expected: "Last Wednesday", + }, + { + description: "Last week, with time", + dueDate: new DueDateInfo({ + recurring: false, + date: "2024-05-29", + datetime: "2024-05-29T12:00:00", + }), + expected: "Last Wednesday at 12:00 PM", + }, + { + description: "Next year, no time", + dueDate: new DueDateInfo({ + recurring: false, + date: "2025-06-10", + }), + expected: "Jun 10, 2025", + }, + { + description: "Next year, with time", + dueDate: new DueDateInfo({ + recurring: false, + date: "2025-06-10", + datetime: "2025-06-10T12:00:00", + }), + expected: "Jun 10, 2025 at 12:00 PM", + }, + { + description: "Last year, no time", + dueDate: new DueDateInfo({ + recurring: false, + date: "2023-08-15", + }), + expected: "Aug 15, 2023", + }, + { + description: "Last year, with time", + dueDate: new DueDateInfo({ + recurring: false, + date: "2023-08-15", + datetime: "2023-08-15T12:00:00", + }), + expected: "Aug 15, 2023 at 12:00 PM", + }, + ]; + + for (const tc of testcases) { + test(tc.description, () => { + const actual = formatDueDate(tc.dueDate); + expect(actual).toBe(tc.expected); + }); + } +}); + +describe("formatAsHeader", () => { + type Testcase = { + description: string; + dueDate: DueDateInfo; + expected: string; + }; + + const testcases: Testcase[] = [ + { + description: "Today", + dueDate: new DueDateInfo({ + date: "2024-06-01", + recurring: false, + }), + expected: "Jun 1 ‧ Saturday ‧ Today", + }, + { + description: "Tomorrow", + dueDate: new DueDateInfo({ + date: "2024-06-02", + recurring: false, + }), + expected: "Jun 2 ‧ Sunday ‧ Tomorrow", + }, + { + description: "Other date", + dueDate: new DueDateInfo({ + date: "2024-06-03", + recurring: false, + }), + expected: "Jun 3 ‧ Monday", + }, + ]; + + for (const tc of testcases) { + test(tc.description, () => { + const actual = formatAsHeader(tc.dueDate); + expect(actual).toBe(tc.expected); + }); + } +}); diff --git a/plugin/src/data/dueDateFormatter.ts b/plugin/src/data/dueDateFormatter.ts new file mode 100644 index 0000000..0e6b820 --- /dev/null +++ b/plugin/src/data/dueDateFormatter.ts @@ -0,0 +1,92 @@ +import type { DueDateInfo } from "@/data/dueDateInfo"; +import { t } from "@/i18n"; +import { locale } from "@/infra/locale"; + +const formatStyles: Record = { + time: { + timeStyle: "short", + }, + weekday: { + weekday: "long", + }, + date: { + month: "short", + day: "numeric", + }, + dateWithYear: { + month: "short", + day: "numeric", + year: "numeric", + }, +}; + +const formatterCache: Record = {}; + +const getFormatter = (style: string): Intl.DateTimeFormat => { + if (formatterCache[style]) { + return formatterCache[style]; + } + + formatterCache[style] = new Intl.DateTimeFormat(locale(), formatStyles[style]); + return formatterCache[style]; +}; + +export const formatDueDate = (dueDate: DueDateInfo): string => { + const date = formatDate(dueDate); + + if (dueDate.hasTime()) { + const i18n = t().dates; + const time = dueDate.format(getFormatter("time")); + + return i18n.dateTime(date, time); + } + + return date; +}; + +const formatDate = (dueDate: DueDateInfo): string => { + const i18n = t().dates; + + if (dueDate.isToday()) { + return i18n.today; + } + + if (dueDate.isTomorrow()) { + return i18n.tomorrow; + } + + if (dueDate.isYesterday()) { + return i18n.yesterday; + } + + if (dueDate.isInLastWeek()) { + return i18n.lastWeekday(dueDate.format(getFormatter("weekday"))); + } + + if (dueDate.isInNextWeek()) { + return dueDate.format(getFormatter("weekday")); + } + + if (!dueDate.isCurrentYear()) { + return dueDate.format(getFormatter("dateWithYear")); + } + + return dueDate.format(getFormatter("date")); +}; + +export const formatAsHeader = (dueDate: DueDateInfo): string => { + const formatParts: string[] = [ + dueDate.format(getFormatter("date")), + dueDate.format(getFormatter("weekday")), + ]; + + const i18n = t().dates; + + if (dueDate.isToday()) { + formatParts.push(i18n.today); + } else if (dueDate.isTomorrow()) { + formatParts.push(i18n.tomorrow); + } + + return formatParts.join(" ‧ "); +}; diff --git a/plugin/src/data/dueDateInfo.test.ts b/plugin/src/data/dueDateInfo.test.ts index e45f8c9..6eb5fa0 100644 --- a/plugin/src/data/dueDateInfo.test.ts +++ b/plugin/src/data/dueDateInfo.test.ts @@ -1,10 +1,13 @@ import type { DueDate } from "@/api/domain/dueDate"; import { DueDateInfo } from "@/data/dueDateInfo"; +import { CalendarDate, ZonedDateTime } from "@internationalized/date"; import { describe, expect, it, vi } from "vitest"; -vi.mock("../now.ts", () => { +vi.mock("../infra/time.ts", () => { return { - now: () => new Date("2024-01-01T12:00:00"), + today: () => new CalendarDate(2024, 1, 1), + now: () => new ZonedDateTime(2024, 1, 1, "Etc/UTC", 0, 12), // 2024-01-01T12:00:00Z + timezone: () => "Etc/UTC", }; }); @@ -237,3 +240,5 @@ describe("isTomorrow", () => { }); } }); + +// TODO: Write tests for other is* methods. diff --git a/plugin/src/data/dueDateInfo.ts b/plugin/src/data/dueDateInfo.ts index e94a5fb..a0a7bad 100644 --- a/plugin/src/data/dueDateInfo.ts +++ b/plugin/src/data/dueDateInfo.ts @@ -1,59 +1,171 @@ import type { DueDate } from "@/api/domain/dueDate"; -import { now } from "@/now"; -import moment from "moment"; +import { now, timezone, today } from "@/infra/time"; +import { CalendarDate, ZonedDateTime, parseAbsolute, parseDate } from "@internationalized/date"; export class DueDateInfo { - private m: moment.Moment | undefined; - private isDateTime = false; + private inner: ZonedDateTime | CalendarDate | undefined; constructor(dueDate: DueDate | undefined) { - if (dueDate !== undefined) { - this.m = moment(dueDate.datetime ?? dueDate.date); - this.isDateTime = dueDate.datetime !== undefined; + if (dueDate === undefined) { + this.inner = undefined; + } else { + if (dueDate.datetime !== undefined) { + // Todoist's datetime comes as a UTC timezone, but without the trailing 'Z'. + // So we just patch it in and carry on our merry way. + this.inner = parseAbsolute(`${dueDate.datetime}Z`, timezone()); + } else { + this.inner = parseDate(dueDate.date); + } } } hasDueDate(): boolean { - return this.m !== undefined; + return this.inner !== undefined; } hasTime(): boolean { - return this.isDateTime; + return this.inner instanceof ZonedDateTime; } isToday(): boolean { - if (this.m === undefined) { + const date = this.calendarDate(); + + if (date === undefined) { return false; } - return this.m.isSame(now(), "day"); + return date.compare(today()) === 0; } isOverdue(): boolean { - if (this.m === undefined) { + if (this.inner === undefined) { return false; } - if (this.isDateTime) { - return this.m.isBefore(now()); + if (this.inner instanceof CalendarDate) { + return this.inner.compare(today()) < 0; } - return this.m.isBefore(now(), "day"); + return this.inner.compare(now()) < 0; } isTomorrow(): boolean { - if (this.m === undefined) { + const date = this.calendarDate(); + + if (date === undefined) { + return false; + } + + return date.compare(today().add({ days: 1 })) === 0; + } + + isYesterday(): boolean { + const date = this.calendarDate(); + + if (date === undefined) { + return false; + } + + return date.compare(today().add({ days: -1 })) === 0; + } + + isInLastWeek(): boolean { + const date = this.calendarDate(); + + if (date === undefined) { return false; } - return this.m.clone().add(-1, "day").isSame(now(), "day"); + return date.compare(today().add({ days: -7 })) >= 0 && date.compare(today()) < 0; + } + + isInNextWeek(): boolean { + const date = this.calendarDate(); + + if (date === undefined) { + return false; + } + + return date.compare(today().add({ days: 7 })) <= 0 && date.compare(today()) > 0; + } + + isCurrentYear(): boolean { + const date = this.calendarDate(); + + if (date === undefined) { + return false; + } + + return date.year === today().year; + } + + format(formatter: Intl.DateTimeFormat): string { + return formatter.format(this.naiveDate()); + } + + compareDate(other: DueDateInfo): -1 | 0 | 1 { + const thisDate = this.calendarDate(); + const otherDate = other.calendarDate(); + + if (thisDate === undefined || otherDate === undefined) { + throw new Error("Called compareDate() on a due date with no due date"); + } + + const cmp = thisDate.compare(otherDate); + + if (cmp === 0) { + return 0; + } + + if (cmp < 0) { + return -1; + } + + return 1; + } + + compareDateTime(other: DueDateInfo): -1 | 0 | 1 { + if (this.inner === undefined || other.inner === undefined) { + throw new Error("Called compareDateTime on due date with no due date"); + } + + if (!(this.inner instanceof ZonedDateTime) || !(other.inner instanceof ZonedDateTime)) { + throw new Error("Called compareDateTime on due dates without time"); + } + + const cmp = this.inner.compare(other.inner); + if (cmp === 0) { + return 0; + } + + if (cmp < 0) { + return -1; + } + + return 1; + } + + private naiveDate(): Date { + if (this.inner === undefined) { + throw new Error("Called naiveDate() on a due date with no due date"); + } + + if (this.inner instanceof CalendarDate) { + return this.inner.toDate(timezone()); + } + + return this.inner.toDate(); } - moment(): moment.Moment { - if (this.m === undefined) { - throw Error("Cannot get moment from an empty due date"); + private calendarDate(): CalendarDate | undefined { + if (this.inner === undefined) { + return undefined; + } + + if (this.inner instanceof CalendarDate) { + return this.inner; } - return this.m; + return new CalendarDate(this.inner.year, this.inner.month, this.inner.day); } } diff --git a/plugin/src/data/transformations/grouping.test.ts b/plugin/src/data/transformations/grouping.test.ts index c581e0b..e87854b 100644 --- a/plugin/src/data/transformations/grouping.test.ts +++ b/plugin/src/data/transformations/grouping.test.ts @@ -5,11 +5,14 @@ import type { Section } from "@/api/domain/section"; import type { Task } from "@/data/task"; import { type GroupedTasks, groupBy } from "@/data/transformations/grouping"; import { GroupVariant } from "@/query/query"; +import { CalendarDate, ZonedDateTime } from "@internationalized/date"; import { describe, expect, it, vi } from "vitest"; -vi.mock("../../now.ts", () => { +vi.mock("../../infra/time.ts", () => { return { - now: () => new Date("2024-01-01T12:00:00"), + today: () => new CalendarDate(2024, 1, 1), + now: () => new ZonedDateTime(2024, 1, 1, "Etc/UTC", 0, 12), // 2024-01-01T12:00:00Z + timezone: () => "Etc/UTC", }; }); diff --git a/plugin/src/data/transformations/grouping.ts b/plugin/src/data/transformations/grouping.ts index 4d86432..1dd695e 100644 --- a/plugin/src/data/transformations/grouping.ts +++ b/plugin/src/data/transformations/grouping.ts @@ -1,6 +1,7 @@ import type { Project } from "@/api/domain/project"; import type { Section } from "@/api/domain/section"; import type { Priority } from "@/api/domain/task"; +import { formatAsHeader } from "@/data/dueDateFormatter"; import { DueDateInfo } from "@/data/dueDateInfo"; import type { Task } from "@/data/task"; import { GroupVariant } from "@/query/query"; @@ -123,6 +124,7 @@ function groupBySection(tasks: Task[]): GroupedTasks[] { } function groupByDate(tasks: Task[]): GroupedTasks[] { + // TODO: Localize const makeHeader = (date: string | undefined): string => { if (date === undefined) { return "No due date"; @@ -132,17 +134,7 @@ function groupByDate(tasks: Task[]): GroupedTasks[] { return "Overdue"; } - const due = new DueDateInfo({ recurring: false, date }); - const m = due.moment(); - - const parts = [m.format("MMM D"), m.format("dddd")]; - if (due.isToday()) { - parts.push("Today"); - } else if (due.isTomorrow()) { - parts.push("Tomorrow"); - } - - return parts.join(" ‧ "); + return formatAsHeader(new DueDateInfo({ recurring: false, date })); }; const dates = partitionBy(tasks, (task: Task) => { diff --git a/plugin/src/data/transformations/sorting.test.ts b/plugin/src/data/transformations/sorting.test.ts index 0d0da94..c6220ee 100644 --- a/plugin/src/data/transformations/sorting.test.ts +++ b/plugin/src/data/transformations/sorting.test.ts @@ -104,14 +104,14 @@ describe("sortTasks", () => { makeTask("d", { due: { recurring: false, - date: "2020-03-20", + date: "2020-03-15", datetime: "2020-03-15T15:00:00", }, }), makeTask("e", { due: { recurring: false, - date: "2020-03-20", + date: "2020-03-15", datetime: "2020-03-15T13:00:00", }, }), @@ -121,14 +121,14 @@ describe("sortTasks", () => { makeTask("e", { due: { recurring: false, - date: "2020-03-20", + date: "2020-03-15", datetime: "2020-03-15T13:00:00", }, }), makeTask("d", { due: { recurring: false, - date: "2020-03-20", + date: "2020-03-15", datetime: "2020-03-15T15:00:00", }, }), @@ -153,14 +153,14 @@ describe("sortTasks", () => { makeTask("e", { due: { recurring: false, - date: "2020-03-20", + date: "2020-03-15", datetime: "2020-03-15T13:00:00", }, }), makeTask("d", { due: { recurring: false, - date: "2020-03-20", + date: "2020-03-15", datetime: "2020-03-15T15:00:00", }, }), @@ -196,14 +196,14 @@ describe("sortTasks", () => { makeTask("d", { due: { recurring: false, - date: "2020-03-20", + date: "2020-03-15", datetime: "2020-03-15T15:00:00", }, }), makeTask("e", { due: { recurring: false, - date: "2020-03-20", + date: "2020-03-15", datetime: "2020-03-15T13:00:00", }, }), @@ -212,29 +212,29 @@ describe("sortTasks", () => { { description: "can sort by dateAdded", input: [ - makeTask("a", { createdAt: "2020-03-03T13:00:00" }), - makeTask("b", { createdAt: "2020-03-02T11:00:00" }), - makeTask("c", { createdAt: "2020-03-02T12:00:00" }), + makeTask("a", { createdAt: "2020-03-03T13:00:00Z" }), + makeTask("b", { createdAt: "2020-03-02T11:00:00Z" }), + makeTask("c", { createdAt: "2020-03-02T12:00:00Z" }), ], sortingOpts: [SortingVariant.DateAdded], expectedOutput: [ - makeTask("b", { createdAt: "2020-03-02T11:00:00" }), - makeTask("c", { createdAt: "2020-03-02T12:00:00" }), - makeTask("a", { createdAt: "2020-03-03T13:00:00" }), + makeTask("b", { createdAt: "2020-03-02T11:00:00Z" }), + makeTask("c", { createdAt: "2020-03-02T12:00:00Z" }), + makeTask("a", { createdAt: "2020-03-03T13:00:00Z" }), ], }, { description: "can sort by dateAddedDescending", input: [ - makeTask("a", { createdAt: "2020-03-02T11:00:00" }), - makeTask("b", { createdAt: "2020-03-03T13:00:00" }), - makeTask("c", { createdAt: "2020-03-02T12:00:00" }), + makeTask("a", { createdAt: "2020-03-02T11:00:00Z" }), + makeTask("b", { createdAt: "2020-03-03T13:00:00Z" }), + makeTask("c", { createdAt: "2020-03-02T12:00:00Z" }), ], sortingOpts: [SortingVariant.DateAddedDescending], expectedOutput: [ - makeTask("b", { createdAt: "2020-03-03T13:00:00" }), - makeTask("c", { createdAt: "2020-03-02T12:00:00" }), - makeTask("a", { createdAt: "2020-03-02T11:00:00" }), + makeTask("b", { createdAt: "2020-03-03T13:00:00Z" }), + makeTask("c", { createdAt: "2020-03-02T12:00:00Z" }), + makeTask("a", { createdAt: "2020-03-02T11:00:00Z" }), ], }, { diff --git a/plugin/src/data/transformations/sorting.ts b/plugin/src/data/transformations/sorting.ts index 47c7cad..5701c96 100644 --- a/plugin/src/data/transformations/sorting.ts +++ b/plugin/src/data/transformations/sorting.ts @@ -1,7 +1,7 @@ import type { Task } from "@/data//task"; import { DueDateInfo } from "@/data/dueDateInfo"; import { SortingVariant } from "@/query/query"; -import moment from "moment"; +import { parseAbsoluteToLocal } from "@internationalized/date"; export function sortTasks(tasks: T[], sort: SortingVariant[]) { tasks.sort((first, second) => { @@ -63,17 +63,12 @@ function compareTaskDate(self: T, other: T): number { return 0; } - const selfDate = selfInfo.moment(); - const otherDate = otherInfo.moment(); - - if (selfDate === undefined || otherDate === undefined) { - throw Error("Found unexpected missing moment date"); - } + const dateCmp = selfInfo.compareDate(otherInfo); // Then lets check if we are the same day, if not // sort just based on the day. - if (!selfDate.isSame(otherDate, "day")) { - return selfDate.isBefore(otherDate, "day") ? -1 : 1; + if (dateCmp !== 0) { + return dateCmp; } if (selfInfo.hasTime() && !otherInfo.hasTime()) { @@ -88,16 +83,12 @@ function compareTaskDate(self: T, other: T): number { return 0; } - return selfDate.isBefore(otherDate) ? -1 : 1; + return selfInfo.compareDateTime(otherInfo); } function compareTaskDateAdded(self: T, other: T): number { - const selfDate = moment(self.createdAt); - const otherDate = moment(other.createdAt); - - if (selfDate === otherDate) { - return 0; - } + const selfDate = parseAbsoluteToLocal(self.createdAt); + const otherDate = parseAbsoluteToLocal(other.createdAt); - return selfDate.isBefore(otherDate) ? -1 : 1; + return selfDate.compare(otherDate) < 0 ? -1 : 1; } diff --git a/plugin/src/i18n/langs/en.ts b/plugin/src/i18n/langs/en.ts index ea72e60..e21849b 100644 --- a/plugin/src/i18n/langs/en.ts +++ b/plugin/src/i18n/langs/en.ts @@ -184,4 +184,15 @@ export const en: Translations = { invalidTokenError: "Oops! Todoist does not recognize this token. Please double check and try again!", }, + dates: { + today: "Today", + tomorrow: "Tomorrow", + yesterday: "Yesterday", + lastWeekday: (weekday: string) => { + return `Last ${weekday}`; + }, + dateTime: (date: string, time: string) => { + return `${date} at ${time}`; + }, + }, }; diff --git a/plugin/src/i18n/translation.ts b/plugin/src/i18n/translation.ts index 0a2abde..f5a4933 100644 --- a/plugin/src/i18n/translation.ts +++ b/plugin/src/i18n/translation.ts @@ -171,4 +171,11 @@ export type Translations = { emptyTokenError: string; invalidTokenError: string; }; + dates: { + today: string; + tomorrow: string; + yesterday: string; + lastWeekday: (weekday: string) => string; + dateTime: (date: string, time: string) => string; + }; }; diff --git a/plugin/src/infra/locale.ts b/plugin/src/infra/locale.ts new file mode 100644 index 0000000..8ecc2da --- /dev/null +++ b/plugin/src/infra/locale.ts @@ -0,0 +1,3 @@ +export const locale = (): string => { + return new Intl.DateTimeFormat(undefined).resolvedOptions().locale; +}; diff --git a/plugin/src/infra/time.ts b/plugin/src/infra/time.ts new file mode 100644 index 0000000..54029a2 --- /dev/null +++ b/plugin/src/infra/time.ts @@ -0,0 +1,19 @@ +import { + type CalendarDate, + type ZonedDateTime, + getLocalTimeZone, + now as realNow, + today as realToday, +} from "@internationalized/date"; + +export const today = (): CalendarDate => { + return realToday(getLocalTimeZone()); +}; + +export const now = (): ZonedDateTime => { + return realNow(getLocalTimeZone()); +}; + +export const timezone = (): string => { + return getLocalTimeZone(); +}; diff --git a/plugin/src/now.ts b/plugin/src/now.ts deleted file mode 100644 index 850bf60..0000000 --- a/plugin/src/now.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const now = () => { - return new Date(); -}; diff --git a/plugin/src/ui/createTaskModal/DueDateSelector.tsx b/plugin/src/ui/createTaskModal/DueDateSelector.tsx index bc7cf14..8baeab9 100644 --- a/plugin/src/ui/createTaskModal/DueDateSelector.tsx +++ b/plugin/src/ui/createTaskModal/DueDateSelector.tsx @@ -1,10 +1,10 @@ import { t } from "@/i18n"; +import { timezone } from "@/infra/time"; import { type CalendarDate, DateFormatter, type Time, endOfWeek, - getLocalTimeZone, isToday, toCalendarDateTime, today, @@ -84,7 +84,7 @@ export const DueDateSelector: React.FC = ({ selected, setSelected }) => { if (selected === undefined) { if (time !== undefined) { setSelected({ - date: today(getLocalTimeZone()), + date: today(timezone()), time, }); } @@ -130,7 +130,7 @@ export const DueDateSelector: React.FC = ({ selected, setSelected }) => { selectDate(date); close(); }} - minValue={today(getLocalTimeZone())} + minValue={today(timezone())} >
@@ -176,15 +176,15 @@ const getLabel = (selected: DueDate | undefined) => { const date = selected.date; const dayPart = (() => { - if (isToday(date, getLocalTimeZone())) { + if (isToday(date, timezone())) { return i18n.today; } - if (today(getLocalTimeZone()).add({ days: 1 }).compare(date) === 0) { + if (today(timezone()).add({ days: 1 }).compare(date) === 0) { return i18n.tomorrow; } - return formatter.format(date.toDate(getLocalTimeZone())); + return formatter.format(date.toDate(timezone())); })(); const time = selected.time; @@ -193,9 +193,7 @@ const getLabel = (selected: DueDate | undefined) => { return ""; } - return timeFormatter.format( - toCalendarDateTime(today(getLocalTimeZone()), time).toDate(getLocalTimeZone()), - ); + return timeFormatter.format(toCalendarDateTime(today(timezone()), time).toDate(timezone())); })(); return [dayPart, timePart].join(" ").trimEnd(); @@ -209,8 +207,7 @@ type DateSuggestionProps = { }; const DateSuggestion: React.FC = ({ id, icon, label, target }) => { - const dayOfWeek = - target !== undefined ? weekdayFormatter.format(target.toDate(getLocalTimeZone())) : ""; + const dayOfWeek = target !== undefined ? weekdayFormatter.format(target.toDate(timezone())) : ""; return ( @@ -228,19 +225,19 @@ const DateSuggestion: React.FC = ({ id, icon, label, target const getSuggestions = (): DateSuggestionProps[] => { const i18n = t().createTaskModal.dateSelector; - const startOfNextWeek = endOfWeek(today(getLocalTimeZone()), "en-US").add({ days: 1 }); + const startOfNextWeek = endOfWeek(today(timezone()), "en-US").add({ days: 1 }); const suggestions = [ { id: "today", icon: "calendar", label: i18n.today, - target: today(getLocalTimeZone()), + target: today(timezone()), }, { id: "tomorrow", icon: "sun", label: i18n.tomorrow, - target: today(getLocalTimeZone()).add({ days: 1 }), + target: today(timezone()).add({ days: 1 }), }, { id: "next-week", diff --git a/plugin/src/ui/createTaskModal/index.tsx b/plugin/src/ui/createTaskModal/index.tsx index bd21071..ef1d174 100644 --- a/plugin/src/ui/createTaskModal/index.tsx +++ b/plugin/src/ui/createTaskModal/index.tsx @@ -1,7 +1,8 @@ import { t } from "@/i18n"; +import { timezone } from "@/infra/time"; import { useSettingsStore } from "@/settings"; import { ModalContext, PluginContext } from "@/ui/context"; -import { getLocalTimeZone, toCalendarDateTime, toZoned } from "@internationalized/date"; +import { toCalendarDateTime, toZoned } from "@internationalized/date"; import { Notice, type TFile } from "obsidian"; import type React from "react"; import { useEffect, useState } from "react"; @@ -113,7 +114,7 @@ const CreateTaskModalContent: React.FC = ({ if (dueDate.time !== undefined) { params.dueDatetime = toZoned( toCalendarDateTime(dueDate.date, dueDate.time), - getLocalTimeZone(), + timezone(), ).toAbsoluteString(); } else { params.dueDate = dueDate.date.toString(); diff --git a/plugin/src/ui/query/task/TaskMetadata.tsx b/plugin/src/ui/query/task/TaskMetadata.tsx index 006f8e4..a337188 100644 --- a/plugin/src/ui/query/task/TaskMetadata.tsx +++ b/plugin/src/ui/query/task/TaskMetadata.tsx @@ -1,9 +1,9 @@ +import { formatDueDate } from "@/data/dueDateFormatter"; import { DueDateInfo } from "@/data/dueDateInfo"; import type { Task } from "@/data/task"; import { type Query, ShowMetadataVariant } from "@/query/query"; import type { Settings } from "@/settings"; import { ObsidianIcon } from "@/ui/components/obsidian-icon"; -import type { CalendarSpec } from "moment"; import type React from "react"; type MetadataContent = { @@ -79,22 +79,7 @@ const dateLabel = (task: Task): string => { return ""; } - const m = info.moment(); - - if (info.hasTime()) { - return m.calendar(); - } - - return m.calendar(dateOnlyCalendarSpec); -}; - -const dateOnlyCalendarSpec: CalendarSpec = { - sameDay: "[Today]", - nextDay: "[Tomorrow]", - nextWeek: "dddd", - lastDay: "[Yesterday]", - lastWeek: "[Last] dddd", - sameElse: "MMM Do", + return formatDueDate(info); }; type TaskMetadataProps = { From 805953d4d01e0c26b59715e23185d28ab805dfc1 Mon Sep 17 00:00:00 2001 From: Jamie Brynes Date: Sun, 24 Nov 2024 19:48:39 +0000 Subject: [PATCH 2/4] refactor: remove momentjs package --- plugin/package-lock.json | 9 --------- plugin/package.json | 1 - 2 files changed, 10 deletions(-) diff --git a/plugin/package-lock.json b/plugin/package-lock.json index d461d29..6554e22 100644 --- a/plugin/package-lock.json +++ b/plugin/package-lock.json @@ -13,7 +13,6 @@ "camelize-ts": "^3.0.0", "classnames": "^2.5.1", "framer-motion": "^11.1.8", - "moment": "^2.29.4", "obsidian": "0.15", "react": "^18.2.0", "react-aria-components": "^1.1.1", @@ -4354,14 +4353,6 @@ "ufo": "^1.3.2" } }, - "node_modules/moment": { - "version": "2.30.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", - "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", - "engines": { - "node": "*" - } - }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", diff --git a/plugin/package.json b/plugin/package.json index 0fd022c..05742c6 100644 --- a/plugin/package.json +++ b/plugin/package.json @@ -17,7 +17,6 @@ "camelize-ts": "^3.0.0", "classnames": "^2.5.1", "framer-motion": "^11.1.8", - "moment": "^2.29.4", "obsidian": "0.15", "react": "^18.2.0", "react-aria-components": "^1.1.1", From 3cb2a7c399e3f25db815e593e84609a0559537d3 Mon Sep 17 00:00:00 2001 From: Jamie Brynes Date: Sun, 24 Nov 2024 20:17:18 +0000 Subject: [PATCH 3/4] refactor: remove possibility of undefined inner DueDateInfo --- plugin/src/data/dueDateInfo.test.ts | 57 +--------------- plugin/src/data/dueDateInfo.ts | 75 +++------------------- plugin/src/data/transformations/sorting.ts | 20 +++--- plugin/src/ui/query/task/Task.tsx | 10 ++- plugin/src/ui/query/task/TaskMetadata.tsx | 5 +- 5 files changed, 31 insertions(+), 136 deletions(-) diff --git a/plugin/src/data/dueDateInfo.test.ts b/plugin/src/data/dueDateInfo.test.ts index 6eb5fa0..21dc3aa 100644 --- a/plugin/src/data/dueDateInfo.test.ts +++ b/plugin/src/data/dueDateInfo.test.ts @@ -13,52 +13,12 @@ vi.mock("../infra/time.ts", () => { type TestCase = { description: string; - input: DueDate | undefined; + input: DueDate; expected: boolean; }; -describe("hasDueDate", () => { - const testcases: TestCase[] = [ - { - description: "should be false for empty due date", - input: undefined, - expected: false, - }, - { - description: "should be true for just date", - input: { - recurring: false, - date: "2024-01-01", - }, - expected: true, - }, - { - description: "should be true for datetime", - input: { - recurring: false, - date: "2024-01-01", - datetime: "2024-01-01T12:00:00", - }, - expected: true, - }, - ]; - - for (const tc of testcases) { - it(tc.description, () => { - const info = new DueDateInfo(tc.input); - const hasDueDate = info.hasDueDate(); - expect(hasDueDate).toEqual(tc.expected); - }); - } -}); - describe("hasTime", () => { const testcases: TestCase[] = [ - { - description: "should be false for empty due date", - input: undefined, - expected: false, - }, { description: "should be false for just date", input: { @@ -89,11 +49,6 @@ describe("hasTime", () => { describe("isToday", () => { const testcases: TestCase[] = [ - { - description: "should be false for empty due date", - input: undefined, - expected: false, - }, { description: "should be false for different day", input: { @@ -132,11 +87,6 @@ describe("isToday", () => { describe("isOverdue", () => { const testcases: TestCase[] = [ - { - description: "should be false for empty due date", - input: undefined, - expected: false, - }, { description: "should be false if date in future", input: { @@ -192,11 +142,6 @@ describe("isOverdue", () => { describe("isTomorrow", () => { const testcases: TestCase[] = [ - { - description: "should be false for empty due date", - input: undefined, - expected: false, - }, { description: "should be false for same day", input: { diff --git a/plugin/src/data/dueDateInfo.ts b/plugin/src/data/dueDateInfo.ts index a0a7bad..e73c8b2 100644 --- a/plugin/src/data/dueDateInfo.ts +++ b/plugin/src/data/dueDateInfo.ts @@ -3,45 +3,28 @@ import { now, timezone, today } from "@/infra/time"; import { CalendarDate, ZonedDateTime, parseAbsolute, parseDate } from "@internationalized/date"; export class DueDateInfo { - private inner: ZonedDateTime | CalendarDate | undefined; + private inner: ZonedDateTime | CalendarDate; - constructor(dueDate: DueDate | undefined) { - if (dueDate === undefined) { - this.inner = undefined; + constructor(dueDate: DueDate) { + if (dueDate.datetime !== undefined) { + // Todoist's datetime comes as a UTC timezone, but without the trailing 'Z'. + // So we just patch it in and carry on our merry way. + this.inner = parseAbsolute(`${dueDate.datetime}Z`, timezone()); } else { - if (dueDate.datetime !== undefined) { - // Todoist's datetime comes as a UTC timezone, but without the trailing 'Z'. - // So we just patch it in and carry on our merry way. - this.inner = parseAbsolute(`${dueDate.datetime}Z`, timezone()); - } else { - this.inner = parseDate(dueDate.date); - } + this.inner = parseDate(dueDate.date); } } - hasDueDate(): boolean { - return this.inner !== undefined; - } - hasTime(): boolean { return this.inner instanceof ZonedDateTime; } isToday(): boolean { const date = this.calendarDate(); - - if (date === undefined) { - return false; - } - return date.compare(today()) === 0; } isOverdue(): boolean { - if (this.inner === undefined) { - return false; - } - if (this.inner instanceof CalendarDate) { return this.inner.compare(today()) < 0; } @@ -51,51 +34,26 @@ export class DueDateInfo { isTomorrow(): boolean { const date = this.calendarDate(); - - if (date === undefined) { - return false; - } - return date.compare(today().add({ days: 1 })) === 0; } isYesterday(): boolean { const date = this.calendarDate(); - - if (date === undefined) { - return false; - } - return date.compare(today().add({ days: -1 })) === 0; } isInLastWeek(): boolean { const date = this.calendarDate(); - - if (date === undefined) { - return false; - } - return date.compare(today().add({ days: -7 })) >= 0 && date.compare(today()) < 0; } isInNextWeek(): boolean { const date = this.calendarDate(); - - if (date === undefined) { - return false; - } - return date.compare(today().add({ days: 7 })) <= 0 && date.compare(today()) > 0; } isCurrentYear(): boolean { const date = this.calendarDate(); - - if (date === undefined) { - return false; - } - return date.year === today().year; } @@ -106,11 +64,6 @@ export class DueDateInfo { compareDate(other: DueDateInfo): -1 | 0 | 1 { const thisDate = this.calendarDate(); const otherDate = other.calendarDate(); - - if (thisDate === undefined || otherDate === undefined) { - throw new Error("Called compareDate() on a due date with no due date"); - } - const cmp = thisDate.compare(otherDate); if (cmp === 0) { @@ -125,10 +78,6 @@ export class DueDateInfo { } compareDateTime(other: DueDateInfo): -1 | 0 | 1 { - if (this.inner === undefined || other.inner === undefined) { - throw new Error("Called compareDateTime on due date with no due date"); - } - if (!(this.inner instanceof ZonedDateTime) || !(other.inner instanceof ZonedDateTime)) { throw new Error("Called compareDateTime on due dates without time"); } @@ -146,10 +95,6 @@ export class DueDateInfo { } private naiveDate(): Date { - if (this.inner === undefined) { - throw new Error("Called naiveDate() on a due date with no due date"); - } - if (this.inner instanceof CalendarDate) { return this.inner.toDate(timezone()); } @@ -157,11 +102,7 @@ export class DueDateInfo { return this.inner.toDate(); } - private calendarDate(): CalendarDate | undefined { - if (this.inner === undefined) { - return undefined; - } - + private calendarDate(): CalendarDate { if (this.inner instanceof CalendarDate) { return this.inner; } diff --git a/plugin/src/data/transformations/sorting.ts b/plugin/src/data/transformations/sorting.ts index 5701c96..c18f233 100644 --- a/plugin/src/data/transformations/sorting.ts +++ b/plugin/src/data/transformations/sorting.ts @@ -48,21 +48,23 @@ function compareTaskDate(self: T, other: T): number { // 1. Any items without a due date are always after those with. // 2. Any items on the same day, but without time are always sorted after those with time. - const selfInfo = new DueDateInfo(self.due); - const otherInfo = new DueDateInfo(other.due); - - if (selfInfo.hasDueDate() && !otherInfo.hasDueDate()) { - return -1; - } + if (self.due === undefined) { + if (other.due === undefined) { + return 0; + } - if (!selfInfo.hasDueDate() && otherInfo.hasDueDate()) { + // Self doesn't have date, but other does return 1; } - if (!selfInfo.hasDueDate() && !otherInfo.hasDueDate()) { - return 0; + // Self has date, but other doesn't + if (other.due === undefined) { + return -1; } + const selfInfo = new DueDateInfo(self.due); + const otherInfo = new DueDateInfo(other.due); + const dateCmp = selfInfo.compareDate(otherInfo); // Then lets check if we are the same day, if not diff --git a/plugin/src/ui/query/task/Task.tsx b/plugin/src/ui/query/task/Task.tsx index 7399023..d04a3de 100644 --- a/plugin/src/ui/query/task/Task.tsx +++ b/plugin/src/ui/query/task/Task.tsx @@ -77,6 +77,10 @@ export const Task: React.FC = ({ tree }) => { }; function getDueMetadataInfo(task: TaskTree): string | undefined { + if (task.due === undefined) { + return undefined; + } + const info = new DueDateInfo(task.due); if (info.isOverdue()) { @@ -92,7 +96,11 @@ function getDueMetadataInfo(task: TaskTree): string | undefined { return undefined; } -function getTimeMetadataInfo(task: TaskTree): boolean { +function getTimeMetadataInfo(task: TaskTree): boolean | undefined { + if (task.due === undefined) { + return undefined; + } + return new DueDateInfo(task.due).hasTime(); } diff --git a/plugin/src/ui/query/task/TaskMetadata.tsx b/plugin/src/ui/query/task/TaskMetadata.tsx index a337188..5c7cefb 100644 --- a/plugin/src/ui/query/task/TaskMetadata.tsx +++ b/plugin/src/ui/query/task/TaskMetadata.tsx @@ -74,12 +74,11 @@ const metadata: MetadataDefinition[] = [ ]; const dateLabel = (task: Task): string => { - const info = new DueDateInfo(task.due); - if (!info.hasDueDate()) { + if (task.due === undefined) { return ""; } - return formatDueDate(info); + return formatDueDate(new DueDateInfo(task.due)); }; type TaskMetadataProps = { From 31c6c7808788df1a246db62a32f52ed5bc77abda Mon Sep 17 00:00:00 2001 From: Jamie Brynes Date: Sun, 24 Nov 2024 20:19:56 +0000 Subject: [PATCH 4/4] refactor: rename DueDateInfo to DueDate --- .../{dueDateInfo.test.ts => dueDate.test.ts} | 14 +++--- .../src/data/{dueDateInfo.ts => dueDate.ts} | 10 ++--- plugin/src/data/dueDateFormatter.test.ts | 44 +++++++++---------- plugin/src/data/dueDateFormatter.ts | 8 ++-- plugin/src/data/transformations/grouping.ts | 6 +-- plugin/src/data/transformations/sorting.ts | 6 +-- plugin/src/ui/query/task/Task.tsx | 6 +-- plugin/src/ui/query/task/TaskMetadata.tsx | 4 +- 8 files changed, 49 insertions(+), 49 deletions(-) rename plugin/src/data/{dueDateInfo.test.ts => dueDate.test.ts} (92%) rename plugin/src/data/{dueDateInfo.ts => dueDate.ts} (92%) diff --git a/plugin/src/data/dueDateInfo.test.ts b/plugin/src/data/dueDate.test.ts similarity index 92% rename from plugin/src/data/dueDateInfo.test.ts rename to plugin/src/data/dueDate.test.ts index 21dc3aa..dc8ef51 100644 --- a/plugin/src/data/dueDateInfo.test.ts +++ b/plugin/src/data/dueDate.test.ts @@ -1,5 +1,5 @@ -import type { DueDate } from "@/api/domain/dueDate"; -import { DueDateInfo } from "@/data/dueDateInfo"; +import type { DueDate as ApiDueDate } from "@/api/domain/dueDate"; +import { DueDate } from "@/data/dueDate"; import { CalendarDate, ZonedDateTime } from "@internationalized/date"; import { describe, expect, it, vi } from "vitest"; @@ -13,7 +13,7 @@ vi.mock("../infra/time.ts", () => { type TestCase = { description: string; - input: DueDate; + input: ApiDueDate; expected: boolean; }; @@ -40,7 +40,7 @@ describe("hasTime", () => { for (const tc of testcases) { it(tc.description, () => { - const info = new DueDateInfo(tc.input); + const info = new DueDate(tc.input); const hasTime = info.hasTime(); expect(hasTime).toEqual(tc.expected); }); @@ -78,7 +78,7 @@ describe("isToday", () => { for (const tc of testcases) { it(tc.description, () => { - const info = new DueDateInfo(tc.input); + const info = new DueDate(tc.input); const isToday = info.isToday(); expect(isToday).toEqual(tc.expected); }); @@ -133,7 +133,7 @@ describe("isOverdue", () => { for (const tc of testcases) { it(tc.description, () => { - const info = new DueDateInfo(tc.input); + const info = new DueDate(tc.input); const isOverdue = info.isOverdue(); expect(isOverdue).toEqual(tc.expected); }); @@ -179,7 +179,7 @@ describe("isTomorrow", () => { for (const tc of testcases) { it(tc.description, () => { - const info = new DueDateInfo(tc.input); + const info = new DueDate(tc.input); const isTomorrow = info.isTomorrow(); expect(isTomorrow).toEqual(tc.expected); }); diff --git a/plugin/src/data/dueDateInfo.ts b/plugin/src/data/dueDate.ts similarity index 92% rename from plugin/src/data/dueDateInfo.ts rename to plugin/src/data/dueDate.ts index e73c8b2..6a45d7b 100644 --- a/plugin/src/data/dueDateInfo.ts +++ b/plugin/src/data/dueDate.ts @@ -1,11 +1,11 @@ -import type { DueDate } from "@/api/domain/dueDate"; +import type { DueDate as ApiDueDate } from "@/api/domain/dueDate"; import { now, timezone, today } from "@/infra/time"; import { CalendarDate, ZonedDateTime, parseAbsolute, parseDate } from "@internationalized/date"; -export class DueDateInfo { +export class DueDate { private inner: ZonedDateTime | CalendarDate; - constructor(dueDate: DueDate) { + constructor(dueDate: ApiDueDate) { if (dueDate.datetime !== undefined) { // Todoist's datetime comes as a UTC timezone, but without the trailing 'Z'. // So we just patch it in and carry on our merry way. @@ -61,7 +61,7 @@ export class DueDateInfo { return formatter.format(this.naiveDate()); } - compareDate(other: DueDateInfo): -1 | 0 | 1 { + compareDate(other: DueDate): -1 | 0 | 1 { const thisDate = this.calendarDate(); const otherDate = other.calendarDate(); const cmp = thisDate.compare(otherDate); @@ -77,7 +77,7 @@ export class DueDateInfo { return 1; } - compareDateTime(other: DueDateInfo): -1 | 0 | 1 { + compareDateTime(other: DueDate): -1 | 0 | 1 { if (!(this.inner instanceof ZonedDateTime) || !(other.inner instanceof ZonedDateTime)) { throw new Error("Called compareDateTime on due dates without time"); } diff --git a/plugin/src/data/dueDateFormatter.test.ts b/plugin/src/data/dueDateFormatter.test.ts index 7c612a1..1895c4b 100644 --- a/plugin/src/data/dueDateFormatter.test.ts +++ b/plugin/src/data/dueDateFormatter.test.ts @@ -1,5 +1,5 @@ +import { DueDate } from "@/data/dueDate"; import { formatAsHeader, formatDueDate } from "@/data/dueDateFormatter"; -import { DueDateInfo } from "@/data/dueDateInfo"; import { CalendarDate, ZonedDateTime } from "@internationalized/date"; import { describe, expect, test, vi } from "vitest"; @@ -20,14 +20,14 @@ vi.mock("../infra/locale.ts", () => { describe("formatDueDate", () => { type Testcase = { description: string; - dueDate: DueDateInfo; + dueDate: DueDate; expected: string; }; const testcases: Testcase[] = [ { description: "Today, no time", - dueDate: new DueDateInfo({ + dueDate: new DueDate({ recurring: false, date: "2024-06-01", }), @@ -35,7 +35,7 @@ describe("formatDueDate", () => { }, { description: "Today, with time", - dueDate: new DueDateInfo({ + dueDate: new DueDate({ recurring: false, date: "2024-06-01", datetime: "2024-06-01T12:00:00", @@ -44,7 +44,7 @@ describe("formatDueDate", () => { }, { description: "Tomorrow, no time", - dueDate: new DueDateInfo({ + dueDate: new DueDate({ recurring: false, date: "2024-06-02", }), @@ -52,7 +52,7 @@ describe("formatDueDate", () => { }, { description: "Tomorrow, with time", - dueDate: new DueDateInfo({ + dueDate: new DueDate({ recurring: false, date: "2024-06-02", datetime: "2024-06-02T12:00:00", @@ -61,7 +61,7 @@ describe("formatDueDate", () => { }, { description: "Next week, no time", - dueDate: new DueDateInfo({ + dueDate: new DueDate({ recurring: false, date: "2024-06-05", }), @@ -69,7 +69,7 @@ describe("formatDueDate", () => { }, { description: "Next week, with time", - dueDate: new DueDateInfo({ + dueDate: new DueDate({ recurring: false, date: "2024-06-05", datetime: "2024-06-05T12:00:00", @@ -78,7 +78,7 @@ describe("formatDueDate", () => { }, { description: "Future date, no time", - dueDate: new DueDateInfo({ + dueDate: new DueDate({ recurring: false, date: "2024-06-30", }), @@ -86,7 +86,7 @@ describe("formatDueDate", () => { }, { description: "Future date, with time", - dueDate: new DueDateInfo({ + dueDate: new DueDate({ recurring: false, date: "2024-06-30", datetime: "2024-06-30T12:00:00", @@ -95,7 +95,7 @@ describe("formatDueDate", () => { }, { description: "Yesterday, no time", - dueDate: new DueDateInfo({ + dueDate: new DueDate({ recurring: false, date: "2024-05-31", }), @@ -103,7 +103,7 @@ describe("formatDueDate", () => { }, { description: "Yesterday, with time", - dueDate: new DueDateInfo({ + dueDate: new DueDate({ recurring: false, date: "2024-05-31", datetime: "2024-05-31T12:00:00", @@ -112,7 +112,7 @@ describe("formatDueDate", () => { }, { description: "Last week, no time", - dueDate: new DueDateInfo({ + dueDate: new DueDate({ recurring: false, date: "2024-05-29", }), @@ -120,7 +120,7 @@ describe("formatDueDate", () => { }, { description: "Last week, with time", - dueDate: new DueDateInfo({ + dueDate: new DueDate({ recurring: false, date: "2024-05-29", datetime: "2024-05-29T12:00:00", @@ -129,7 +129,7 @@ describe("formatDueDate", () => { }, { description: "Next year, no time", - dueDate: new DueDateInfo({ + dueDate: new DueDate({ recurring: false, date: "2025-06-10", }), @@ -137,7 +137,7 @@ describe("formatDueDate", () => { }, { description: "Next year, with time", - dueDate: new DueDateInfo({ + dueDate: new DueDate({ recurring: false, date: "2025-06-10", datetime: "2025-06-10T12:00:00", @@ -146,7 +146,7 @@ describe("formatDueDate", () => { }, { description: "Last year, no time", - dueDate: new DueDateInfo({ + dueDate: new DueDate({ recurring: false, date: "2023-08-15", }), @@ -154,7 +154,7 @@ describe("formatDueDate", () => { }, { description: "Last year, with time", - dueDate: new DueDateInfo({ + dueDate: new DueDate({ recurring: false, date: "2023-08-15", datetime: "2023-08-15T12:00:00", @@ -174,14 +174,14 @@ describe("formatDueDate", () => { describe("formatAsHeader", () => { type Testcase = { description: string; - dueDate: DueDateInfo; + dueDate: DueDate; expected: string; }; const testcases: Testcase[] = [ { description: "Today", - dueDate: new DueDateInfo({ + dueDate: new DueDate({ date: "2024-06-01", recurring: false, }), @@ -189,7 +189,7 @@ describe("formatAsHeader", () => { }, { description: "Tomorrow", - dueDate: new DueDateInfo({ + dueDate: new DueDate({ date: "2024-06-02", recurring: false, }), @@ -197,7 +197,7 @@ describe("formatAsHeader", () => { }, { description: "Other date", - dueDate: new DueDateInfo({ + dueDate: new DueDate({ date: "2024-06-03", recurring: false, }), diff --git a/plugin/src/data/dueDateFormatter.ts b/plugin/src/data/dueDateFormatter.ts index 0e6b820..22f4f8d 100644 --- a/plugin/src/data/dueDateFormatter.ts +++ b/plugin/src/data/dueDateFormatter.ts @@ -1,4 +1,4 @@ -import type { DueDateInfo } from "@/data/dueDateInfo"; +import type { DueDate } from "@/data/dueDate"; import { t } from "@/i18n"; import { locale } from "@/infra/locale"; @@ -31,7 +31,7 @@ const getFormatter = (style: string): Intl.DateTimeFormat => { return formatterCache[style]; }; -export const formatDueDate = (dueDate: DueDateInfo): string => { +export const formatDueDate = (dueDate: DueDate): string => { const date = formatDate(dueDate); if (dueDate.hasTime()) { @@ -44,7 +44,7 @@ export const formatDueDate = (dueDate: DueDateInfo): string => { return date; }; -const formatDate = (dueDate: DueDateInfo): string => { +const formatDate = (dueDate: DueDate): string => { const i18n = t().dates; if (dueDate.isToday()) { @@ -74,7 +74,7 @@ const formatDate = (dueDate: DueDateInfo): string => { return dueDate.format(getFormatter("date")); }; -export const formatAsHeader = (dueDate: DueDateInfo): string => { +export const formatAsHeader = (dueDate: DueDate): string => { const formatParts: string[] = [ dueDate.format(getFormatter("date")), dueDate.format(getFormatter("weekday")), diff --git a/plugin/src/data/transformations/grouping.ts b/plugin/src/data/transformations/grouping.ts index 1dd695e..76d0e2c 100644 --- a/plugin/src/data/transformations/grouping.ts +++ b/plugin/src/data/transformations/grouping.ts @@ -1,8 +1,8 @@ import type { Project } from "@/api/domain/project"; import type { Section } from "@/api/domain/section"; import type { Priority } from "@/api/domain/task"; +import { DueDate } from "@/data/dueDate"; import { formatAsHeader } from "@/data/dueDateFormatter"; -import { DueDateInfo } from "@/data/dueDateInfo"; import type { Task } from "@/data/task"; import { GroupVariant } from "@/query/query"; @@ -134,7 +134,7 @@ function groupByDate(tasks: Task[]): GroupedTasks[] { return "Overdue"; } - return formatAsHeader(new DueDateInfo({ recurring: false, date })); + return formatAsHeader(new DueDate({ recurring: false, date })); }; const dates = partitionBy(tasks, (task: Task) => { @@ -142,7 +142,7 @@ function groupByDate(tasks: Task[]): GroupedTasks[] { return undefined; } - if (new DueDateInfo(task.due).isOverdue()) { + if (new DueDate(task.due).isOverdue()) { return "Overdue"; } diff --git a/plugin/src/data/transformations/sorting.ts b/plugin/src/data/transformations/sorting.ts index c18f233..ffbe534 100644 --- a/plugin/src/data/transformations/sorting.ts +++ b/plugin/src/data/transformations/sorting.ts @@ -1,5 +1,5 @@ import type { Task } from "@/data//task"; -import { DueDateInfo } from "@/data/dueDateInfo"; +import { DueDate } from "@/data/dueDate"; import { SortingVariant } from "@/query/query"; import { parseAbsoluteToLocal } from "@internationalized/date"; @@ -62,8 +62,8 @@ function compareTaskDate(self: T, other: T): number { return -1; } - const selfInfo = new DueDateInfo(self.due); - const otherInfo = new DueDateInfo(other.due); + const selfInfo = new DueDate(self.due); + const otherInfo = new DueDate(other.due); const dateCmp = selfInfo.compareDate(otherInfo); diff --git a/plugin/src/ui/query/task/Task.tsx b/plugin/src/ui/query/task/Task.tsx index d04a3de..aab53a3 100644 --- a/plugin/src/ui/query/task/Task.tsx +++ b/plugin/src/ui/query/task/Task.tsx @@ -1,4 +1,4 @@ -import { DueDateInfo } from "@/data/dueDateInfo"; +import { DueDate } from "@/data/dueDate"; import type { TaskTree } from "@/data/transformations/relationships"; import { t } from "@/i18n"; import { ShowMetadataVariant } from "@/query/query"; @@ -81,7 +81,7 @@ function getDueMetadataInfo(task: TaskTree): string | undefined { return undefined; } - const info = new DueDateInfo(task.due); + const info = new DueDate(task.due); if (info.isOverdue()) { return "overdue"; @@ -101,7 +101,7 @@ function getTimeMetadataInfo(task: TaskTree): boolean | undefined { return undefined; } - return new DueDateInfo(task.due).hasTime(); + return new DueDate(task.due).hasTime(); } const sanitizeContent = (content: string): string => { diff --git a/plugin/src/ui/query/task/TaskMetadata.tsx b/plugin/src/ui/query/task/TaskMetadata.tsx index 5c7cefb..6b1da1a 100644 --- a/plugin/src/ui/query/task/TaskMetadata.tsx +++ b/plugin/src/ui/query/task/TaskMetadata.tsx @@ -1,5 +1,5 @@ +import { DueDate } from "@/data/dueDate"; import { formatDueDate } from "@/data/dueDateFormatter"; -import { DueDateInfo } from "@/data/dueDateInfo"; import type { Task } from "@/data/task"; import { type Query, ShowMetadataVariant } from "@/query/query"; import type { Settings } from "@/settings"; @@ -78,7 +78,7 @@ const dateLabel = (task: Task): string => { return ""; } - return formatDueDate(new DueDateInfo(task.due)); + return formatDueDate(new DueDate(task.due)); }; type TaskMetadataProps = {