@notionhq/custom-blocks 0.1.45 → 0.1.47
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.
- package/README.md +3 -4
- package/dist/bridge/SandboxBridge.d.ts +1 -1
- package/dist/bridge/SandboxBridge.d.ts.map +1 -1
- package/dist/bridge/SandboxBridge.js +3 -20
- package/dist/bridge/sandboxClient.d.ts +1 -1
- package/dist/bridge/sandboxClient.d.ts.map +1 -1
- package/dist/bridge/sandboxClient.js +2 -2
- package/dist/customBlock.d.ts +76 -3
- package/dist/customBlock.d.ts.map +1 -1
- package/dist/customBlock.js +120 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/init.d.ts +2 -14
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +7 -30
- package/dist/protocol/index.d.ts +3 -2
- package/dist/protocol/index.js +3 -2
- package/dist/protocol/messages/init.d.ts +2 -2
- package/dist/protocol/messages/isInitHandshakeMessage.d.ts +4 -0
- package/dist/protocol/messages/isInitHandshakeMessage.js +11 -0
- package/dist/protocol/{messagePayload.d.ts → messages/messagePayload.d.ts} +1 -1
- package/dist/react/NotionCustomBlock.d.ts +3 -5
- package/dist/react/NotionCustomBlock.d.ts.map +1 -1
- package/dist/react/NotionCustomBlock.js +3 -4
- package/dist/react/useCustomBlockInit.d.ts +4 -4
- package/dist/react/useCustomBlockInit.d.ts.map +1 -1
- package/dist/react/useCustomBlockInit.js +3 -4
- package/dist/react/useRuntimeState.d.ts +3 -5
- package/dist/react/useRuntimeState.d.ts.map +1 -1
- package/dist/react/useRuntimeState.js +10 -12
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/docs/block-location.md +51 -55
- package/docs/data-sources.md +3 -3
- package/docs/errors.md +1 -1
- package/docs/lifecycle.md +60 -25
- package/docs/users.md +4 -3
- package/package.json +1 -1
- package/src/bridge/SandboxBridge.ts +3 -23
- package/src/bridge/sandboxClient.ts +2 -2
- package/src/customBlock.ts +139 -3
- package/src/index.ts +0 -1
- package/src/init.ts +7 -52
- package/src/react/NotionCustomBlock.tsx +4 -6
- package/src/react/useCustomBlockInit.ts +5 -12
- package/src/react/useRuntimeState.ts +25 -12
- package/src/types.ts +1 -1
- /package/dist/protocol/{incomingType.d.ts → messages/incomingType.d.ts} +0 -0
- /package/dist/protocol/{incomingType.js → messages/incomingType.js} +0 -0
- /package/dist/protocol/{messagePayload.js → messages/messagePayload.js} +0 -0
package/docs/users.md
CHANGED
|
@@ -29,14 +29,15 @@ For non-React renderers, read the same state through `customBlock` after initial
|
|
|
29
29
|
|
|
30
30
|
```ts
|
|
31
31
|
await initCustomBlock()
|
|
32
|
-
const me = customBlock.getCurrentUser()
|
|
33
32
|
|
|
34
|
-
const unsubscribe = customBlock.
|
|
35
|
-
const nextMe = customBlock.getCurrentUser()
|
|
33
|
+
const unsubscribe = customBlock.subscribeToCurrentUser(nextMe => {
|
|
36
34
|
// Update your renderer with nextMe.
|
|
37
35
|
})
|
|
38
36
|
```
|
|
39
37
|
|
|
38
|
+
Call `unsubscribe()` when your renderer is removed. The callback receives the initial profile and later changes.
|
|
39
|
+
See [context subscriptions](./block-location.md#reading-values-and-following-changes) for initialization and equality rules.
|
|
40
|
+
|
|
40
41
|
## Listing users
|
|
41
42
|
|
|
42
43
|
`users.list(input?)` returns workspace users visible to the current custom block, mirroring Notion's [`GET /v1/users`](https://developers.notion.com/reference/get-users) shape.
|
package/package.json
CHANGED
|
@@ -5,7 +5,6 @@ import type {
|
|
|
5
5
|
NotionDataSourceBindings,
|
|
6
6
|
} from "@notionhq/custom-blocks-protocol/dataSources/dataSource.js"
|
|
7
7
|
import type { NotionPageId } from "@notionhq/custom-blocks-protocol/ids.js"
|
|
8
|
-
import { readIncomingType } from "@notionhq/custom-blocks-protocol/incomingType.js"
|
|
9
8
|
import type { CustomBlockManifest } from "@notionhq/custom-blocks-protocol/manifest.js"
|
|
10
9
|
import type { ConnectMessage } from "@notionhq/custom-blocks-protocol/messages/connect.js"
|
|
11
10
|
import type {
|
|
@@ -18,6 +17,7 @@ import {
|
|
|
18
17
|
hostToSandboxMessageSchema,
|
|
19
18
|
type PrimeableHostToSandboxMessage,
|
|
20
19
|
} from "@notionhq/custom-blocks-protocol/messages/hostToSandbox.js"
|
|
20
|
+
import { readIncomingType } from "@notionhq/custom-blocks-protocol/messages/incomingType.js"
|
|
21
21
|
import {
|
|
22
22
|
CustomBlockInitializationError,
|
|
23
23
|
type InitMessage,
|
|
@@ -173,28 +173,8 @@ export class SandboxBridge {
|
|
|
173
173
|
return () => this.messageLogListeners.delete(listener)
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
awaitInit(
|
|
177
|
-
|
|
178
|
-
return this.initMessage
|
|
179
|
-
}
|
|
180
|
-
return new Promise((resolve, reject) => {
|
|
181
|
-
if (signal.aborted) {
|
|
182
|
-
reject(signal.reason)
|
|
183
|
-
return
|
|
184
|
-
}
|
|
185
|
-
const onAbort = () => reject(signal.reason)
|
|
186
|
-
signal.addEventListener("abort", onAbort, { once: true })
|
|
187
|
-
this.initMessage.then(
|
|
188
|
-
() => {
|
|
189
|
-
signal.removeEventListener("abort", onAbort)
|
|
190
|
-
resolve()
|
|
191
|
-
},
|
|
192
|
-
err => {
|
|
193
|
-
signal.removeEventListener("abort", onAbort)
|
|
194
|
-
reject(err)
|
|
195
|
-
},
|
|
196
|
-
)
|
|
197
|
-
})
|
|
176
|
+
awaitInit(): Promise<void> {
|
|
177
|
+
return this.initMessage
|
|
198
178
|
}
|
|
199
179
|
|
|
200
180
|
sendConnect(manifestResult: ManifestLoadResult) {
|
|
@@ -42,8 +42,8 @@ export const customBlockHost = {
|
|
|
42
42
|
getBridge().sendConnect(manifestResult)
|
|
43
43
|
},
|
|
44
44
|
|
|
45
|
-
awaitInit: (
|
|
46
|
-
return getBridge().awaitInit(
|
|
45
|
+
awaitInit: (): Promise<void> => {
|
|
46
|
+
return getBridge().awaitInit()
|
|
47
47
|
},
|
|
48
48
|
|
|
49
49
|
subscribe: (listener: () => void) => {
|
package/src/customBlock.ts
CHANGED
|
@@ -12,8 +12,10 @@ import type {
|
|
|
12
12
|
CustomBlockHostState,
|
|
13
13
|
InitializedHostState,
|
|
14
14
|
} from "./bridge/hostState.js"
|
|
15
|
+
import { notifyListener } from "./bridge/notifyListener.js"
|
|
15
16
|
import { customBlockHost } from "./bridge/sandboxClient.js"
|
|
16
17
|
|
|
18
|
+
/** @deprecated Use individual context types and getters after `initCustomBlock` resolves. */
|
|
17
19
|
export type CustomBlockState =
|
|
18
20
|
| {
|
|
19
21
|
status: "uninitialized"
|
|
@@ -31,47 +33,144 @@ export type CustomBlockState =
|
|
|
31
33
|
dataSources: NotionDataSource[]
|
|
32
34
|
}
|
|
33
35
|
|
|
36
|
+
type Unsubscribe = () => void
|
|
37
|
+
|
|
34
38
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
39
|
+
* Read block and app context with getters, or follow changes with subscriptions.
|
|
40
|
+
* Getters throw if called before `initCustomBlock()` completes. Subscriptions
|
|
41
|
+
* can be registered before initialization and deliver the initial value once
|
|
42
|
+
* initialization completes, followed by changes to that value.
|
|
43
|
+
*
|
|
44
|
+
* Each subscription returns an unsubscribe function. Call it when the
|
|
45
|
+
* subscription is no longer needed.
|
|
37
46
|
*/
|
|
38
47
|
export const customBlock = {
|
|
39
|
-
|
|
48
|
+
/** @deprecated Use an individual subscription method, such as `subscribeToTheme()` or `subscribeToCurrentUser()`. */
|
|
49
|
+
subscribe(listener: () => void): Unsubscribe {
|
|
40
50
|
return customBlockHost.subscribe(listener)
|
|
41
51
|
},
|
|
42
52
|
|
|
53
|
+
/** @deprecated Use an individual getter method, such as `getTheme()` or `getCurrentUser()`, after `initCustomBlock()` resolves. */
|
|
43
54
|
getState(): CustomBlockState {
|
|
44
55
|
return toPublicState(customBlockHost.getState())
|
|
45
56
|
},
|
|
46
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Get the current user viewing the block.
|
|
60
|
+
* Throws if called before SDK initialization completes.
|
|
61
|
+
*/
|
|
47
62
|
getCurrentUser(): NotionUser {
|
|
48
63
|
return getInitializedHostState("getCurrentUser").currentUser
|
|
49
64
|
},
|
|
50
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Subscribes to the current `currentUser` and future changes.
|
|
68
|
+
* Waits for initialization before delivering the initial value.
|
|
69
|
+
*/
|
|
70
|
+
subscribeToCurrentUser(listener: (value: NotionUser) => void): Unsubscribe {
|
|
71
|
+
return subscribeToContext({ key: "currentUser", listener })
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get the host's current theme.
|
|
76
|
+
* Throws if called before SDK initialization completes.
|
|
77
|
+
*/
|
|
51
78
|
getTheme(): NotionTheme {
|
|
52
79
|
return getInitializedHostState("getTheme").theme
|
|
53
80
|
},
|
|
54
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Subscribes to the current `theme` and future changes.
|
|
84
|
+
* Waits for initialization before delivering the initial value.
|
|
85
|
+
*/
|
|
86
|
+
subscribeToTheme(listener: (value: NotionTheme) => void): Unsubscribe {
|
|
87
|
+
return subscribeToContext({ key: "theme", listener })
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Get the host's current contrast mode.
|
|
92
|
+
* Throws if called before SDK initialization completes.
|
|
93
|
+
*/
|
|
55
94
|
getContrastMode(): NotionContrastMode {
|
|
56
95
|
return getInitializedHostState("getContrastMode").contrastMode
|
|
57
96
|
},
|
|
58
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Subscribes to the current `contrastMode` and future changes.
|
|
100
|
+
* Waits for initialization before delivering the initial value.
|
|
101
|
+
*/
|
|
102
|
+
subscribeToContrastMode(
|
|
103
|
+
listener: (value: NotionContrastMode) => void,
|
|
104
|
+
): Unsubscribe {
|
|
105
|
+
return subscribeToContext({ key: "contrastMode", listener })
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Get the custom block's ID. The ID is stable for the lifetime of the block.
|
|
110
|
+
* Throws if called before SDK initialization completes.
|
|
111
|
+
*/
|
|
59
112
|
getBlockId(): NotionBlockId {
|
|
60
113
|
return getInitializedHostState("getBlockId").blockId
|
|
61
114
|
},
|
|
62
115
|
|
|
116
|
+
/**
|
|
117
|
+
* Delivers the block ID once. The ID is stable for the lifetime of the block.
|
|
118
|
+
* Waits for initialization before delivering the initial value.
|
|
119
|
+
*/
|
|
120
|
+
subscribeToBlockId(listener: (value: NotionBlockId) => void): Unsubscribe {
|
|
121
|
+
return subscribeToContext({ key: "blockId", listener })
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get the custom block's parent.
|
|
126
|
+
* Throws if called before SDK initialization completes.
|
|
127
|
+
*/
|
|
63
128
|
getParent(): NotionParent {
|
|
64
129
|
return getInitializedHostState("getParent").parent
|
|
65
130
|
},
|
|
66
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Subscribes to the current `parent` and future changes.
|
|
134
|
+
* Waits for initialization before delivering the initial value.
|
|
135
|
+
*/
|
|
136
|
+
subscribeToParent(listener: (value: NotionParent) => void): Unsubscribe {
|
|
137
|
+
return subscribeToContext({ key: "parent", listener })
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Get the containing page and its parent.
|
|
142
|
+
* Throws if called before SDK initialization completes.
|
|
143
|
+
*/
|
|
67
144
|
getPage(): CustomBlockPage {
|
|
68
145
|
return getInitializedHostState("getPage").page
|
|
69
146
|
},
|
|
70
147
|
|
|
148
|
+
/**
|
|
149
|
+
* Subscribes to the current `page` and future changes.
|
|
150
|
+
* Waits for initialization before delivering the initial value.
|
|
151
|
+
*/
|
|
152
|
+
subscribeToPage(listener: (value: CustomBlockPage) => void): Unsubscribe {
|
|
153
|
+
return subscribeToContext({ key: "page", listener })
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get the block's declared data source manifest.
|
|
158
|
+
* Throws if called before SDK initialization completes.
|
|
159
|
+
*/
|
|
71
160
|
getManifest(): CustomBlockManifest {
|
|
72
161
|
return getInitializedHostState("getManifest").manifest
|
|
73
162
|
},
|
|
74
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Subscribes to the current `manifest` and future changes.
|
|
166
|
+
* Waits for initialization before delivering the initial value.
|
|
167
|
+
*/
|
|
168
|
+
subscribeToManifest(
|
|
169
|
+
listener: (value: CustomBlockManifest) => void,
|
|
170
|
+
): Unsubscribe {
|
|
171
|
+
return subscribeToContext({ key: "manifest", listener })
|
|
172
|
+
},
|
|
173
|
+
|
|
75
174
|
autoResize,
|
|
76
175
|
|
|
77
176
|
/**
|
|
@@ -119,3 +218,40 @@ function getInitializedHostState(methodName: string): InitializedHostState {
|
|
|
119
218
|
}
|
|
120
219
|
return hostState
|
|
121
220
|
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Subscribe to one context value. Wait for initialization before delivering
|
|
224
|
+
* the initial value, then deliver updates when that value changes.
|
|
225
|
+
* Return a function that stops delivery, including before initialization.
|
|
226
|
+
*/
|
|
227
|
+
function subscribeToContext<K extends keyof InitializedHostState>(args: {
|
|
228
|
+
key: K
|
|
229
|
+
listener: (value: InitializedHostState[K]) => void
|
|
230
|
+
}): Unsubscribe {
|
|
231
|
+
const { key, listener } = args
|
|
232
|
+
let active = true
|
|
233
|
+
let delivered = false
|
|
234
|
+
let previous: InitializedHostState[K] | undefined
|
|
235
|
+
const deliverIfChanged = () => {
|
|
236
|
+
const state = customBlockHost.getState()
|
|
237
|
+
if (!active || state.status !== "initialized") {
|
|
238
|
+
return
|
|
239
|
+
}
|
|
240
|
+
const next = state[key]
|
|
241
|
+
// Use referential equality to keep the implementation simple.
|
|
242
|
+
// Callbacks can receive different objects with the same contents.
|
|
243
|
+
// We have not benchmarked this choice. Deep equality may also be suitable.
|
|
244
|
+
if (delivered && Object.is(previous, next)) {
|
|
245
|
+
return
|
|
246
|
+
}
|
|
247
|
+
previous = next
|
|
248
|
+
delivered = true
|
|
249
|
+
notifyListener(() => listener(next))
|
|
250
|
+
}
|
|
251
|
+
const unsubscribe = customBlockHost.subscribe(deliverIfChanged)
|
|
252
|
+
deliverIfChanged()
|
|
253
|
+
return () => {
|
|
254
|
+
active = false
|
|
255
|
+
unsubscribe()
|
|
256
|
+
}
|
|
257
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -101,7 +101,6 @@ export { pages, users } from "./bridge/sandboxClient.js"
|
|
|
101
101
|
export { type CustomBlockState, customBlock } from "./customBlock.js"
|
|
102
102
|
export {
|
|
103
103
|
type CustomBlockInitPayload,
|
|
104
|
-
type InitCustomBlockOptions,
|
|
105
104
|
initCustomBlock,
|
|
106
105
|
NotInIframeError,
|
|
107
106
|
} from "./init.js"
|
package/src/init.ts
CHANGED
|
@@ -42,21 +42,6 @@ export class NotInIframeError extends CustomBlockInitializationError {
|
|
|
42
42
|
declare code: "not_in_iframe"
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
/**
|
|
46
|
-
* Options for {@link initCustomBlock}.
|
|
47
|
-
*/
|
|
48
|
-
export type InitCustomBlockOptions = {
|
|
49
|
-
/**
|
|
50
|
-
* How long to wait for the host's `init` response before rejecting with an error.
|
|
51
|
-
*
|
|
52
|
-
* @default 15000 - Intentionally longer than Notion's 10s host-dependency watchdog
|
|
53
|
-
* so host-owned init errors can arrive before the SDK's generic fallback appears.
|
|
54
|
-
*/
|
|
55
|
-
timeoutMs?: number
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const DEFAULT_INIT_TIMEOUT_MS = 15_000
|
|
59
|
-
|
|
60
45
|
const NOT_IN_IFRAME_MESSAGE =
|
|
61
46
|
"<NotionCustomBlock> only works inside an iframe — use the dev shell or deploy to Notion."
|
|
62
47
|
|
|
@@ -69,15 +54,13 @@ let initPromise: Promise<CustomBlockInitPayload> | undefined
|
|
|
69
54
|
* user, data sources) once the host has initialized the block. Rejects with a
|
|
70
55
|
* `CustomBlockInitializationError` if initialization fails.
|
|
71
56
|
*
|
|
72
|
-
* Idempotent: subsequent calls return the same promise as the first
|
|
57
|
+
* Idempotent: subsequent calls return the same promise as the first.
|
|
73
58
|
* Mount your React tree (or call any SDK hook) only after the returned promise resolves.
|
|
74
59
|
*/
|
|
75
|
-
export function initCustomBlock(
|
|
76
|
-
opts: InitCustomBlockOptions = {},
|
|
77
|
-
): Promise<CustomBlockInitPayload> {
|
|
60
|
+
export function initCustomBlock(): Promise<CustomBlockInitPayload> {
|
|
78
61
|
if (initPromise === undefined) {
|
|
79
62
|
customBlockHost.start()
|
|
80
|
-
initPromise = performHandshake(
|
|
63
|
+
initPromise = performHandshake()
|
|
81
64
|
}
|
|
82
65
|
return initPromise
|
|
83
66
|
}
|
|
@@ -93,13 +76,11 @@ export function initCustomBlock(
|
|
|
93
76
|
* Resolves with the `init` payload after the process above. Rejects with a
|
|
94
77
|
* `CustomBlockInitializationError` if any step fails.
|
|
95
78
|
*/
|
|
96
|
-
async function performHandshake(
|
|
97
|
-
opts: InitCustomBlockOptions,
|
|
98
|
-
): Promise<CustomBlockInitPayload> {
|
|
79
|
+
async function performHandshake(): Promise<CustomBlockInitPayload> {
|
|
99
80
|
try {
|
|
100
81
|
// Fail fast with a typed error when rendered as a standalone tab and not in a parent frame.
|
|
101
|
-
// Otherwise,
|
|
102
|
-
//
|
|
82
|
+
// Otherwise, `postMessage` to `window.parent` would just hit the same window and never
|
|
83
|
+
// arrive.
|
|
103
84
|
if (typeof window !== "undefined" && window.parent === window) {
|
|
104
85
|
throw new NotInIframeError()
|
|
105
86
|
}
|
|
@@ -108,8 +89,7 @@ async function performHandshake(
|
|
|
108
89
|
const manifestResult = await attemptToLoadSelfHostedManifest()
|
|
109
90
|
customBlockHost.sendConnect(manifestResult)
|
|
110
91
|
|
|
111
|
-
|
|
112
|
-
await awaitHostInitWithTimeout(timeoutMs)
|
|
92
|
+
await customBlockHost.awaitInit()
|
|
113
93
|
|
|
114
94
|
const hostState = customBlockHost.getState()
|
|
115
95
|
switch (hostState.status) {
|
|
@@ -144,28 +124,3 @@ async function performHandshake(
|
|
|
144
124
|
})
|
|
145
125
|
}
|
|
146
126
|
}
|
|
147
|
-
|
|
148
|
-
async function awaitHostInitWithTimeout(timeoutMs: number): Promise<void> {
|
|
149
|
-
try {
|
|
150
|
-
await customBlockHost.awaitInit(AbortSignal.timeout(timeoutMs))
|
|
151
|
-
} catch (error) {
|
|
152
|
-
if (isTimeoutError(error)) {
|
|
153
|
-
throw new CustomBlockInitializationError({
|
|
154
|
-
code: "init_timeout",
|
|
155
|
-
message:
|
|
156
|
-
"Host did not respond to the `connect` message before the timeout.",
|
|
157
|
-
isRetryable: true,
|
|
158
|
-
})
|
|
159
|
-
}
|
|
160
|
-
throw error
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
function isTimeoutError(error: unknown): boolean {
|
|
165
|
-
return (
|
|
166
|
-
typeof error === "object" &&
|
|
167
|
-
error !== null &&
|
|
168
|
-
"name" in error &&
|
|
169
|
-
(error.name === "TimeoutError" || error.name === "AbortError")
|
|
170
|
-
)
|
|
171
|
-
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CustomBlockInitializationError } from "@notionhq/custom-blocks-protocol/messages/init.js"
|
|
2
2
|
import { type ReactNode, useEffect, useLayoutEffect, useState } from "react"
|
|
3
3
|
import { customBlockHost } from "../bridge/sandboxClient.js"
|
|
4
|
-
import {
|
|
4
|
+
import { NotInIframeError } from "../init.js"
|
|
5
5
|
import { DebugMessageLog } from "./DebugMessageLog.js"
|
|
6
6
|
import { seedStandalonePreviewState } from "./standalonePreview.js"
|
|
7
7
|
import { useCustomBlockAutoResize } from "./useCustomBlockAutoResize.js"
|
|
@@ -12,7 +12,7 @@ import "./NotionCustomBlock.css"
|
|
|
12
12
|
/**
|
|
13
13
|
* Props accepted by {@link NotionCustomBlock}.
|
|
14
14
|
*/
|
|
15
|
-
export type NotionCustomBlockProps =
|
|
15
|
+
export type NotionCustomBlockProps = {
|
|
16
16
|
children: ReactNode
|
|
17
17
|
/**
|
|
18
18
|
* Rendered while the SDK ↔ host handshake is in progress. Defaults to
|
|
@@ -37,8 +37,7 @@ export type NotionCustomBlockProps = InitCustomBlockOptions & {
|
|
|
37
37
|
|
|
38
38
|
/**
|
|
39
39
|
* Top-level wrapper that runs the SDK ↔ host handshake and gates `children`
|
|
40
|
-
* until it resolves.
|
|
41
|
-
* {@link initCustomBlock}.
|
|
40
|
+
* until it resolves.
|
|
42
41
|
*
|
|
43
42
|
* Templates that prefer not to write a `Root` gating component (or top-level
|
|
44
43
|
* `await`) can mount their app entirely inside this provider:
|
|
@@ -56,12 +55,11 @@ export type NotionCustomBlockProps = InitCustomBlockOptions & {
|
|
|
56
55
|
*/
|
|
57
56
|
export function NotionCustomBlock({
|
|
58
57
|
children,
|
|
59
|
-
timeoutMs,
|
|
60
58
|
fallback = null,
|
|
61
59
|
errorFallback,
|
|
62
60
|
autoResize = true,
|
|
63
61
|
}: NotionCustomBlockProps) {
|
|
64
|
-
const init = useCustomBlockInit(
|
|
62
|
+
const init = useCustomBlockInit()
|
|
65
63
|
useCustomBlockAutoResize({ enabled: init.isLoaded && autoResize })
|
|
66
64
|
// True if the block has no host (i.e. it's running in standalone preview).
|
|
67
65
|
const isStandalone = init.error instanceof NotInIframeError
|
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import type { CustomBlockInitializationError } from "@notionhq/custom-blocks-protocol/messages/init.js"
|
|
2
2
|
import { useEffect, useState } from "react"
|
|
3
|
-
import {
|
|
4
|
-
type CustomBlockInitPayload,
|
|
5
|
-
type InitCustomBlockOptions,
|
|
6
|
-
initCustomBlock,
|
|
7
|
-
} from "../init.js"
|
|
3
|
+
import { type CustomBlockInitPayload, initCustomBlock } from "../init.js"
|
|
8
4
|
|
|
9
5
|
/**
|
|
10
6
|
* Discriminated state returned by {@link useCustomBlockInit}.
|
|
11
7
|
*
|
|
12
8
|
* Branch on `isLoaded`/`error`:
|
|
13
9
|
* - `{ isLoaded: false, error: undefined }` — handshake in progress.
|
|
14
|
-
* - `{ isLoaded: false, error: CustomBlockInitializationError }` — handshake failed
|
|
15
|
-
*
|
|
10
|
+
* - `{ isLoaded: false, error: CustomBlockInitializationError }` — handshake failed because
|
|
11
|
+
* the host or SDK reported a terminal initialization error.
|
|
16
12
|
* - `{ isLoaded: true, initial }` — handshake complete; safe to render
|
|
17
13
|
* children that call `useTheme`, `useBlockId`, etc.
|
|
18
14
|
*/
|
|
@@ -38,17 +34,14 @@ export type UseCustomBlockInitResult =
|
|
|
38
34
|
* return <App />
|
|
39
35
|
* }
|
|
40
36
|
*/
|
|
41
|
-
export function useCustomBlockInit(
|
|
42
|
-
opts?: InitCustomBlockOptions,
|
|
43
|
-
): UseCustomBlockInitResult {
|
|
37
|
+
export function useCustomBlockInit(): UseCustomBlockInitResult {
|
|
44
38
|
const [state, setState] = useState<UseCustomBlockInitResult>({
|
|
45
39
|
isLoaded: false,
|
|
46
40
|
error: undefined,
|
|
47
41
|
})
|
|
48
|
-
// biome-ignore lint/correctness/useExhaustiveDependencies(opts): Initialization caches its first options and promise. Later options are ignored, so changes should not resubscribe to the same promise.
|
|
49
42
|
useEffect(() => {
|
|
50
43
|
let cancelled = false
|
|
51
|
-
initCustomBlock(
|
|
44
|
+
initCustomBlock().then(
|
|
52
45
|
initial => {
|
|
53
46
|
if (!cancelled) {
|
|
54
47
|
setState({ isLoaded: true, error: undefined, initial })
|
|
@@ -17,7 +17,10 @@ import { customBlock } from "../customBlock.js"
|
|
|
17
17
|
* const blockId = useBlockId()
|
|
18
18
|
*/
|
|
19
19
|
export function useBlockId(): NotionBlockId {
|
|
20
|
-
return useSyncExternalStore(
|
|
20
|
+
return useSyncExternalStore(
|
|
21
|
+
customBlock.subscribeToBlockId,
|
|
22
|
+
customBlock.getBlockId,
|
|
23
|
+
)
|
|
21
24
|
}
|
|
22
25
|
|
|
23
26
|
/**
|
|
@@ -30,7 +33,10 @@ export function useBlockId(): NotionBlockId {
|
|
|
30
33
|
* const parent = useParent()
|
|
31
34
|
*/
|
|
32
35
|
export function useParent(): NotionParent {
|
|
33
|
-
return useSyncExternalStore(
|
|
36
|
+
return useSyncExternalStore(
|
|
37
|
+
customBlock.subscribeToParent,
|
|
38
|
+
customBlock.getParent,
|
|
39
|
+
)
|
|
34
40
|
}
|
|
35
41
|
|
|
36
42
|
/**
|
|
@@ -43,11 +49,11 @@ export function useParent(): NotionParent {
|
|
|
43
49
|
* const page = usePage()
|
|
44
50
|
*/
|
|
45
51
|
export function usePage(): CustomBlockPage {
|
|
46
|
-
return useSyncExternalStore(customBlock.
|
|
52
|
+
return useSyncExternalStore(customBlock.subscribeToPage, customBlock.getPage)
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
/**
|
|
50
|
-
* Returns the host's current theme. Re-renders
|
|
56
|
+
* Returns the host's current theme. Re-renders when the theme changes.
|
|
51
57
|
*
|
|
52
58
|
* Throws if called before `initCustomBlock` has resolved.
|
|
53
59
|
*
|
|
@@ -55,12 +61,14 @@ export function usePage(): CustomBlockPage {
|
|
|
55
61
|
* const theme = useTheme()
|
|
56
62
|
*/
|
|
57
63
|
export function useTheme(): NotionTheme {
|
|
58
|
-
return useSyncExternalStore(
|
|
64
|
+
return useSyncExternalStore(
|
|
65
|
+
customBlock.subscribeToTheme,
|
|
66
|
+
customBlock.getTheme,
|
|
67
|
+
)
|
|
59
68
|
}
|
|
60
69
|
|
|
61
70
|
/**
|
|
62
|
-
* Returns the host's contrast preference. Re-renders
|
|
63
|
-
* `contrastModeChanged` message from the host.
|
|
71
|
+
* Returns the host's contrast preference. Re-renders when the contrast preference changes.
|
|
64
72
|
*
|
|
65
73
|
* Throws if called before `initCustomBlock` has resolved.
|
|
66
74
|
*
|
|
@@ -69,14 +77,13 @@ export function useTheme(): NotionTheme {
|
|
|
69
77
|
*/
|
|
70
78
|
export function useContrastMode(): NotionContrastMode {
|
|
71
79
|
return useSyncExternalStore(
|
|
72
|
-
customBlock.
|
|
80
|
+
customBlock.subscribeToContrastMode,
|
|
73
81
|
customBlock.getContrastMode,
|
|
74
82
|
)
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
/**
|
|
78
|
-
* Returns the viewing user's Notion profile. Re-renders
|
|
79
|
-
* `currentUserChanged` message.
|
|
86
|
+
* Returns the viewing user's Notion profile. Re-renders when the profile data changes.
|
|
80
87
|
*
|
|
81
88
|
* Throws if called before `initCustomBlock` has resolved. `await` it before mounting.
|
|
82
89
|
*
|
|
@@ -85,7 +92,10 @@ export function useContrastMode(): NotionContrastMode {
|
|
|
85
92
|
* console.log(me.id, me.name, me.person.email)
|
|
86
93
|
*/
|
|
87
94
|
export function useCurrentUser(): NotionUser {
|
|
88
|
-
return useSyncExternalStore(
|
|
95
|
+
return useSyncExternalStore(
|
|
96
|
+
customBlock.subscribeToCurrentUser,
|
|
97
|
+
customBlock.getCurrentUser,
|
|
98
|
+
)
|
|
89
99
|
}
|
|
90
100
|
|
|
91
101
|
/**
|
|
@@ -101,5 +111,8 @@ export function useCurrentUser(): NotionUser {
|
|
|
101
111
|
* resolved property IDs and schemas.
|
|
102
112
|
*/
|
|
103
113
|
export function useManifest(): CustomBlockManifest {
|
|
104
|
-
return useSyncExternalStore(
|
|
114
|
+
return useSyncExternalStore(
|
|
115
|
+
customBlock.subscribeToManifest,
|
|
116
|
+
customBlock.getManifest,
|
|
117
|
+
)
|
|
105
118
|
}
|
package/src/types.ts
CHANGED
|
@@ -5,7 +5,6 @@ import type {
|
|
|
5
5
|
NotionDataSourceId,
|
|
6
6
|
NotionPageId,
|
|
7
7
|
} from "@notionhq/custom-blocks-protocol/ids.js"
|
|
8
|
-
import type { BridgeMessagePayload } from "@notionhq/custom-blocks-protocol/messagePayload.js"
|
|
9
8
|
import type { NotionCreatePagePosition } from "@notionhq/custom-blocks-protocol/messages/createPage.js"
|
|
10
9
|
import type {
|
|
11
10
|
CreatePageResultMessage,
|
|
@@ -24,6 +23,7 @@ import type {
|
|
|
24
23
|
ListUsersMessage,
|
|
25
24
|
ListUsersResultMessage,
|
|
26
25
|
} from "@notionhq/custom-blocks-protocol/messages/listUsers.js"
|
|
26
|
+
import type { BridgeMessagePayload } from "@notionhq/custom-blocks-protocol/messages/messagePayload.js"
|
|
27
27
|
import type {
|
|
28
28
|
CustomBlockCheckboxFilterOperator,
|
|
29
29
|
CustomBlockContainsFilterOperator,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|