@storypane/sdk 0.1.0-rc.2 → 0.1.0-rc.3
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 +87 -9
- package/dist/preview.d.ts +2 -0
- package/dist/preview.js +43 -0
- package/dist/react.d.ts +1 -1
- package/dist/react.js +5 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,15 +12,24 @@ Start with the [Next.js integration guide](https://github.com/lkuich/storypane/b
|
|
|
12
12
|
server-side fetching, typed block components, and a separate live preview route.
|
|
13
13
|
An [Astro example](https://github.com/lkuich/storypane/blob/main/apps/example-astro/README.md) is also included.
|
|
14
14
|
|
|
15
|
-
The current version is **0.1.0-rc.
|
|
16
|
-
[matching GitHub prerelease](https://github.com/lkuich/storypane/releases), or build
|
|
17
|
-
from this monorepo with `pnpm --filter @storypane/sdk build` and pack/link
|
|
18
|
-
`packages/sdk`. npm publication is separately gated on publisher setup; when
|
|
19
|
-
available, select the explicit RC version or `@storypane/sdk@rc`. See the
|
|
20
|
-
[release guide](https://storypane.dev/docs/releases-and-versioning/). Install the release artifact without waiting for npm publication:
|
|
15
|
+
The current version is **0.1.0-rc.3**, published on npm as a release candidate:
|
|
21
16
|
|
|
22
17
|
```sh
|
|
23
|
-
npm install
|
|
18
|
+
npm install @storypane/sdk@0.1.0-rc.3
|
|
19
|
+
# Or follow the candidate channel:
|
|
20
|
+
npm install @storypane/sdk@rc
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Pin the explicit version for reproducible builds. npm also assigned `latest` to
|
|
24
|
+
this first publication; that tag does not make it stable. See the
|
|
25
|
+
[release guide](https://storypane.dev/docs/releases-and-versioning/) for status,
|
|
26
|
+
upgrades, and known limits.
|
|
27
|
+
|
|
28
|
+
You can also build from this monorepo with `pnpm --filter @storypane/sdk build`
|
|
29
|
+
and pack/link `packages/sdk`, or install the verified GitHub archive:
|
|
30
|
+
|
|
31
|
+
```sh
|
|
32
|
+
npm install https://github.com/lkuich/storypane/releases/download/v0.1.0-rc.3/storypane-sdk-0.1.0-rc.3.tgz
|
|
24
33
|
```
|
|
25
34
|
|
|
26
35
|
Entrypoints:
|
|
@@ -42,6 +51,12 @@ The root locale contributes an empty path segment; the editor removes doubled
|
|
|
42
51
|
slashes. `{slug}` is the selected locale's URL slug. Query placeholders also work:
|
|
43
52
|
`https://site.example/preview?slug={slug}&locale={locale}`.
|
|
44
53
|
|
|
54
|
+
Admins set the **Space default** preview URL. Each member can save **My default
|
|
55
|
+
preview** in the same settings screen, or choose **Use space default**. This
|
|
56
|
+
preference follows the user across browsers in that space. Editor and component
|
|
57
|
+
previews use it unless another URL is selected in the editor. Server-created
|
|
58
|
+
preview sessions continue to use the space default unless `previewId` is provided.
|
|
59
|
+
|
|
45
60
|
The default preview parameter is `storypane_preview=1`. You can change its name in
|
|
46
61
|
settings; use the same name in your SDK options. Your app must permit the CMS
|
|
47
62
|
origin in its CSP `frame-ancestors` policy and must not send a conflicting
|
|
@@ -174,6 +189,61 @@ The [complete Next.js example](https://github.com/lkuich/storypane/blob/main/app
|
|
|
174
189
|
It keeps published pages on the server and reuses the renderer inside `/preview`.
|
|
175
190
|
Client preview components must not import server-only fetch modules.
|
|
176
191
|
|
|
192
|
+
### Block hover previews
|
|
193
|
+
|
|
194
|
+
Component previews are available starting with `@storypane/sdk@0.1.0-rc.3`.
|
|
195
|
+
|
|
196
|
+
The editor can preview saved blocks and the add-block picker's sample content in
|
|
197
|
+
one temporary iframe after a short hover or keyboard focus. It uses the selected
|
|
198
|
+
preview URL and the same authenticated, read-only flow as the full preview.
|
|
199
|
+
Escape dismisses the preview. Nothing is inserted or saved by hovering.
|
|
200
|
+
|
|
201
|
+
Opt in by passing a stable `onComponent` callback to `useStorypanePreview`,
|
|
202
|
+
`useStorypaneStory`, or `connectPreview`. Render that block alone with your existing
|
|
203
|
+
component renderer, without page navigation or preview status chrome:
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
const [component, setComponent] = useState<StorypaneComponent>();
|
|
207
|
+
const { story } = useStorypanePreview<StorypaneComponent>({
|
|
208
|
+
cmsUrl,
|
|
209
|
+
spaceId,
|
|
210
|
+
story: { slug, locale },
|
|
211
|
+
onComponent: setComponent,
|
|
212
|
+
});
|
|
213
|
+
// Keep passing the story's references and other renderer context as usual.
|
|
214
|
+
if (story) return <Block block={component ?? story.content} />;
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Use the generated `StorypaneComponent` union here: picker previews include nestable
|
|
218
|
+
blocks as well as content types. Both example apps demonstrate this integration.
|
|
219
|
+
Picker previews are opt-in per definition: set **Config → Picker preview data (JSON)**
|
|
220
|
+
and deploy the schema migration. This stores an optional `previewData` object with
|
|
221
|
+
sample field values; nested blocks use `{ _uid, component, ...fields }`. Use real
|
|
222
|
+
representative content, including the nested items your renderer needs. A definition
|
|
223
|
+
without sample data has no picker preview. Samples override defaults for this iframe
|
|
224
|
+
only; inserting a block still uses its normal field defaults.
|
|
225
|
+
|
|
226
|
+
For example, a feature grid can have this preview data:
|
|
227
|
+
|
|
228
|
+
```json
|
|
229
|
+
{
|
|
230
|
+
"title": "A fresh perspective",
|
|
231
|
+
"items": [
|
|
232
|
+
{ "_uid": "preview-feature", "component": "feature", "title": "Start with a good question." }
|
|
233
|
+
]
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Samples are validated against field types and allowed nested blocks, limited to
|
|
238
|
+
100 KB per definition, and included in full schema migrations. Referenced original
|
|
239
|
+
media follows the existing clone/sync/promotion dependency review. They are excluded
|
|
240
|
+
from delivery schema metadata and generated component types. Both examples keep
|
|
241
|
+
editable fixtures in `storypane/preview-data.json`; the Astro setup script installs
|
|
242
|
+
them into newly seeded spaces. For an existing space, copy each fixture into that
|
|
243
|
+
block's Config tab and deploy the migration.
|
|
244
|
+
|
|
245
|
+
Existing integrations without `onComponent` keep full-page preview and show a setup message for hover previews.
|
|
246
|
+
|
|
177
247
|
If you prefer preview on an existing published route, `useStorypaneStory(initial,
|
|
178
248
|
options)` remains available. It starts with the publication and activates drafts
|
|
179
249
|
only when its `mode` matches. For Next.js Draft Mode, pass the server's `isEnabled`
|
|
@@ -642,7 +712,15 @@ authenticated `getAsset` integration for static export when public media is disa
|
|
|
642
712
|
|
|
643
713
|
## MCP
|
|
644
714
|
|
|
645
|
-
|
|
646
|
-
|
|
715
|
+
Launch the pinned package directly (Node 20+), with `STORYPANE_URL`,
|
|
716
|
+
`STORYPANE_SPACE_ID`, and a private `STORYPANE_MANAGEMENT_TOKEN` configured in
|
|
717
|
+
your MCP client's environment:
|
|
718
|
+
|
|
719
|
+
```sh
|
|
720
|
+
npx --yes --package=@storypane/sdk@0.1.0-rc.3 storypane mcp
|
|
721
|
+
```
|
|
722
|
+
|
|
723
|
+
If installed in your project, `pnpm exec storypane mcp` also works from its root.
|
|
724
|
+
The stdio adapter exposes read and write
|
|
647
725
|
tools over the management API; the same scopes, issuer permissions and revocation
|
|
648
726
|
apply. See [MCP configuration and contracts](https://github.com/lkuich/storypane/blob/main/docs/management-api.md#mcp-adapter).
|
package/dist/preview.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface PreviewOptions<T = StorypaneStory['content']> {
|
|
|
9
9
|
story: StoryReference;
|
|
10
10
|
mode?: PreviewMode;
|
|
11
11
|
onChange: (story: StorypaneStory<T>) => void;
|
|
12
|
+
/** Opt in to isolated block previews using the same component renderer as your site. */
|
|
13
|
+
onComponent?: (block: T | undefined) => void;
|
|
12
14
|
onStatus?: (status: PreviewStatus, error?: Error) => void;
|
|
13
15
|
/** For standalone preview, fetch a short-lived session from your authenticated app server. */
|
|
14
16
|
getSession?: () => Promise<PreviewSession>;
|
package/dist/preview.js
CHANGED
|
@@ -13,6 +13,8 @@ export function connectPreview(options) {
|
|
|
13
13
|
let reconnect, renew;
|
|
14
14
|
let ready, retry = 0, revision = -1, selected = '';
|
|
15
15
|
let referencesRevision;
|
|
16
|
+
let content;
|
|
17
|
+
let component;
|
|
16
18
|
const embedded = window.parent !== window;
|
|
17
19
|
const send = (message) => {
|
|
18
20
|
if (embedded)
|
|
@@ -39,6 +41,31 @@ export function connectPreview(options) {
|
|
|
39
41
|
element.removeAttribute('data-storypane-selected');
|
|
40
42
|
}
|
|
41
43
|
}
|
|
44
|
+
function renderComponent() {
|
|
45
|
+
if (!component ||
|
|
46
|
+
!options.onComponent ||
|
|
47
|
+
!session ||
|
|
48
|
+
Date.parse(session.expiresAt) <= Date.now() ||
|
|
49
|
+
content === undefined)
|
|
50
|
+
return;
|
|
51
|
+
function find(value, id) {
|
|
52
|
+
if (!value || typeof value !== 'object')
|
|
53
|
+
return;
|
|
54
|
+
if ('_uid' in value &&
|
|
55
|
+
value._uid === id &&
|
|
56
|
+
'component' in value &&
|
|
57
|
+
typeof value.component === 'string')
|
|
58
|
+
return value;
|
|
59
|
+
for (const child of Object.values(value)) {
|
|
60
|
+
const found = find(child, id);
|
|
61
|
+
if (found)
|
|
62
|
+
return found;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
const block = 'block' in component ? component.block : find(content, component.blockId);
|
|
66
|
+
options.onComponent(block);
|
|
67
|
+
send({ type: 'storypane:component-rendered', found: !!block });
|
|
68
|
+
}
|
|
42
69
|
function open(value) {
|
|
43
70
|
if (stopped || !matches(value))
|
|
44
71
|
return;
|
|
@@ -77,6 +104,8 @@ export function connectPreview(options) {
|
|
|
77
104
|
revision = message.story.revision;
|
|
78
105
|
referencesRevision = message.story.referencesRevision;
|
|
79
106
|
options.onChange(message.story);
|
|
107
|
+
content = message.story.content;
|
|
108
|
+
renderComponent();
|
|
80
109
|
highlight();
|
|
81
110
|
}
|
|
82
111
|
else if (message.type === 'error')
|
|
@@ -114,12 +143,14 @@ export function connectPreview(options) {
|
|
|
114
143
|
else if (embedded) {
|
|
115
144
|
send({
|
|
116
145
|
type: 'storypane:ready',
|
|
146
|
+
componentPreview: !!options.onComponent,
|
|
117
147
|
spaceId: options.spaceId,
|
|
118
148
|
...options.story,
|
|
119
149
|
});
|
|
120
150
|
if (!ready)
|
|
121
151
|
ready = setInterval(() => send({
|
|
122
152
|
type: 'storypane:ready',
|
|
153
|
+
componentPreview: !!options.onComponent,
|
|
123
154
|
spaceId: options.spaceId,
|
|
124
155
|
...options.story,
|
|
125
156
|
}), 3000);
|
|
@@ -137,6 +168,16 @@ export function connectPreview(options) {
|
|
|
137
168
|
return;
|
|
138
169
|
if (event.data?.type === 'storypane:init')
|
|
139
170
|
open(event.data.session);
|
|
171
|
+
if (event.data?.type === 'storypane:component' && options.onComponent) {
|
|
172
|
+
const { blockId, block } = event.data;
|
|
173
|
+
if (typeof blockId === 'string')
|
|
174
|
+
component = { blockId };
|
|
175
|
+
else if (block && typeof block._uid === 'string' && typeof block.component === 'string')
|
|
176
|
+
component = { block };
|
|
177
|
+
else
|
|
178
|
+
return;
|
|
179
|
+
renderComponent();
|
|
180
|
+
}
|
|
140
181
|
if (event.data?.type === 'storypane:select' && typeof event.data.blockId === 'string') {
|
|
141
182
|
selected = event.data.blockId;
|
|
142
183
|
highlight();
|
|
@@ -186,6 +227,8 @@ export function connectPreview(options) {
|
|
|
186
227
|
void authorize();
|
|
187
228
|
return () => {
|
|
188
229
|
stopped = true;
|
|
230
|
+
if (component)
|
|
231
|
+
options.onComponent?.(undefined);
|
|
189
232
|
if (socket) {
|
|
190
233
|
socket.onclose = null;
|
|
191
234
|
socket.close();
|
package/dist/react.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type PreviewOptions, type PreviewStatus } from './preview.js';
|
|
|
2
2
|
import type { StorypaneStory, StoryReference } from './types.js';
|
|
3
3
|
export { editable } from './types.js';
|
|
4
4
|
/** Preview a story that may never have been published. The editor supplies its short-lived session. */
|
|
5
|
-
export declare function useStorypanePreview<T = StorypaneStory['content']>({ story: reference, cmsUrl, spaceId, migrationVersion, getSession, }: Omit<PreviewOptions<T>, 'story' | 'mode' | 'onChange' | 'onStatus'> & {
|
|
5
|
+
export declare function useStorypanePreview<T = StorypaneStory['content']>({ story: reference, cmsUrl, spaceId, migrationVersion, getSession, onComponent, }: Omit<PreviewOptions<T>, 'story' | 'mode' | 'onChange' | 'onStatus'> & {
|
|
6
6
|
story?: StoryReference;
|
|
7
7
|
}): {
|
|
8
8
|
story: StorypaneStory<T> | undefined;
|
package/dist/react.js
CHANGED
|
@@ -3,7 +3,7 @@ import { useEffect, useRef, useState } from 'react';
|
|
|
3
3
|
import { connectPreview } from './preview.js';
|
|
4
4
|
export { editable } from './types.js';
|
|
5
5
|
/** Preview a story that may never have been published. The editor supplies its short-lived session. */
|
|
6
|
-
export function useStorypanePreview({ story: reference, cmsUrl, spaceId, migrationVersion, getSession, }) {
|
|
6
|
+
export function useStorypanePreview({ story: reference, cmsUrl, spaceId, migrationVersion, getSession, onComponent, }) {
|
|
7
7
|
const id = reference?.id;
|
|
8
8
|
const slug = reference?.slug;
|
|
9
9
|
const locale = reference?.locale;
|
|
@@ -20,12 +20,13 @@ export function useStorypanePreview({ story: reference, cmsUrl, spaceId, migrati
|
|
|
20
20
|
spaceId,
|
|
21
21
|
migrationVersion,
|
|
22
22
|
getSession,
|
|
23
|
+
onComponent,
|
|
23
24
|
story: id ? { id, locale } : { slug: slug, locale },
|
|
24
25
|
mode: { draft: true },
|
|
25
26
|
onChange: (story) => setResult((current) => ({ ...current, key, story })),
|
|
26
27
|
onStatus: (status, error) => setResult((current) => ({ ...current, key, status, error })),
|
|
27
28
|
});
|
|
28
|
-
}, [cmsUrl, spaceId, id, slug, locale, migrationVersion, getSession, key]);
|
|
29
|
+
}, [cmsUrl, spaceId, id, slug, locale, migrationVersion, getSession, onComponent, key]);
|
|
29
30
|
// Never display the previous story while a new route is authorizing.
|
|
30
31
|
const current = result.key === key
|
|
31
32
|
? result
|
|
@@ -48,6 +49,7 @@ export function useStorypaneStory(initial, options) {
|
|
|
48
49
|
story: { id: initial.id, locale: initial.locale },
|
|
49
50
|
mode: JSON.parse(mode),
|
|
50
51
|
getSession: options.getSession,
|
|
52
|
+
onComponent: options.onComponent,
|
|
51
53
|
onChange: setStory,
|
|
52
54
|
onStatus: (value, cause) => {
|
|
53
55
|
setStatus(value);
|
|
@@ -60,6 +62,7 @@ export function useStorypaneStory(initial, options) {
|
|
|
60
62
|
options.spaceId,
|
|
61
63
|
options.migrationVersion,
|
|
62
64
|
options.getSession,
|
|
65
|
+
options.onComponent,
|
|
63
66
|
mode,
|
|
64
67
|
]);
|
|
65
68
|
return { story, status, error };
|