diff --git a/manifest-beta.json b/manifest-beta.json index c4cd1725..99fd7197 100644 --- a/manifest-beta.json +++ b/manifest-beta.json @@ -1,7 +1,7 @@ { "id": "obsidian-projects", "name": "Projects", - "version": "0.0.0-999", + "version": "0.0.0-996", "minAppVersion": "1.0.0", "description": "Plain text project planning.", "author": "Marcus Olsson", diff --git a/package-lock.json b/package-lock.json index d5b5502b..df273744 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,6 +47,7 @@ "svelte-i18next": "^2.2.2", "svelte-media-queries": "^1.6.2", "svelte-preprocess": "^5.1.4", + "temporal-polyfill": "^0.2.5", "ts-essentials": "^10.0.3", "ts-jest": "^29.1.3", "tslib": "^2.6.2", @@ -7620,6 +7621,23 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/temporal-polyfill": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/temporal-polyfill/-/temporal-polyfill-0.2.5.tgz", + "integrity": "sha512-ye47xp8Cb0nDguAhrrDS1JT1SzwEV9e26sSsrWzVu+yPZ7LzceEcH0i2gci9jWfOfSCCgM3Qv5nOYShVUUFUXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "temporal-spec": "^0.2.4" + } + }, + "node_modules/temporal-spec": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/temporal-spec/-/temporal-spec-0.2.4.tgz", + "integrity": "sha512-lDMFv4nKQrSjlkHKAlHVqKrBG4DyFfa9F74cmBZ3Iy3ed8yvWnlWSIdi4IKfSqwmazAohBNwiN64qGx4y5Q3IQ==", + "dev": true, + "license": "ISC" + }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmmirror.com/test-exclude/-/test-exclude-6.0.0.tgz", diff --git a/package.json b/package.json index 1c0aecb3..5944a5e3 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "svelte-i18next": "^2.2.2", "svelte-media-queries": "^1.6.2", "svelte-preprocess": "^5.1.4", + "temporal-polyfill": "^0.2.5", "ts-essentials": "^10.0.3", "ts-jest": "^29.1.3", "tslib": "^2.6.2", @@ -63,4 +64,4 @@ "uuid": "^11.0.3", "yaml": "^2.4.2" } -} \ No newline at end of file +} diff --git a/src/lib/dataApi.ts b/src/lib/dataApi.ts index bac957d8..065e240b 100644 --- a/src/lib/dataApi.ts +++ b/src/lib/dataApi.ts @@ -1,4 +1,3 @@ -import dayjs from "dayjs"; import { produce } from "immer"; import moment from "moment"; import { get } from "svelte/store"; @@ -185,17 +184,15 @@ export function doUpdateRecord( (field) => field.name === entry[0] && field.type === DataFieldType.Date && - (field.typeConfig?.time || - entry[1].getHours() || - entry[1].getMinutes() || - entry[1].getSeconds() || - entry[1].getMilliseconds()) + (field.typeConfig?.time || entry[1].hour) + // TODO: double check ); - return produce(entry, (draft) => { - draft[1] = dayjs(entry[1]).format( - isDatetime ? "YYYY-MM-DDTHH:mm" : "YYYY-MM-DD" - ); + draft[1] = isDatetime + ? entry[1] + .toPlainDateTime() + .toString({ smallestUnit: "minute" }) + : entry[1].toPlainDate().toString(); }); } return entry; diff --git a/src/lib/dataframe/dataframe.ts b/src/lib/dataframe/dataframe.ts index ed77a55e..8ca6bd9f 100644 --- a/src/lib/dataframe/dataframe.ts +++ b/src/lib/dataframe/dataframe.ts @@ -1,5 +1,6 @@ import type { FieldConfig } from "src/settings/settings"; import type { RecordError } from "../datasources/frontmatter/datasource"; +import { Temporal } from "temporal-polyfill"; /** * DataFrame is the core data structure that contains structured data for a @@ -77,32 +78,32 @@ export type DataValue = | string | number | boolean - | Date + | Temporal.ZonedDateTime | Array>; -export function isOptionalDataValue( - value: unknown -): value is Optional { - switch (typeof value) { - case "string": - return true; - case "number": - return true; - case "boolean": - return true; - default: - return false; - } -} +// export function isOptionalDataValue( +// value: unknown +// ): value is Optional { +// switch (typeof value) { +// case "string": +// return true; +// case "number": +// return true; +// case "boolean": +// return true; +// default: +// return false; +// } +// } -export function isRepeatedDataValue( - value: unknown -): value is Array> { - if (Array.isArray(value)) { - return value.every(isOptionalDataValue); - } - return false; -} +// export function isRepeatedDataValue( +// value: unknown +// ): value is Array> { +// if (Array.isArray(value)) { +// return value.every(isOptionalDataValue); +// } +// return false; +// } export type Optional = | T @@ -138,8 +139,11 @@ export function isNumber( return typeof value === "number"; } -export function isDate(value: Optional | DataValue): value is Date { - return value instanceof Date; +export function isDate( + value: Optional | DataValue +): value is Temporal.ZonedDateTime { + return value instanceof Temporal.ZonedDateTime; + //TODO: can be lessen to ZonedDateTimeLike ? } // export function hasValue(value: Optional): value is DataValue { @@ -179,7 +183,7 @@ export function isOptionalNumber( export function isOptionalDate( value: Optional -): value is Optional { +): value is Optional { return isDate(value) || isOptional(value); } diff --git a/src/lib/datasources/dataview/standardize.ts b/src/lib/datasources/dataview/standardize.ts index 2e219ab1..fb5ade2a 100644 --- a/src/lib/datasources/dataview/standardize.ts +++ b/src/lib/datasources/dataview/standardize.ts @@ -1,6 +1,6 @@ -import dayjs from "dayjs"; import type { Link } from "obsidian-dataview"; import type { DataValue, Optional } from "src/lib/dataframe/dataframe"; +import { Temporal } from "temporal-polyfill"; /** * standardizeValues converts a Dataview data structure of values to the common @@ -36,6 +36,6 @@ function standardizeObject(value: any) { return (value as Link).toString(); } if ("ts" in value) { - return dayjs(value.ts).format("YYYY-MM-DD"); + return Temporal.PlainDateTime.from(value.c).toString() } } diff --git a/src/lib/datasources/helpers.ts b/src/lib/datasources/helpers.ts index d29283f4..a75236bb 100644 --- a/src/lib/datasources/helpers.ts +++ b/src/lib/datasources/helpers.ts @@ -1,5 +1,3 @@ -import dayjs from "dayjs"; - import { DataFieldType, type DataField, @@ -7,6 +5,7 @@ import { type DataValue, type Optional, } from "../dataframe/dataframe"; +import { Temporal } from "temporal-polyfill"; /** * Parses the values for each record based on the detected field types. @@ -29,15 +28,44 @@ export function parseRecords( const value = record.values[field.name]; switch (field.type) { - case DataFieldType.Date: + case DataFieldType.Date: //TODO: extract to helpers, and processing numbers if (typeof value === "string") { - record.values[field.name] = dayjs(value).toDate(); + let parsedValue = null; + try { + // Attempt to parse as ZonedDateTime + parsedValue = Temporal.ZonedDateTime.from(value); + } catch { + try { + // Attempt to parse as Instant and convert to ZonedDateTime + parsedValue = + Temporal.Instant.from(value).toZonedDateTimeISO(value); + } catch { + try { + // Attempt to create ZonedDateTime using the current time and a PlainDate + parsedValue = Temporal.PlainDateTime.from( + value + ).toZonedDateTime(Temporal.Now.timeZoneId()); + } catch { + parsedValue = null; // Default to null if all parsing fails + } + } + } + + // Assign the parsed value to the record + record.values[field.name] = parsedValue; } break; case DataFieldType.Number: if (typeof value === "string") { record.values[field.name] = parseFloat(value); } + // else if (typeof value === "number") { + // record.values[field.name] = + // Temporal.ZonedDateTime.from(value.toString()) ?? + // Temporal.Instant.fromEpochMilliseconds(value).toZonedDateTimeISO( + // "UTC" // possible thru Now, for user local zone + // ); + // } break; case DataFieldType.Boolean: if (typeof value === "string") { @@ -123,7 +151,9 @@ export function detectCellType(value: unknown): DataFieldType { // Standard types if (typeof value === "string") { if ( - /^\d{4}-\d{2}-\d{2}(T)?(\d{2})?(:\d{2})?(:\d{2})?(.\d{3})?$/.test(value) + /^\d{4}-\d{2}-\d{2}(?:[Tt ](?:\d{2})?(?::\d{2})?(?::\d{2})?(?:.\d+)?(?:[+-]\d{2}(?::?\d{2})?|[Zz])?)?$/.test( + value + ) ) { return DataFieldType.Date; } diff --git a/src/main.ts b/src/main.ts index aa87c764..7c75b3f7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,3 @@ -import dayjs from "dayjs"; -import isoWeek from "dayjs/plugin/isoWeek"; -import localizedFormat from "dayjs/plugin/localizedFormat"; import { either, task, taskEither } from "fp-ts"; import { pipe } from "fp-ts/lib/function"; import { Plugin, TFolder, WorkspaceLeaf, addIcon } from "obsidian"; @@ -25,9 +22,6 @@ import { } from "./settings/settings"; import { ProjectsView, VIEW_TYPE_PROJECTS } from "./view"; -dayjs.extend(isoWeek); -dayjs.extend(localizedFormat); - const PROJECTS_PLUGIN_ID = "obsidian-projects"; export default class ProjectsPlugin extends Plugin { diff --git a/src/ui/app/filterFunctions.ts b/src/ui/app/filterFunctions.ts index 993ac7ad..e0b053fc 100644 --- a/src/ui/app/filterFunctions.ts +++ b/src/ui/app/filterFunctions.ts @@ -1,5 +1,4 @@ import { produce } from "immer"; -import dayjs from "dayjs"; import { type DataFrame, type DataRecord, @@ -27,6 +26,7 @@ import { type DateFilterOperator, type ListFilterOperator, } from "src/settings/settings"; +import { Temporal } from "temporal-polyfill"; export function matchesCondition( cond: FilterCondition, @@ -61,10 +61,30 @@ export function matchesCondition( } else if (isOptionalBoolean(value) && isBooleanFilterOperator(operator)) { return booleanFns[operator](value); } else if (isOptionalDate(value) && isDateFilterOperator(operator)) { - return dateFns[operator]( - value, - cond.value ? dayjs(cond.value).toDate() : undefined - ); + let parsedValue = null; // TODO: extract to help functions / processing numbers + if (cond.value) { + try { + // Attempt to parse as ZonedDateTime + parsedValue = Temporal.ZonedDateTime.from(cond.value); + } catch { + try { + // Attempt to parse as Instant and convert to ZonedDateTime + parsedValue = Temporal.Instant.from(cond.value).toZonedDateTimeISO( + cond.value + ); + } catch { + try { + // Attempt to create ZonedDateTime using the current time and a PlainDate + parsedValue = Temporal.PlainDateTime.from( + cond.value + ).toZonedDateTime(Temporal.Now.timeZoneId()); + } catch { + parsedValue = null; // Default to null if all parsing fails + } + } + } + } + return dateFns[operator](value, parsedValue ?? undefined); } return false; @@ -137,22 +157,28 @@ export const booleanFns: Record< }; export const dateFns: Record< + //TODO: handle datetime DateFilterOperator, - (left: Optional, right?: Optional) => boolean + ( + left: Optional, + right?: Optional + ) => boolean // TODO: refactor > = { "is-on": (left, right) => { - return left && right ? left.getTime() == right.getTime() : false; + return left && right ? left.equals(right) : false; }, "is-not-on": (left, right) => - left && right ? left.getTime() != right.getTime() : true, + left?.toPlainDate() && right?.toPlainDate() + ? !left.toPlainDate().equals(right.toPlainDate()) + : true, "is-before": (left, right) => - left && right ? left.getTime() < right.getTime() : false, + left && right ? Temporal.PlainDate.compare(left, right) == -1 : false, "is-after": (left, right) => - left && right ? left.getTime() > right.getTime() : false, + left && right ? Temporal.PlainDate.compare(left, right) == 1 : false, "is-on-and-before": (left, right) => - left && right ? left.getTime() <= right.getTime() : false, + left && right ? Temporal.PlainDate.compare(left, right) < 1 : false, "is-on-and-after": (left, right) => - left && right ? left.getTime() >= right.getTime() : false, + left && right ? Temporal.PlainDate.compare(left, right) > -1 : false, }; export const listFns_multitext: Record< diff --git a/src/ui/app/onboarding/demoProject.ts b/src/ui/app/onboarding/demoProject.ts index 3856b296..0cae62ea 100644 --- a/src/ui/app/onboarding/demoProject.ts +++ b/src/ui/app/onboarding/demoProject.ts @@ -1,6 +1,6 @@ -import dayjs from "dayjs"; import { normalizePath, stringifyYaml, type Vault } from "obsidian"; import { v4 as uuidv4 } from "uuid"; +import { Temporal } from "temporal-polyfill"; import { settings } from "src/lib/stores/settings"; import type { BoardConfig } from "src/ui/views/Board/types"; @@ -14,12 +14,12 @@ export async function createDemoProject(vault: Vault) { await vault.createFolder(demoFolder); - const startDate = dayjs(); + const startDate = Temporal.Now.zonedDateTimeISO(); const files = { "The Best Notes You'll Ever Make": { status: "Done", - due: startDate.subtract(2, "weeks").format("YYYY-MM-DD"), + due: startDate.subtract({weeks: 2}).toPlainDate().toString(), published: true, weight: 1, tags: ["note-taking"], @@ -28,7 +28,7 @@ export async function createDemoProject(vault: Vault) { }, "The Easiest Way to Start Taking Notes": { status: "Done", - due: startDate.subtract(1, "weeks").format("YYYY-MM-DD"), + due: startDate.subtract({weeks: 1}).toPlainDate().toString(), published: true, weight: 2, tags: ["note-taking", "obsidian"], @@ -37,7 +37,7 @@ export async function createDemoProject(vault: Vault) { }, "Why You Should Be Taking More Notes": { status: "Doing", - due: startDate.format("YYYY-MM-DD"), + due: startDate.toPlainDate().toString(), published: false, weight: 3, tags: ["note-taking", "pkm"], @@ -46,7 +46,7 @@ export async function createDemoProject(vault: Vault) { }, "What I Learned From Taking 15,000 Notes": { status: "Backlog", - due: startDate.add(1, "weeks").format("YYYY-MM-DD"), + due: startDate.add({weeks: 1}).toPlainDate().toString(), published: false, weight: 4, tags: ["pkm", "obsidian"], @@ -55,7 +55,7 @@ export async function createDemoProject(vault: Vault) { }, "5 Mistakes I Made When I Started Using Obsidian": { status: "Backlog", - due: startDate.add(2, "weeks").format("YYYY-MM-DD"), + due: startDate.add({weeks: 2}).toPlainDate().toString(), published: false, tags: ["obsidian"], image: diff --git a/src/ui/app/toolbar/viewOptions/color/ColorOptions.svelte b/src/ui/app/toolbar/viewOptions/color/ColorOptions.svelte index 874fa346..f69ae3c2 100644 --- a/src/ui/app/toolbar/viewOptions/color/ColorOptions.svelte +++ b/src/ui/app/toolbar/viewOptions/color/ColorOptions.svelte @@ -1,6 +1,6 @@