@notionhq/custom-blocks-host 0.3.6 → 0.3.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,120 @@
1
+ import type {
2
+ NotionDate,
3
+ NotionDateRange,
4
+ NotionDateTime,
5
+ NotionDateTimeRange,
6
+ } from "@notionhq/custom-blocks-protocol/dataSources/dateValue.js"
7
+ import type { NotionDateInput } from "@notionhq/custom-blocks-protocol/pages/page.js"
8
+
9
+ export type CustomBlockDateValue =
10
+ | NotionDate
11
+ | NotionDateRange
12
+ | (NotionDateTime & { time_zone: string })
13
+ | (NotionDateTimeRange & { time_zone: string })
14
+
15
+ export class InvalidNotionDateError extends Error {}
16
+
17
+ export function toNotionDateValue(
18
+ value: NotionDateInput,
19
+ ): CustomBlockDateValue {
20
+ try {
21
+ return convertDateValue(value)
22
+ } catch (error) {
23
+ if (error instanceof RangeError) {
24
+ throw new InvalidNotionDateError(error.message)
25
+ }
26
+ throw error
27
+ }
28
+ }
29
+
30
+ function convertDateValue(value: NotionDateInput): CustomBlockDateValue {
31
+ const { start, end, time_zone, reminder } = value
32
+ if (!start.split("T")[0]) {
33
+ throw new InvalidNotionDateError("Date values must include a date.")
34
+ }
35
+ const timed = start.includes("T") || time_zone != null
36
+ const offset = start.match(/([+-])(\d{2}):(\d{2})$/)
37
+ const offsetMinutes = offset
38
+ ? (Number(offset[2]) * 60 + Number(offset[3])) *
39
+ (offset[1] === "-" ? -1 : 1)
40
+ : 0
41
+ const fixedZone = time_zone?.match(/^UTC(?:([+-])(\d{1,2})(?::(\d{2}))?)?$/)
42
+ const targetOffsetMinutes = fixedZone
43
+ ? (Number(fixedZone[2] ?? 0) * 60 + Number(fixedZone[3] ?? 0)) *
44
+ (fixedZone[1] === "-" ? -1 : 1)
45
+ : offsetMinutes
46
+ const parts = (input: string) => {
47
+ if (!input.split("T")[0]) {
48
+ throw new InvalidNotionDateError("Date values must include a date.")
49
+ }
50
+ if (!input.includes("T")) {
51
+ return { date: input, time: "00:00" }
52
+ }
53
+ const wallClock = input.match(
54
+ /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2})(?::\d{2}(?:\.\d+)?)?$/,
55
+ )
56
+ if (wallClock) {
57
+ return { date: wallClock[1], time: wallClock[2] }
58
+ }
59
+ if (!time_zone || fixedZone) {
60
+ const local = new Date(
61
+ new Date(input).getTime() + targetOffsetMinutes * 60_000,
62
+ ).toISOString()
63
+ return { date: local.slice(0, 10), time: local.slice(11, 16) }
64
+ }
65
+ const entries = new Intl.DateTimeFormat("en-CA", {
66
+ timeZone: time_zone,
67
+ year: "numeric",
68
+ month: "2-digit",
69
+ day: "2-digit",
70
+ hour: "2-digit",
71
+ minute: "2-digit",
72
+ hourCycle: "h23",
73
+ }).formatToParts(new Date(input))
74
+ const get = (type: string) =>
75
+ entries.find(part => part.type === type)?.value
76
+ return {
77
+ date: `${get("year")}-${get("month")}-${get("day")}`,
78
+ time: `${get("hour")}:${get("minute")}`,
79
+ }
80
+ }
81
+ const startParts = parts(start)
82
+ const endParts = end ? parts(end) : undefined
83
+ if (!timed) {
84
+ if (reminder && !("time" in reminder) && reminder.unit !== "none") {
85
+ throw new InvalidNotionDateError(
86
+ "Hour and minute reminders require a date with a time.",
87
+ )
88
+ }
89
+ return endParts
90
+ ? {
91
+ type: "daterange",
92
+ start_date: startParts.date,
93
+ end_date: endParts.date,
94
+ reminder,
95
+ }
96
+ : { type: "date", start_date: startParts.date, reminder }
97
+ }
98
+ const zone =
99
+ time_zone ??
100
+ (offsetMinutes === 0
101
+ ? "UTC"
102
+ : `UTC${offsetMinutes < 0 ? "-" : "+"}${Math.floor(Math.abs(offsetMinutes) / 60)}${Math.abs(offsetMinutes) % 60 === 0 ? "" : `:${String(Math.abs(offsetMinutes) % 60).padStart(2, "0")}`}`)
103
+ return endParts
104
+ ? {
105
+ type: "datetimerange",
106
+ start_date: startParts.date,
107
+ start_time: startParts.time,
108
+ end_date: endParts.date,
109
+ end_time: endParts.time,
110
+ time_zone: zone,
111
+ reminder,
112
+ }
113
+ : {
114
+ type: "datetime",
115
+ start_date: startParts.date,
116
+ start_time: startParts.time,
117
+ time_zone: zone,
118
+ reminder,
119
+ }
120
+ }
package/src/index.ts CHANGED
@@ -55,6 +55,11 @@ export {
55
55
  type CustomBlockHostOptions,
56
56
  createCustomBlockHost,
57
57
  } from "./createCustomBlockHost.js"
