From f673880171164f4591b8f2ae40b1338f1d007147 Mon Sep 17 00:00:00 2001 From: Isaac Roberts <119639439+madebyisaacr@users.noreply.github.com> Date: Mon, 12 Jan 2026 13:31:04 -0500 Subject: [PATCH 1/3] Add time to Notion date fields --- plugins/notion/package.json | 2 +- plugins/notion/src/FieldMapping.tsx | 8 ++++--- plugins/notion/src/api.ts | 25 ++++++++++++--------- plugins/notion/src/data.ts | 35 ++++++++++++++++++++++++++--- yarn.lock | 2 +- 5 files changed, 54 insertions(+), 18 deletions(-) diff --git a/plugins/notion/package.json b/plugins/notion/package.json index 75123dd5a..b668041bd 100644 --- a/plugins/notion/package.json +++ b/plugins/notion/package.json @@ -14,7 +14,7 @@ }, "dependencies": { "@notionhq/client": "^3.1.3", - "framer-plugin": "^3.6.0", + "framer-plugin": "3.10.0-alpha.2", "react": "^18.3.1", "react-dom": "^18.3.1", "valibot": "^1.2.0" diff --git a/plugins/notion/src/FieldMapping.tsx b/plugins/notion/src/FieldMapping.tsx index 4b38baf37..ec808e7ba 100644 --- a/plugins/notion/src/FieldMapping.tsx +++ b/plugins/notion/src/FieldMapping.tsx @@ -10,6 +10,7 @@ import { useEffect, useMemo, useState } from "react" import { type FieldId, type FieldInfo, + type VirtualFieldType, getDatabaseFieldsInfo, getPossibleSlugFieldIds, isMissingCollection, @@ -25,9 +26,10 @@ import { } from "./data" import { assert, syncMethods } from "./utils" -const labelByFieldTypeOption: Record = { +const labelByFieldTypeOption: Record = { boolean: "Toggle", date: "Date", + dateTime: "Date & Time", number: "Number", formattedText: "Formatted Text", color: "Color", @@ -49,7 +51,7 @@ interface FieldMappingRowProps { missingCollection: boolean onToggleIgnored: (fieldId: string) => void onNameChange: (fieldId: string, name: string) => void - onFieldTypeChange: (fieldId: string, type: ManagedCollectionField["type"]) => void + onFieldTypeChange: (fieldId: string, type: VirtualFieldType) => void } function FieldMappingRow({ @@ -211,7 +213,7 @@ export function FieldMapping({ }) } - const changeFieldType = (fieldId: string, type: ManagedCollectionField["type"]) => { + const changeFieldType = (fieldId: string, type: VirtualFieldType) => { setFieldsInfo(prevFieldsInfo => { const updatedFieldInfo = prevFieldsInfo.map(fieldInfo => { if (fieldInfo.id !== fieldId) return fieldInfo diff --git a/plugins/notion/src/api.ts b/plugins/notion/src/api.ts index f3c8d3062..4cdcf9446 100644 --- a/plugins/notion/src/api.ts +++ b/plugins/notion/src/api.ts @@ -27,12 +27,14 @@ const LAST_CONTENT_IMPORTING_UPDATE_DATE = new Date("2025-07-01T12:00:00.000Z") export type FieldId = string +export type VirtualFieldType = ManagedCollectionField["type"] | "dateTime" + export interface FieldInfo { id: FieldId name: string originalName: string - type: ManagedCollectionField["type"] | null - allowedTypes: ManagedCollectionField["type"][] + type: VirtualFieldType | null + allowedTypes: VirtualFieldType[] notionProperty: NotionProperty | null } @@ -66,12 +68,12 @@ const slugFieldTypes: NotionProperty["type"][] = ["title", "rich_text", "unique_ export const supportedCMSTypeByNotionPropertyType = { checkbox: ["boolean"], - date: ["date"], + date: ["dateTime", "date"], number: ["number"], title: ["string"], rich_text: ["formattedText", "string", "color"], - created_time: ["date"], - last_edited_time: ["date"], + created_time: ["dateTime", "date"], + last_edited_time: ["dateTime", "date"], select: ["enum"], status: ["enum"], url: ["link"], @@ -80,8 +82,8 @@ export const supportedCMSTypeByNotionPropertyType = { files: ["file", "image", "array"], relation: ["multiCollectionReference", "collectionReference"], unique_id: ["string", "number"], - formula: ["string", "number", "boolean", "date", "link", "color"], -} satisfies Partial> + formula: ["string", "number", "boolean", "date", "dateTime", "link", "color"], +} satisfies Partial> // Naive implementation to be authenticated, a token could be expired. // For simplicity we just close the plugin and clear storage in that case. @@ -216,15 +218,18 @@ export async function getNotionDatabases() { export function assertFieldTypeMatchesPropertyType( propertyType: NotionProperty["type"], - fieldType: ManagedCollectionField["type"] -): asserts fieldType is ManagedCollectionField["type"] { + fieldType: VirtualFieldType | ManagedCollectionField["type"] +): void { if (!isSupportedPropertyType(propertyType)) { throw new Error(`Property type '${propertyType}' is not supported.`) } const allowedFieldTypes = supportedCMSTypeByNotionPropertyType[propertyType] - if (!allowedFieldTypes.includes(fieldType as never)) { + // For dateTime, treat it as "date" for validation purposes + const typeToCheck = fieldType === "dateTime" ? "date" : fieldType + + if (!allowedFieldTypes.includes(typeToCheck as never)) { throw new Error(`Field type '${fieldType}' is not valid for property type '${propertyType}'.`) } } diff --git a/plugins/notion/src/data.ts b/plugins/notion/src/data.ts index fbb782929..493c0c9cb 100644 --- a/plugins/notion/src/data.ts +++ b/plugins/notion/src/data.ts @@ -75,8 +75,16 @@ export function mergeFieldsInfoWithExistingFields( ): FieldInfo[] { return sourceFieldsInfo.map(sourceFieldInfo => { const existingField = existingFields.find(existingField => existingField.id === sourceFieldInfo.id) - if (existingField && sourceFieldInfo.allowedTypes.includes(existingField.type)) { - return { ...sourceFieldInfo, name: existingField.name, type: existingField.type } + if (existingField) { + // Handle date fields with displayTime: convert to dateTime virtual type + let fieldType: FieldInfo["type"] = existingField.type + if (existingField.type === "date" && "displayTime" in existingField && existingField.displayTime === true) { + fieldType = "dateTime" + } + + if (sourceFieldInfo.allowedTypes.includes(fieldType)) { + return { ...sourceFieldInfo, name: existingField.name, type: fieldType } + } } return sourceFieldInfo }) @@ -433,7 +441,6 @@ export function fieldsInfoToCollectionFields( switch (fieldType) { case "boolean": - case "date": case "number": case "string": case "formattedText": @@ -449,6 +456,28 @@ export function fieldsInfoToCollectionFields( }) break } + case "date": { + assertFieldTypeMatchesPropertyType(property.type, fieldType) + fields.push({ + type: "date", + id: fieldInfo.id, + name: fieldName, + userEditable: false, + displayTime: false, + }) + break + } + case "dateTime": { + assertFieldTypeMatchesPropertyType(property.type, "date") + fields.push({ + type: "date", + id: fieldInfo.id, + name: fieldName, + userEditable: false, + displayTime: true, + }) + break + } case "enum": { assertFieldTypeMatchesPropertyType(property.type, fieldType) diff --git a/yarn.lock b/yarn.lock index 98251554a..eaf2a44eb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5661,7 +5661,7 @@ __metadata: "@notionhq/client": "npm:^3.1.3" "@types/react": "npm:^18.3.24" "@types/react-dom": "npm:^18.3.7" - framer-plugin: "npm:^3.6.0" + framer-plugin: "npm:3.10.0-alpha.2" framer-plugin-tools: "npm:^1.0.0" react: "npm:^18.3.1" react-dom: "npm:^18.3.1" From b915ad8978d0f95e9c14ffb8a19b791919ee8f55 Mon Sep 17 00:00:00 2001 From: Isaac Roberts <119639439+madebyisaacr@users.noreply.github.com> Date: Mon, 12 Jan 2026 13:48:30 -0500 Subject: [PATCH 2/3] Biome fix --- plugins/notion/src/FieldMapping.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/notion/src/FieldMapping.tsx b/plugins/notion/src/FieldMapping.tsx index ec808e7ba..d9b52f01c 100644 --- a/plugins/notion/src/FieldMapping.tsx +++ b/plugins/notion/src/FieldMapping.tsx @@ -10,10 +10,10 @@ import { useEffect, useMemo, useState } from "react" import { type FieldId, type FieldInfo, - type VirtualFieldType, getDatabaseFieldsInfo, getPossibleSlugFieldIds, isMissingCollection, + type VirtualFieldType, } from "./api" import { type DatabaseIdMap, From c336286846e6d36f55bc2d7003ff47cf6b1a91b3 Mon Sep 17 00:00:00 2001 From: Isaac Roberts <119639439+madebyisaacr@users.noreply.github.com> Date: Tue, 13 Jan 2026 12:03:27 -0500 Subject: [PATCH 3/3] Improve types --- plugins/notion/src/FieldMapping.tsx | 2 +- plugins/notion/src/api.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/notion/src/FieldMapping.tsx b/plugins/notion/src/FieldMapping.tsx index d9b52f01c..9ee2315d9 100644 --- a/plugins/notion/src/FieldMapping.tsx +++ b/plugins/notion/src/FieldMapping.tsx @@ -26,7 +26,7 @@ import { } from "./data" import { assert, syncMethods } from "./utils" -const labelByFieldTypeOption: Record = { +const labelByFieldTypeOption: Record = { boolean: "Toggle", date: "Date", dateTime: "Date & Time", diff --git a/plugins/notion/src/api.ts b/plugins/notion/src/api.ts index 4cdcf9446..377c3414f 100644 --- a/plugins/notion/src/api.ts +++ b/plugins/notion/src/api.ts @@ -83,7 +83,7 @@ export const supportedCMSTypeByNotionPropertyType = { relation: ["multiCollectionReference", "collectionReference"], unique_id: ["string", "number"], formula: ["string", "number", "boolean", "date", "dateTime", "link", "color"], -} satisfies Partial> +} satisfies Partial> // Naive implementation to be authenticated, a token could be expired. // For simplicity we just close the plugin and clear storage in that case. @@ -218,7 +218,7 @@ export async function getNotionDatabases() { export function assertFieldTypeMatchesPropertyType( propertyType: NotionProperty["type"], - fieldType: VirtualFieldType | ManagedCollectionField["type"] + fieldType: VirtualFieldType ): void { if (!isSupportedPropertyType(propertyType)) { throw new Error(`Property type '${propertyType}' is not supported.`)