@preprio/toolkit 0.5.0-rc.5 → 0.5.1-dom.0
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 +218 -226
- package/dist/astro/index.cjs +1 -1
- package/dist/astro/index.js +1 -1
- package/dist/auto.cjs +4572 -0
- package/dist/auto.d.cts +42 -0
- package/dist/auto.d.ts +42 -0
- package/dist/auto.js +105 -0
- package/dist/browser/auto.js +117 -0
- package/dist/chunk-5I5TQ3NO.js +74 -0
- package/dist/chunk-AZC4BZJI.js +447 -0
- package/dist/{chunk-TG6N3C22.js → chunk-IKOOXTPM.js} +1 -1
- package/dist/{chunk-3X55JL52.js → chunk-IOSO232M.js} +4 -2
- package/dist/{chunk-SBLZANL5.js → chunk-RRM5762S.js} +2 -71
- package/dist/chunk-SZTHSJUA.js +326 -0
- package/dist/{chunk-CMUFTCG3.js → chunk-TEB7M7EM.js} +0 -4
- package/dist/chunk-UK664KSV.js +6 -0
- package/dist/{chunk-74KY5EAM.js → chunk-ZFGVP2GW.js} +28 -447
- package/dist/html-live-DzmEG9wU.d.cts +42 -0
- package/dist/html-live-DzmEG9wU.d.ts +42 -0
- package/dist/index.cjs +177 -2
- package/dist/index.d.cts +31 -28
- package/dist/index.d.ts +31 -28
- package/dist/index.js +21 -154
- package/dist/{live-preview-H4643QYF.js → live-preview-IYAS7PYV.js} +3 -2
- package/dist/nextjs/components.cjs +1 -1
- package/dist/nextjs/components.d.cts +2 -1
- package/dist/nextjs/components.d.ts +2 -1
- package/dist/nextjs/components.js +6 -3
- package/dist/nextjs/index.cjs +1 -1
- package/dist/nextjs/index.d.cts +0 -2
- package/dist/nextjs/index.d.ts +0 -2
- package/dist/nextjs/index.js +1 -1
- package/dist/nextjs/live-action.js +5 -3
- package/dist/nextjs/live.cjs +1 -4
- package/dist/nextjs/live.d.cts +1 -3
- package/dist/nextjs/live.d.ts +1 -3
- package/dist/nextjs/live.js +2 -5
- package/dist/nuxt/index.cjs +1 -1
- package/dist/nuxt/index.js +1 -1
- package/dist/pixel-DwW7cXJm.d.cts +49 -0
- package/dist/pixel-DwW7cXJm.d.ts +49 -0
- package/dist/react/components.cjs +1 -1
- package/dist/react/components.js +1 -1
- package/dist/sveltekit/index.cjs +1 -1
- package/dist/sveltekit/index.js +1 -1
- package/dist/{pixel-CX422dCO.d.cts → types-DtMa_-f2.d.cts} +1 -49
- package/dist/{pixel-CX422dCO.d.ts → types-DtMa_-f2.d.ts} +1 -49
- package/package.json +12 -7
- package/dist/sveltekit/live-preview-O2ZTJNW4.js +0 -1520
- package/dist/sveltekit/live.cjs +0 -1731
- package/dist/sveltekit/live.d.cts +0 -59
- package/dist/sveltekit/live.d.ts +0 -59
- package/dist/sveltekit/live.js +0 -97
package/README.md
CHANGED
|
@@ -17,9 +17,8 @@ Every integration is the same three steps:
|
|
|
17
17
|
1. **Middleware** — resolves the visitor's customer ID, segment, and A/B variant into request headers.
|
|
18
18
|
2. **Layout** — mounts the preview toolbar and the tracking pixel.
|
|
19
19
|
3. **Data fetching** — forwards the Prepr headers on your GraphQL requests, so Prepr returns personalized content.
|
|
20
|
-
4. **Live preview** (optional) — shows the editor's unsaved changes in Prepr's side-by-side preview while they type. See [Live preview](#live-preview).
|
|
21
20
|
|
|
22
|
-
Jump to your framework: [Next.js](#nextjs) · [Astro](#astro) · [SvelteKit](#sveltekit) · [Nuxt](#nuxt) · [React without a framework](#react-no-framework) · [
|
|
21
|
+
Jump to your framework: [Next.js](#nextjs) · [Astro](#astro) · [SvelteKit](#sveltekit) · [Nuxt](#nuxt) · [React without a framework](#react-no-framework) · [PHP, Ruby, Python and other backends](#any-backend-drop-in-script) · [anything else](#any-other-framework).
|
|
23
22
|
|
|
24
23
|
Coming from `@preprio/prepr-nextjs`? See the [migration guide](https://github.com/preprio/prepr-toolkit/blob/main/MIGRATION.md).
|
|
25
24
|
|
|
@@ -123,12 +122,9 @@ Per-framework peer dependencies — all optional; installing without them is fin
|
|
|
123
122
|
| `@preprio/toolkit` (vanilla core) | none |
|
|
124
123
|
| `@preprio/toolkit/react` | `react` >= 17, `react-dom` >= 17 |
|
|
125
124
|
| `@preprio/toolkit/nextjs` | `next` >= 13, `react` >= 17, `react-dom` >= 17 |
|
|
126
|
-
| `@preprio/toolkit/nextjs/live` | `next` >= 13, `react` >= 18 (live preview) |
|
|
127
125
|
| `@preprio/toolkit/astro` | none (`.astro` components compile in your own pipeline) |
|
|
128
126
|
| `@preprio/toolkit/sveltekit` | `svelte` (only for the `.svelte` components) |
|
|
129
|
-
| `@preprio/toolkit/sveltekit/live` | none (returns a plain Svelte store) |
|
|
130
127
|
| `@preprio/toolkit/nuxt` | `vue` (only for the `.vue` components) |
|
|
131
|
-
| `@preprio/toolkit/nuxt/live` | `vue` >= 3.3 |
|
|
132
128
|
|
|
133
129
|
## The preview gate
|
|
134
130
|
|
|
@@ -254,83 +250,158 @@ The loader rewrites `w_` to the width Next asks for and scales `h_` alongside it
|
|
|
254
250
|
|
|
255
251
|
See the runnable example at `examples/nextjs` for the full source of truth.
|
|
256
252
|
|
|
257
|
-
### Live preview
|
|
253
|
+
### Live preview (beta)
|
|
258
254
|
|
|
259
|
-
|
|
260
|
-
|
|
255
|
+
True live preview inside the Prepr editor iframe: keystrokes patch your query
|
|
256
|
+
results instantly by item `_id`; saves refetch the affected queries.
|
|
261
257
|
|
|
262
|
-
**
|
|
263
|
-
|
|
258
|
+
**Requirements.** Live preview runs only where the Prepr middleware handles
|
|
259
|
+
the request with `preview: true` (`createPreprMiddleware(request, { preview })`
|
|
260
|
+
in `proxy.ts`). Make sure your middleware `matcher` covers every page you
|
|
261
|
+
preview: on a page it skips, live preview does nothing and shows no error.
|
|
262
|
+
Pass your preview flag as `enabled` to every live preview entry point below;
|
|
263
|
+
each one does nothing without it.
|
|
264
264
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
const preview = process.env.VERCEL_ENV !== 'production';
|
|
265
|
+
Live updates and click-to-edit find content by id, never by position, so
|
|
266
|
+
they stay correct while an editor adds, removes or reorders blocks before
|
|
267
|
+
saving:
|
|
269
268
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
269
|
+
- Select `_id` (and ideally `__typename`) on every model and component level
|
|
270
|
+
that should live-update. Items without `_id` silently stay static.
|
|
271
|
+
- In dynamic content fields, select `_id` on every entry type (`Text`,
|
|
272
|
+
`Quote`, …). `Assets` entries have no `_id` of their own: select
|
|
273
|
+
`items { _id }` instead.
|
|
274
|
+
- A `Text` entry's content updates whether the query selects `body`,
|
|
275
|
+
`html` or `text` (the latter with tags stripped).
|
|
276
|
+
- Render each dynamic content entry inside an element marked with
|
|
277
|
+
`data-prepr-edit-target="<field>.<index>"` and
|
|
278
|
+
`data-prepr-entry-id="<entry _id>"` (for `Assets`, the first asset's
|
|
279
|
+
`_id`). The index is the fallback when no id matches.
|
|
280
|
+
- Values without a hidden edit link (images, videos, other URLs) are made
|
|
281
|
+
click-to-edit with `previewAttrs({ id, field }, { enabled: isPreview })`
|
|
282
|
+
on the element that renders them.
|
|
283
|
+
- Where the page renders a linked item whose content comes from items nested
|
|
284
|
+
inside it (for example a personalization or a shared section referenced
|
|
285
|
+
from a stack), wrap it in an element with
|
|
286
|
+
`data-prepr-reference="<linked item _id>"`. The editor only knows the items
|
|
287
|
+
the open item links to directly; content inside the wrapper is outlined as
|
|
288
|
+
that linked item, and clicking it opens the link in the editor.
|
|
273
289
|
|
|
274
|
-
|
|
290
|
+
Configure once in a small client component mounted in your root layout:
|
|
275
291
|
|
|
276
292
|
```tsx
|
|
277
|
-
|
|
293
|
+
'use client';
|
|
294
|
+
import { useEffect } from 'react';
|
|
295
|
+
import { initLivePreview } from '@preprio/toolkit/nextjs/live';
|
|
278
296
|
|
|
279
|
-
{
|
|
280
|
-
|
|
297
|
+
export default function LivePreviewInit({ preview }: { preview: boolean }) {
|
|
298
|
+
useEffect(() => {
|
|
299
|
+
initLivePreview({
|
|
300
|
+
// Your preview flag. Without it nothing starts.
|
|
301
|
+
enabled: preview,
|
|
302
|
+
// Runs a registered query against Prepr GraphQL. `query` is whatever
|
|
303
|
+
// you registered (string or DocumentNode) — you own the transport.
|
|
304
|
+
fetcher: async (query, variables) => {
|
|
305
|
+
/* POST to your Prepr GraphQL endpoint, return json.data */
|
|
306
|
+
},
|
|
307
|
+
});
|
|
308
|
+
}, [preview]);
|
|
309
|
+
return null;
|
|
281
310
|
}
|
|
282
311
|
```
|
|
283
312
|
|
|
284
|
-
|
|
285
|
-
action and the route re-renders with them; on save it calls
|
|
286
|
-
`router.refresh()` for the stored content. Server components need nothing
|
|
287
|
-
else. Requires Next.js 16 or later; on Next.js 15 and older pages update on
|
|
288
|
-
save only.
|
|
289
|
-
|
|
290
|
-
**Client components** can skip the server round trip: wrap the data they
|
|
291
|
-
render in `useLive`, and keystrokes patch it in the browser. Import it from
|
|
292
|
-
`@preprio/toolkit/nextjs/live` — the `/nextjs` barrel also exports
|
|
293
|
-
server-only helpers (`next/headers`), which a client module graph must not
|
|
294
|
-
include.
|
|
313
|
+
Register query results in client components with `useLive`:
|
|
295
314
|
|
|
296
315
|
```tsx
|
|
297
316
|
'use client';
|
|
298
317
|
import { useLive } from '@preprio/toolkit/nextjs/live';
|
|
299
318
|
|
|
300
|
-
export default function
|
|
301
|
-
const
|
|
302
|
-
|
|
319
|
+
export default function PageContent({ data, slug }) {
|
|
320
|
+
const liveData = useLive(data, {
|
|
321
|
+
query: GetPageBySlugDocument,
|
|
322
|
+
variables: { slug },
|
|
323
|
+
});
|
|
324
|
+
// render from liveData — it re-renders on editor keystrokes and saves
|
|
303
325
|
}
|
|
304
326
|
```
|
|
305
327
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
runs the query again through your `fetcher`:
|
|
328
|
+
In `'use client'` files always import from `@preprio/toolkit/nextjs/live` —
|
|
329
|
+
the `/nextjs` barrel also exports server-only helpers (`next/headers`), which
|
|
330
|
+
a client module graph must not include.
|
|
310
331
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
332
|
+
Omitting `query`/`variables` degrades that registration to keystroke pushes
|
|
333
|
+
only. For server-component pages, mount `<PreprRefresh enabled={preview} />`
|
|
334
|
+
instead — it calls `router.refresh()` on every save. Don't combine
|
|
335
|
+
`PreprRefresh` with `useLive` registrations that pass a `query`: a save would
|
|
336
|
+
both refetch and refresh. `useLive` without a `query` works alongside it.
|
|
315
337
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
338
|
+
Without `enabled`, and outside the Prepr editor iframe, all of this is a
|
|
339
|
+
passthrough with zero overhead. The CMS editor must emit `prepr:fieldChange` (per keystroke) and
|
|
340
|
+
`prepr:itemChanged` (on save, with the root content item id) into the preview
|
|
341
|
+
iframe — `examples/editor-emulator` emulates this for local development.
|
|
342
|
+
Unsaved structural changes (adding/reordering components) appear after save;
|
|
343
|
+
typed text is live immediately.
|
|
344
|
+
|
|
345
|
+
#### Unsaved changes in server components: `normalizeQuery` (experimental, Next.js 16+)
|
|
346
|
+
|
|
347
|
+
Server components show the editor's unsaved changes when you pass their query
|
|
348
|
+
results through `normalizeQuery`, with whatever client you fetch with:
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
import { normalizeQuery } from '@preprio/toolkit/nextjs';
|
|
352
|
+
|
|
353
|
+
const preview = process.env.VERCEL_ENV !== 'production';
|
|
326
354
|
|
|
327
|
-
//
|
|
328
|
-
const
|
|
355
|
+
const data = await fetchFromPrepr(GetPageDocument, { slug }); // any client
|
|
356
|
+
const page = await normalizeQuery(GetPageDocument, data, { enabled: preview });
|
|
329
357
|
```
|
|
330
358
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
359
|
+
Mount `<PreprRefresh enabled={preview} />` in the root layout. While an
|
|
360
|
+
editor types, it sends the unsaved changes to the server and the route
|
|
361
|
+
re-renders with them; on save it refreshes with the stored content.
|
|
362
|
+
|
|
363
|
+
- The query (source text, a `DocumentNode`, or a typed document string) tells
|
|
364
|
+
the toolkit which response key holds which field. Changes land under
|
|
365
|
+
aliases, inside single components, on dynamic content entries, and on every
|
|
366
|
+
place the same item appears. Fields the query did not select are left
|
|
367
|
+
alone. Select `_id` on every item and component that should update.
|
|
368
|
+
- Changed text keeps its hidden edit link, so it stays click-to-edit.
|
|
369
|
+
- `useLive` data keeps the typed values across these re-renders.
|
|
370
|
+
|
|
371
|
+
**Production safety.** Pass `enabled` from the same preview flag you give the
|
|
372
|
+
middleware, and never `true` on a production deployment:
|
|
373
|
+
|
|
374
|
+
- With `enabled: false` (the default) the result is returned untouched and
|
|
375
|
+
the request is never read, so static and ISR pages stay static.
|
|
376
|
+
`PreprRefresh`, `initLivePreview` and `useLive` don't listen to the editor
|
|
377
|
+
at all.
|
|
378
|
+
- Next.js registers the toolkit's server action in every build that imports
|
|
379
|
+
`@preprio/toolkit/nextjs/live`, so anyone can call it, browsers and scripts
|
|
380
|
+
alike. It does nothing (no parsing, no storage, no re-render) unless the
|
|
381
|
+
Prepr middleware handled the request with `preview: true`. The middleware
|
|
382
|
+
drops `Prepr-*` headers a client sends, so this can't be forged on a page
|
|
383
|
+
the middleware runs for. On a page its `matcher` skips, a forged header can
|
|
384
|
+
at most trigger an extra render, never change content, because
|
|
385
|
+
`normalizeQuery` there still needs `enabled`.
|
|
386
|
+
- Changes only reach the re-render of the editor's own server action call.
|
|
387
|
+
The action stores them in the server process under a random, single-use
|
|
388
|
+
token and sets only that token as a cookie that expires immediately. Any
|
|
389
|
+
other request carrying the cookie, such as a plain or cacheable GET that
|
|
390
|
+
replays a token, sees nothing.
|
|
391
|
+
- Changes are only accepted from the Prepr editor (`https://*.prepr.io`, or
|
|
392
|
+
your `allowedEditorOrigins` via `initLivePreview`), never from another page
|
|
393
|
+
that frames your site. They are validated as hostile input: link fields
|
|
394
|
+
never take `javascript:`, `vbscript:` or `data:` URLs. HTML text is passed
|
|
395
|
+
on as typed, like rich text from the API.
|
|
396
|
+
|
|
397
|
+
On Next.js 15 and older the action does nothing and pages update on save.
|
|
398
|
+
|
|
399
|
+
For Astro and Nuxt, see their sections. The framework-free core exports,
|
|
400
|
+
`normalizeQuery(query, result, { log })`, `startHtmlLivePreview()` and
|
|
401
|
+
`readLiveLogFromRequest(request)` from `@preprio/toolkit`, cover any other
|
|
402
|
+
server-rendered stack. A backend that cannot run them uses
|
|
403
|
+
`startHtmlLivePreview({ apply: 'dom' })`; see
|
|
404
|
+
[Any backend](#any-backend-drop-in-script).
|
|
334
405
|
|
|
335
406
|
## Astro
|
|
336
407
|
|
|
@@ -383,11 +454,11 @@ The `.astro` components ship as source, compiled by your own Astro/Vite pipeline
|
|
|
383
454
|
|
|
384
455
|
See the runnable example at `examples/astro` for the full source of truth.
|
|
385
456
|
|
|
386
|
-
### Live preview
|
|
457
|
+
### Live preview (experimental)
|
|
387
458
|
|
|
388
|
-
|
|
389
|
-
as above, and run each query result through
|
|
390
|
-
flag:
|
|
459
|
+
Show the editor's unsaved changes while they type. Pass `preview` to
|
|
460
|
+
`onPreprRequest` as above, and run each query result through
|
|
461
|
+
`normalizeQuery` with your preview flag:
|
|
391
462
|
|
|
392
463
|
```astro
|
|
393
464
|
---
|
|
@@ -403,8 +474,13 @@ const page = normalizeQuery(GetPageDocument, data, {
|
|
|
403
474
|
|
|
404
475
|
`PreprToolbar.astro` does the rest. Inside the Prepr editor it requests the
|
|
405
476
|
page again with the unsaved changes and updates only the parts of the DOM that
|
|
406
|
-
changed; on save it shows the stored content. Preview
|
|
407
|
-
|
|
477
|
+
changed; on save it shows the stored content. Preview must render on demand
|
|
478
|
+
(`output: 'server'`).
|
|
479
|
+
|
|
480
|
+
Safety: the middleware only accepts changes in preview, from a same-origin
|
|
481
|
+
`POST` that carries the toolkit's header and a JSON body, and marks that
|
|
482
|
+
response `Cache-Control: private, no-store`. With `enabled: false` the result
|
|
483
|
+
is returned untouched. Changes are accepted only from the Prepr editor.
|
|
408
484
|
|
|
409
485
|
## SvelteKit
|
|
410
486
|
|
|
@@ -466,35 +542,6 @@ The `.svelte` components ship as source (compiled by your own Vite/Svelte pipeli
|
|
|
466
542
|
|
|
467
543
|
See the runnable example at `examples/sveltekit` for the full source of truth.
|
|
468
544
|
|
|
469
|
-
### Live preview in SvelteKit
|
|
470
|
-
|
|
471
|
-
To turn on [live preview](#live-preview), return your preview flag from
|
|
472
|
-
`load`, then run the query result through `normalizedQuery`. It applies the
|
|
473
|
-
editor's changes in the browser, with no server round trip:
|
|
474
|
-
|
|
475
|
-
```svelte
|
|
476
|
-
<script lang="ts">
|
|
477
|
-
import { invalidateAll } from '$app/navigation';
|
|
478
|
-
import { normalizedQuery } from '@preprio/toolkit/sveltekit/live';
|
|
479
|
-
|
|
480
|
-
let { data } = $props();
|
|
481
|
-
|
|
482
|
-
const result = $derived(
|
|
483
|
-
normalizedQuery(GetPageDocument, data.result, {
|
|
484
|
-
enabled: data.preview,
|
|
485
|
-
onSave: invalidateAll,
|
|
486
|
-
}),
|
|
487
|
-
);
|
|
488
|
-
</script>
|
|
489
|
-
|
|
490
|
-
<h1>{$result.Page.title}</h1>
|
|
491
|
-
```
|
|
492
|
-
|
|
493
|
-
`normalizedQuery` returns a Svelte store: render from `$result`. Pass the
|
|
494
|
-
result in the shape the query returns (`{ Page: … }`, not just the page), so
|
|
495
|
-
the toolkit can tell which response key holds which field. On save,
|
|
496
|
-
`invalidateAll` reruns your `load` functions for the stored content.
|
|
497
|
-
|
|
498
545
|
## Nuxt
|
|
499
546
|
|
|
500
547
|
Requires Nitro's Node.js preset (the Nuxt default).
|
|
@@ -546,25 +593,24 @@ The `.vue` components ship as source (compiled by your own Vite/Vue pipeline) an
|
|
|
546
593
|
|
|
547
594
|
See the runnable example at `examples/nuxt` for the full source of truth.
|
|
548
595
|
|
|
549
|
-
### Live preview
|
|
596
|
+
### Live preview (experimental)
|
|
550
597
|
|
|
551
|
-
|
|
552
|
-
through `useNormalizedQuery` with your preview flag. It applies the editor's
|
|
553
|
-
changes in the browser, with no server round trip:
|
|
598
|
+
Show the editor's unsaved changes while they type, with no server round trip:
|
|
554
599
|
|
|
555
600
|
```ts
|
|
556
601
|
import { useNormalizedQuery } from '@preprio/toolkit/nuxt/live';
|
|
557
602
|
|
|
558
603
|
const { data, refresh } = await useAsyncData('page', () => fetchPage(slug));
|
|
559
604
|
const page = useNormalizedQuery(GetPageDocument, data, {
|
|
560
|
-
enabled: preview,
|
|
605
|
+
enabled: preview, // your preview flag; nothing starts without it
|
|
561
606
|
onSave: refresh,
|
|
562
607
|
});
|
|
563
608
|
```
|
|
564
609
|
|
|
565
610
|
Render from `page`. Nuxt hydrates the whole page, so every component that
|
|
566
|
-
shows a changed field updates.
|
|
567
|
-
|
|
611
|
+
shows a changed field updates. Without `enabled`, on the server and outside
|
|
612
|
+
the Prepr editor's preview iframe it returns the data unchanged. Changes are accepted only from
|
|
613
|
+
the Prepr editor.
|
|
568
614
|
|
|
569
615
|
## React (no framework)
|
|
570
616
|
|
|
@@ -641,6 +687,49 @@ This double-counts the first pageview, since the pixel's own install event fires
|
|
|
641
687
|
|
|
642
688
|
The `id` is your access token, and in a client-rendered app it is public — bundled into JS and visible in devtools. Use a tracking-only token; do not derive it from a browser-exposed GraphQL URL, which would also expose content read access.
|
|
643
689
|
|
|
690
|
+
## Any backend: drop-in script
|
|
691
|
+
|
|
692
|
+
For server-rendered sites whose backend is not JavaScript (Laravel, Symfony, Rails, Django, …). The browser side needs one script tag; live preview needs no server support.
|
|
693
|
+
|
|
694
|
+
**1. Render the props** into the page on preview deployments only, as JSON:
|
|
695
|
+
|
|
696
|
+
```html
|
|
697
|
+
<script type="application/json" data-prepr-toolbar-props>
|
|
698
|
+
{
|
|
699
|
+
"props": {
|
|
700
|
+
"activeSegment": null,
|
|
701
|
+
"activeVariant": null,
|
|
702
|
+
"segments": [{ "_id": "…", "name": "Students" }]
|
|
703
|
+
},
|
|
704
|
+
"options": { "locale": "nl" }
|
|
705
|
+
}
|
|
706
|
+
</script>
|
|
707
|
+
```
|
|
708
|
+
|
|
709
|
+
`segments` comes from a `{ _Segments { _id name } }` query against your preview GraphQL URL; `activeSegment` / `activeVariant` from the `Prepr-Segments` / `Prepr-ABtesting` cookies (see [How It Works](#middleware)). Escape the JSON for a `<script>` element (`<` as `\u003c`).
|
|
710
|
+
|
|
711
|
+
**2. Load the script**, from your bundle or a CDN:
|
|
712
|
+
|
|
713
|
+
```javascript
|
|
714
|
+
import '@preprio/toolkit/auto';
|
|
715
|
+
```
|
|
716
|
+
|
|
717
|
+
```html
|
|
718
|
+
<script
|
|
719
|
+
type="module"
|
|
720
|
+
src="https://cdn.jsdelivr.net/npm/@preprio/toolkit@VERSION/dist/browser/auto.js"
|
|
721
|
+
></script>
|
|
722
|
+
```
|
|
723
|
+
|
|
724
|
+
It mounts the toolbar, click-to-edit and live preview, and does nothing on a page without the props tag. Client-side page swaps (Livewire `wire:navigate`, Turbo, htmx) are handled: when `<body>` is replaced the preview starts again on the new page.
|
|
725
|
+
|
|
726
|
+
**Live preview.** Inside the Prepr editor, text the editor types is written straight into the elements whose edit links point at the changed field. Images, links and added or reordered blocks appear on save, when the page is requested again. Two attributes tune it:
|
|
727
|
+
|
|
728
|
+
- `data-prepr-live="html"` on an element (or an ancestor) writes rich text as markup, reduced to formatting tags first. Without it, rich text waits for save, so it never loses its formatting while the editor types.
|
|
729
|
+
- `data-prepr-live="off"` leaves an element alone, for values the template reformats (dates, truncation) that should wait for save.
|
|
730
|
+
|
|
731
|
+
Set `"live": "server"` in the payload if your server applies the editor's changes itself (see [`readLiveLogFromRequest`](#live-preview-beta)), or `"live": false` to turn live preview off.
|
|
732
|
+
|
|
644
733
|
## Any other framework
|
|
645
734
|
|
|
646
735
|
The core is runtime-neutral: it works off a WHATWG `Request` and hands back the headers to forward to Prepr plus the cookies to persist. Adapting it to Express, Hono, Fastify, or a plain server is roughly twenty lines.
|
|
@@ -687,115 +776,6 @@ const controller = createPreprPreview({ props: toolbarProps });
|
|
|
687
776
|
|
|
688
777
|
Serialize the server-computed props into the page — a `<script type="application/json">` tag works well — and read them back on the client. See the runnable example at `examples/express` for the full source of truth.
|
|
689
778
|
|
|
690
|
-
## Live preview
|
|
691
|
-
|
|
692
|
-
Inside Prepr's side-by-side preview, live preview shows what an editor types
|
|
693
|
-
before they save: text, links and other fields update on the page as they
|
|
694
|
-
type, and a save shows the stored content. Outside the editor, and when it is
|
|
695
|
-
not enabled, it does nothing and costs nothing: the runtime loads only inside
|
|
696
|
-
the editor iframe.
|
|
697
|
-
|
|
698
|
-
| Framework | Entry point | Applies changes | Setup |
|
|
699
|
-
| --------- | --------------------------------- | ------------------------------------- | --------------------------------------- |
|
|
700
|
-
| Next.js | `@preprio/toolkit/nextjs/live` | On the server (`normalizeQuery`) | [Next.js](#live-preview-in-nextjs) |
|
|
701
|
-
| Astro | `@preprio/toolkit/astro` | On the server (`normalizeQuery`) | [Astro](#live-preview-in-astro) |
|
|
702
|
-
| SvelteKit | `@preprio/toolkit/sveltekit/live` | In the browser (`normalizedQuery`) | [SvelteKit](#live-preview-in-sveltekit) |
|
|
703
|
-
| Nuxt | `@preprio/toolkit/nuxt/live` | In the browser (`useNormalizedQuery`) | [Nuxt](#live-preview-in-nuxt) |
|
|
704
|
-
|
|
705
|
-
Other server-rendered stacks can use the framework-free core exports from
|
|
706
|
-
`@preprio/toolkit`: `readLiveLogFromRequest(request)` in the server handler,
|
|
707
|
-
`normalizeQuery(query, result, { log })` on each result, and
|
|
708
|
-
`startHtmlLivePreview()` in the browser. The Astro wrapper is built from
|
|
709
|
-
exactly these.
|
|
710
|
-
|
|
711
|
-
### Before you start
|
|
712
|
-
|
|
713
|
-
1. **Set up the toolbar for your framework first.** Live preview runs only
|
|
714
|
-
where the Prepr middleware handles the request with `preview: true`. Make
|
|
715
|
-
sure the middleware covers every page you preview: on a page it skips,
|
|
716
|
-
live preview does nothing and shows no error.
|
|
717
|
-
2. **Pass your preview flag as `enabled`** to every live preview entry point.
|
|
718
|
-
Each one does nothing without it. Never pass `true` on a production
|
|
719
|
-
deployment.
|
|
720
|
-
3. **Point Prepr at your preview deployment.** In Prepr, set the preview URL
|
|
721
|
-
to a deployment that uses the **GraphQL Preview** access token, with
|
|
722
|
-
**Enable edit mode** checked.
|
|
723
|
-
4. **Allow Prepr to frame your site.** If your server sends a
|
|
724
|
-
`Content-Security-Policy`, add your Prepr environments to
|
|
725
|
-
`frame-ancestors`, for example
|
|
726
|
-
`frame-ancestors https://acme.prepr.io https://dev-acme.prepr.io`.
|
|
727
|
-
|
|
728
|
-
### Preparing your queries
|
|
729
|
-
|
|
730
|
-
Live updates and click-to-edit find content by id, never by position, so
|
|
731
|
-
they stay correct while an editor adds, removes or reorders blocks before
|
|
732
|
-
saving:
|
|
733
|
-
|
|
734
|
-
- Select `_id` (and ideally `__typename`) on every model and component level
|
|
735
|
-
that should live-update. Items without `_id` stay static.
|
|
736
|
-
- In dynamic content fields, select `_id` on every entry type (`Text`,
|
|
737
|
-
`Quote`, …). `Assets` entries have no `_id` of their own: select
|
|
738
|
-
`items { _id }` instead.
|
|
739
|
-
- A `Text` entry's content updates whether the query selects `body`,
|
|
740
|
-
`html` or `text` (the latter with tags stripped).
|
|
741
|
-
- Render each dynamic content entry inside an element marked with
|
|
742
|
-
`data-prepr-edit-target="<field>.<index>"` and
|
|
743
|
-
`data-prepr-entry-id="<entry _id>"` (for `Assets`, the first asset's
|
|
744
|
-
`_id`). The index is the fallback when no id matches.
|
|
745
|
-
- Values without a hidden edit link (images, videos, other URLs) are made
|
|
746
|
-
click-to-edit with `previewAttrs({ id, field }, { enabled: preview })`
|
|
747
|
-
on the element that renders them.
|
|
748
|
-
- Where the page renders a linked item whose content comes from items nested
|
|
749
|
-
inside it (for example a personalization or a shared section referenced
|
|
750
|
-
from a stack), wrap it in an element with
|
|
751
|
-
`data-prepr-reference="<linked item _id>"`. The editor only knows the items
|
|
752
|
-
the open item links to directly; content inside the wrapper is outlined as
|
|
753
|
-
that linked item, and clicking it opens the link in the editor.
|
|
754
|
-
|
|
755
|
-
Typed text and changed fields show immediately. Structural changes, such as
|
|
756
|
-
adding or reordering components, appear after save.
|
|
757
|
-
|
|
758
|
-
### Options
|
|
759
|
-
|
|
760
|
-
`PreprRefresh`, `initLivePreview`, `normalizedQuery` and `useNormalizedQuery`
|
|
761
|
-
take the same two options as the toolbar (in Astro, set them on the
|
|
762
|
-
toolbar's `options`):
|
|
763
|
-
|
|
764
|
-
- **`allowedEditorOrigins`** — exact editor origins, replacing the
|
|
765
|
-
`https://*.prepr.io` default. Only needed when Prepr runs on your own domain.
|
|
766
|
-
- **`strictMode`** — set it when your preview token uses GraphQL strict mode
|
|
767
|
-
(required fields never null). The editor then holds back new entries until
|
|
768
|
-
their required fields are filled, and never sends a cleared required field
|
|
769
|
-
as null.
|
|
770
|
-
|
|
771
|
-
### Security
|
|
772
|
-
|
|
773
|
-
- **Disabled means untouched.** With `enabled: false` (the default) results
|
|
774
|
-
are returned as they are and the request is never read, so static and ISR
|
|
775
|
-
pages stay static.
|
|
776
|
-
- **Only the Prepr editor can send changes.** Changes are accepted only from
|
|
777
|
-
`https://*.prepr.io` (or your `allowedEditorOrigins`), never from another
|
|
778
|
-
page that frames your site. They are validated as hostile input: link
|
|
779
|
-
fields never take `javascript:`, `vbscript:` or `data:` URLs. HTML text is
|
|
780
|
-
passed on as typed, like rich text from the API.
|
|
781
|
-
- **One editor's changes never reach anyone else.** On the server-side
|
|
782
|
-
frameworks, changes are read only in preview. Astro accepts them only from
|
|
783
|
-
a same-origin `POST` carrying the toolkit's header and marks that response
|
|
784
|
-
`Cache-Control: private, no-store`. Next.js stores them in the server
|
|
785
|
-
process under a random, single-use token, so a replayed or cached request
|
|
786
|
-
sees nothing.
|
|
787
|
-
- **The Next.js server action is public but inert.** Next.js registers the
|
|
788
|
-
toolkit's server action in every build that imports
|
|
789
|
-
`@preprio/toolkit/nextjs/live`, so anyone can call it. It does nothing
|
|
790
|
-
unless the Prepr middleware handled the request with `preview: true`, and
|
|
791
|
-
the middleware drops `Prepr-*` headers a client sends.
|
|
792
|
-
|
|
793
|
-
### Trying it locally
|
|
794
|
-
|
|
795
|
-
The Next.js, Astro, SvelteKit and Nuxt examples all have live preview wired
|
|
796
|
-
up. Open them from a Prepr environment whose preview URL points at your local
|
|
797
|
-
server.
|
|
798
|
-
|
|
799
779
|
## API Reference
|
|
800
780
|
|
|
801
781
|
### Framework server helpers
|
|
@@ -912,6 +892,26 @@ controller.destroy();
|
|
|
912
892
|
|
|
913
893
|
`trackEvent` and `setTrackingParam` are independent of `loadTrackingPixel` — they work against `window.prepr` however it got installed, including a legacy HTML snippet already on the page. If no pixel is installed at all they warn once in the console and return without throwing.
|
|
914
894
|
|
|
895
|
+
#### `startHtmlLivePreview(options?)`
|
|
896
|
+
|
|
897
|
+
Live preview for server-rendered pages without a client framework. A no-op outside the Prepr editor. Returns `{ stop() }`.
|
|
898
|
+
|
|
899
|
+
| Option | Type | Default | Description |
|
|
900
|
+
| ---------------------- | ------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
901
|
+
| `apply` | `'server' \| 'dom'` | `'server'` | `'server'` requests the page again with the changes (the server applies them with `normalizeQuery`). `'dom'` writes plain-text changes into the page directly and needs no server support; rich text waits for save unless the element opts in with `data-prepr-live="html"`. |
|
|
902
|
+
| `allowedEditorOrigins` | `string[]` | `*.prepr.io` | As in [Preview options](#preview-options). |
|
|
903
|
+
| `strictMode` | `boolean` | `false` | As in [Preview options](#preview-options). |
|
|
904
|
+
|
|
905
|
+
Every mode renders the stored page again on save.
|
|
906
|
+
|
|
907
|
+
#### `applyPatchLogToDom(log, root?)`
|
|
908
|
+
|
|
909
|
+
The `'dom'` mode on its own: writes a parsed patch log's text changes into the elements that render them and returns `{ applied, skipped }`. For custom setups; `startHtmlLivePreview` and `@preprio/toolkit/auto` call it for you.
|
|
910
|
+
|
|
911
|
+
#### `startPreprAuto()`
|
|
912
|
+
|
|
913
|
+
From `@preprio/toolkit/auto`, which also calls it on import. Starts the preview from the page's `data-prepr-toolbar-props` JSON and returns a function that stops it. See [Any backend](#any-backend-drop-in-script).
|
|
914
|
+
|
|
915
915
|
#### `stegaClean(value)`
|
|
916
916
|
|
|
917
917
|
Strips stega-encoded characters from a string. Rarely needed, since cleaning is automatic (see [Visual editing](#visual-editing)), but available when you need a clean value for comparison, sorting, or a non-DOM API.
|
|
@@ -957,15 +957,15 @@ createPreprMiddleware(request, response, { preview });
|
|
|
957
957
|
createPreprPreview({ props, options: { debug: true, locale: 'nl' } });
|
|
958
958
|
```
|
|
959
959
|
|
|
960
|
-
| Option | Type | Default | Description
|
|
961
|
-
| ---------------------- | ------------------------------ | ------------- |
|
|
962
|
-
| `debug` | `boolean` | `false` | Enable debug logging.
|
|
963
|
-
| `locale` | `'en' \| 'nl' \| 'fr' \| 'de'` | auto-detected | UI language. Falls back to the first supported browser language, then `en`. Inside the Prepr editor, the editor's interface language wins.
|
|
964
|
-
| `features` | `PreprFeatures` | all enabled | Which features run. See [Feature flags](#feature-flags).
|
|
965
|
-
| `ui` | `boolean` | `true` | Mount the visible toolbar. See [Headless preview](#headless-preview-no-toolbar-ui).
|
|
966
|
-
| `allowedEditorOrigins` | `string[]` | `*.prepr.io` | Exact editor origins allowed to drive this preview, for self-hosted or local editors. Replaces the wildcard. `['*']` accepts any origin — development only.
|
|
967
|
-
| `strictMode` | `boolean` | `false` | Your preview token uses GraphQL strict mode (required fields never null). The editor then holds back new entries until their required fields are filled and never sends a cleared required field as null. Also on `PreprRefresh`, `initLivePreview
|
|
968
|
-
| `autoClean` | `boolean` | `true` | Strip the invisible stega characters from visible text. See [Stega auto-cleaning](#stega-auto-cleaning).
|
|
960
|
+
| Option | Type | Default | Description |
|
|
961
|
+
| ---------------------- | ------------------------------ | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
962
|
+
| `debug` | `boolean` | `false` | Enable debug logging. |
|
|
963
|
+
| `locale` | `'en' \| 'nl' \| 'fr' \| 'de'` | auto-detected | UI language. Falls back to the first supported browser language, then `en`. Inside the Prepr editor, the editor's interface language wins. |
|
|
964
|
+
| `features` | `PreprFeatures` | all enabled | Which features run. See [Feature flags](#feature-flags). |
|
|
965
|
+
| `ui` | `boolean` | `true` | Mount the visible toolbar. See [Headless preview](#headless-preview-no-toolbar-ui). |
|
|
966
|
+
| `allowedEditorOrigins` | `string[]` | `*.prepr.io` | Exact editor origins allowed to drive this preview, for self-hosted or local editors. Replaces the wildcard. `['*']` accepts any origin — development only. |
|
|
967
|
+
| `strictMode` | `boolean` | `false` | Your preview token uses GraphQL strict mode (required fields never null). The editor then holds back new entries until their required fields are filled and never sends a cleared required field as null. Also on `PreprRefresh`, `initLivePreview` and `useNormalizedQuery`. |
|
|
968
|
+
| `autoClean` | `boolean` | `true` | Strip the invisible stega characters from visible text. See [Stega auto-cleaning](#stega-auto-cleaning). |
|
|
969
969
|
|
|
970
970
|
`features` and `ui` are independent: `features` decides _what runs_, `ui` decides _whether the toolbar is visible_.
|
|
971
971
|
|
|
@@ -1101,14 +1101,6 @@ createPreprPreview({ props, options: { locale: 'nl' } });
|
|
|
1101
1101
|
- **API calls**: personalization only works if you spread `getPreprHeaders()` into your GraphQL fetch. Missing headers is the most common reason segments appear to have no effect.
|
|
1102
1102
|
- **Chained middleware**: when chaining, pass the existing `NextResponse` as the second argument. Creating a fresh `NextResponse.next()` afterwards discards Prepr's request headers.
|
|
1103
1103
|
|
|
1104
|
-
### Live preview does not update
|
|
1105
|
-
|
|
1106
|
-
- **Check `enabled`**: every live preview entry point needs your preview flag, and the middleware must run with `preview: true` for that page.
|
|
1107
|
-
- **Check the preview URL in Prepr**: it must point at a deployment that uses the GraphQL Preview token. A production build with preview off shows saved content only.
|
|
1108
|
-
- **Check `_id`**: an item or component whose query does not select `_id` stays static. See [Preparing your queries](#preparing-your-queries).
|
|
1109
|
-
- **Check the frame**: a `Refused to frame` error in the browser console means your `Content-Security-Policy` `frame-ancestors` does not list your Prepr environment.
|
|
1110
|
-
- **Next.js only shows changes on save**: `normalizeQuery` with `PreprRefresh` needs Next.js 16 or later.
|
|
1111
|
-
|
|
1112
1104
|
### Segments switch but content does not change
|
|
1113
1105
|
|
|
1114
1106
|
The toolbar sets the segment; your data fetch decides what to do with it. Check that the fetch forwards `getPreprHeaders()` and is not being served from cache — use `cache: 'no-store'`, or Apollo's `fetchPolicy: 'no-cache'`, on personalized queries.
|
package/dist/astro/index.cjs
CHANGED
package/dist/astro/index.js
CHANGED