58
+ export {
59
+ type CustomBlockDateValue,
60
+ InvalidNotionDateError,
61
+ toNotionDateValue,
62
+ } from "./dateValue.js"
58
63
  export { toNotionPageId } from "./ids.js"
59
64
  export type { CustomBlockHostManifestOptions } from "./lifecycle/manifestOptions.js"
60
65
  export type {
@@ -5,6 +5,7 @@ import type {
5
5
  notionPageDataSchema,
6
6
  } from "@notionhq/custom-blocks-protocol/pages/page.js"
7
7
  import type { InferOutput } from "valibot"
8
+ import { toNotionDateValue } from "../dateValue.js"
8
9
  import { unreachable } from "../utils.js"
9
10
 
10
11
  export function toLegacyPage(
@@ -78,78 +79,6 @@ function timestamp(
78
79
  }
79
80
  }
80
81
 
81
- function legacyDate(value: {
82
- start: string
83
- end?: string | null
84
- time_zone?: string | null
85
- }): NotionDataSourceValue {
86
- const timed = value.start.includes("T") || value.time_zone != null
87
- const offset = value.start.match(/([+-])(\d{2}):(\d{2})$/)
88
- const offsetMinutes = offset
89
- ? (Number(offset[2]) * 60 + Number(offset[3])) *
90
- (offset[1] === "-" ? -1 : 1)
91
- : 0
92
- const parts = (input: string) => {
93
- if (!input.includes("T")) {
94
- return { date: input, time: "00:00" }
95
- }
96
- const wallClock = input.match(
97
- /^(\d{4}-\d{2}-\d{2})T(\d{2}:\d{2})(?::\d{2}(?:\.\d+)?)?$/,
98
- )
99
- if (wallClock) {
100
- return { date: wallClock[1], time: wallClock[2] }
101
- }
102
- if (!value.time_zone) {
103
- const local = new Date(
104
- new Date(input).getTime() + offsetMinutes * 60_000,
105
- ).toISOString()
106
- return { date: local.slice(0, 10), time: local.slice(11, 16) }
107
- }
108
- const entries = new Intl.DateTimeFormat("en-CA", {
109
- timeZone: value.time_zone,
110
- year: "numeric",
111
- month: "2-digit",
112
- day: "2-digit",
113
- hour: "2-digit",
114
- minute: "2-digit",
115
- hourCycle: "h23",
116
- }).formatToParts(new Date(input))
117
- const get = (type: string) =>
118
- entries.find(part => part.type === type)?.value
119
- return {
120
- date: `${get("year")}-${get("month")}-${get("day")}`,
121
- time: `${get("hour")}:${get("minute")}`,
122
- }
123
- }
124
- const start = parts(value.start)
125
- const end = value.end ? parts(value.end) : undefined
126
- if (!timed) {
127
- return end
128
- ? { type: "daterange", start_date: start.date, end_date: end.date }
129
- : { type: "date", start_date: start.date }
130
- }
131
- const zone =
132
- value.time_zone ??
133
- (offsetMinutes === 0
134
- ? "UTC"
135
- : `UTC${offsetMinutes < 0 ? "-" : "+"}${Math.floor(Math.abs(offsetMinutes) / 60)}${Math.abs(offsetMinutes) % 60 === 0 ? "" : `:${String(Math.abs(offsetMinutes) % 60).padStart(2, "0")}`}`)
136
- return end
137
- ? {
138
- type: "datetimerange",
139
- start_date: start.date,
140
- start_time: start.time,
141
- end_date: end.date,
142
- end_time: end.time,
143
- time_zone: zone,
144
- }
145
- : {
146
- type: "datetime",
147
- start_date: start.date,
148
- start_time: start.time,
149
- time_zone: zone,
150
- }
151
- }
152
-
153
82
  function toLegacyProperty(
154
83
  property: NotionPagePropertyValue,
155
84
  ): NotionDataSourceValue | undefined {
@@ -175,7 +104,7 @@ function toLegacyProperty(
175
104
  case "multi_select":
176
105
  return property.multi_select.flatMap(option => option.name ?? [])
177
106
  case "date":
178
- return property.date ? legacyDate(property.date) : undefined
107
+ return property.date ? toNotionDateValue(property.date) : undefined
179
108
  case "people":
180
109
  return property.people.map(person =>
181
110
  person.object === "group"
@@ -190,6 +119,18 @@ function toLegacyProperty(
190
119
  return property.relation.map(page => ({ id: page.id, table: "block" }))
191
120
  case "files":
192
121
  return property.files.map(file => file.name).join(",")
122
+ case "unique_id":
123
+ return property.unique_id
124
+ case "button":
125
+ return property.button
126
+ case "formula":
127
+ return property.formula
128
+ case "location":
129
+ return property.location
130
+ case "rollup":
131
+ return property.rollup
132
+ case "verification":
133
+ return property.verification
193
134
  case "place":
194
135
  return ""
195
136
  case "created_time":