payload-live-preview 1.0.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/LICENSE +21 -0
- package/README.md +389 -0
- package/dist/adapters/astro/RichText.astro +55 -0
- package/dist/adapters/astro/index.cjs +370 -0
- package/dist/adapters/astro/index.cjs.map +1 -0
- package/dist/adapters/astro/index.d.cts +105 -0
- package/dist/adapters/astro/index.d.ts +105 -0
- package/dist/adapters/astro/index.js +364 -0
- package/dist/adapters/astro/index.js.map +1 -0
- package/dist/adapters/astro/middleware-entry.cjs +303 -0
- package/dist/adapters/astro/middleware-entry.cjs.map +1 -0
- package/dist/adapters/astro/middleware-entry.d.cts +15 -0
- package/dist/adapters/astro/middleware-entry.d.ts +15 -0
- package/dist/adapters/astro/middleware-entry.js +297 -0
- package/dist/adapters/astro/middleware-entry.js.map +1 -0
- package/dist/adapters/nextjs/index.cjs +309 -0
- package/dist/adapters/nextjs/index.cjs.map +1 -0
- package/dist/adapters/nextjs/index.d.cts +75 -0
- package/dist/adapters/nextjs/index.d.ts +75 -0
- package/dist/adapters/nextjs/index.js +306 -0
- package/dist/adapters/nextjs/index.js.map +1 -0
- package/dist/adapters/nuxt/index.cjs +299 -0
- package/dist/adapters/nuxt/index.cjs.map +1 -0
- package/dist/adapters/nuxt/index.d.cts +126 -0
- package/dist/adapters/nuxt/index.d.ts +126 -0
- package/dist/adapters/nuxt/index.js +294 -0
- package/dist/adapters/nuxt/index.js.map +1 -0
- package/dist/adapters/sveltekit/index.cjs +275 -0
- package/dist/adapters/sveltekit/index.cjs.map +1 -0
- package/dist/adapters/sveltekit/index.d.cts +80 -0
- package/dist/adapters/sveltekit/index.d.ts +80 -0
- package/dist/adapters/sveltekit/index.js +273 -0
- package/dist/adapters/sveltekit/index.js.map +1 -0
- package/dist/bind-BSgxoQhN.d.cts +1137 -0
- package/dist/bind-BSgxoQhN.d.ts +1137 -0
- package/dist/codegen-astro.cjs +612 -0
- package/dist/codegen-astro.cjs.map +1 -0
- package/dist/codegen-astro.d.cts +54 -0
- package/dist/codegen-astro.d.ts +54 -0
- package/dist/codegen-astro.js +610 -0
- package/dist/codegen-astro.js.map +1 -0
- package/dist/codegen-cli.cjs +669 -0
- package/dist/codegen-cli.cjs.map +1 -0
- package/dist/codegen-cli.d.cts +19 -0
- package/dist/codegen-cli.d.ts +19 -0
- package/dist/codegen-cli.js +667 -0
- package/dist/codegen-cli.js.map +1 -0
- package/dist/codegen.cjs +551 -0
- package/dist/codegen.cjs.map +1 -0
- package/dist/codegen.d.cts +162 -0
- package/dist/codegen.d.ts +162 -0
- package/dist/codegen.js +547 -0
- package/dist/codegen.js.map +1 -0
- package/dist/core.cjs +3684 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +24 -0
- package/dist/core.d.ts +24 -0
- package/dist/core.js +3657 -0
- package/dist/core.js.map +1 -0
- package/dist/index.cjs +4083 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +472 -0
- package/dist/index.d.ts +472 -0
- package/dist/index.js +4040 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware-BwIlf0jW.d.cts +168 -0
- package/dist/middleware-BwIlf0jW.d.ts +168 -0
- package/dist/payload.cjs +31 -0
- package/dist/payload.cjs.map +1 -0
- package/dist/payload.d.cts +87 -0
- package/dist/payload.d.ts +87 -0
- package/dist/payload.js +29 -0
- package/dist/payload.js.map +1 -0
- package/dist/preview-request-DAvxm18T.d.cts +68 -0
- package/dist/preview-request-DAvxm18T.d.ts +68 -0
- package/package.json +186 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for the Astro adapter.
|
|
3
|
+
*
|
|
4
|
+
* @module @adapters/astro/types
|
|
5
|
+
*/
|
|
6
|
+
interface LivePreviewAstroOptions {
|
|
7
|
+
/**
|
|
8
|
+
* How the integration wires the preview:
|
|
9
|
+
*
|
|
10
|
+
* - `'inline'` (default) — bake the runtime into every page at
|
|
11
|
+
* build time. For `output: 'static'` projects.
|
|
12
|
+
* - `'middleware'` — auto-register the preview middleware:
|
|
13
|
+
* request-time injection into preview requests only, plus
|
|
14
|
+
* `frame-ancestors` CSP management. For `output: 'server'`.
|
|
15
|
+
* (`shouldInject` is unsupported here — options serialize into
|
|
16
|
+
* the build.)
|
|
17
|
+
*/
|
|
18
|
+
readonly mode?: 'inline' | 'middleware';
|
|
19
|
+
/**
|
|
20
|
+
* Trusted Payload admin origin(s). Required in production unless
|
|
21
|
+
* `document.referrer` reliably exposes the parent origin.
|
|
22
|
+
*/
|
|
23
|
+
readonly allowedOrigins?: readonly string[];
|
|
24
|
+
/**
|
|
25
|
+
* Payload server origin, e.g. `https://cms.example.com`. When set,
|
|
26
|
+
* incoming updates are re-fetched through the Payload REST API so
|
|
27
|
+
* relationship/upload fields render populated instead of as bare
|
|
28
|
+
* IDs. Strongly recommended for Payload 3.x.
|
|
29
|
+
*/
|
|
30
|
+
readonly serverURL?: string;
|
|
31
|
+
/** REST API route prefix used with `serverURL`. Defaults to `/api`. */
|
|
32
|
+
readonly apiRoute?: string;
|
|
33
|
+
/** Population depth used with `serverURL`. Defaults to `1`. */
|
|
34
|
+
readonly mergeDepth?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Auto-inject the inline preview script. Default `true`. Disable to
|
|
37
|
+
* embed the script manually via `renderLivePreviewScript()`.
|
|
38
|
+
*/
|
|
39
|
+
readonly autoInject?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* When the middleware injects the script (integration-based
|
|
42
|
+
* injection is always build-time and unaffected):
|
|
43
|
+
*
|
|
44
|
+
* - `'preview-only'` (default) — only into responses that look
|
|
45
|
+
* like preview requests (`?preview=true` / `?draft=true` query,
|
|
46
|
+
* `Sec-Fetch-Dest: iframe`, or a referer from `allowedOrigins`).
|
|
47
|
+
* Normal production traffic passes through untouched.
|
|
48
|
+
* - `'always'` — into every HTML response.
|
|
49
|
+
*/
|
|
50
|
+
readonly inject?: 'preview-only' | 'always';
|
|
51
|
+
/**
|
|
52
|
+
* Query parameters that mark a request as a preview (values `true`
|
|
53
|
+
* or `1`). Default: `['preview', 'draft', 'livePreview']`.
|
|
54
|
+
*/
|
|
55
|
+
readonly previewQueryParams?: readonly string[];
|
|
56
|
+
/**
|
|
57
|
+
* Which signals count as a preview request. Default: all three
|
|
58
|
+
* (`query`, `fetch-dest`, `referer`). Restrict to `['query']` when
|
|
59
|
+
* an unsolicited iframe load must never trigger preview handling
|
|
60
|
+
* (e.g. sites that serve `frame-ancestors 'none'` and only allow
|
|
61
|
+
* preview via the explicit `?preview=true` URL).
|
|
62
|
+
*/
|
|
63
|
+
readonly previewSignals?: readonly ('query' | 'fetch-dest' | 'referer')[];
|
|
64
|
+
/**
|
|
65
|
+
* Restrict auto-inject to requests where this function returns
|
|
66
|
+
* `true`. Applied on top of the `inject` mode.
|
|
67
|
+
*/
|
|
68
|
+
readonly shouldInject?: (request: Request) => boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Content-Security-Policy management on preview responses:
|
|
71
|
+
*
|
|
72
|
+
* - `'frame-ancestors'` (default) — merge a `frame-ancestors`
|
|
73
|
+
* directive allowing the admin origins to embed the page.
|
|
74
|
+
* Existing CSP directives are preserved (union merge).
|
|
75
|
+
* - `'full'` — additionally manage `script-src` with a
|
|
76
|
+
* per-request nonce. Only useful when your whole page is
|
|
77
|
+
* nonce-disciplined; see `strictDynamic`.
|
|
78
|
+
* - `false` — never touch CSP headers.
|
|
79
|
+
*
|
|
80
|
+
* `true` is accepted as a legacy alias for `'frame-ancestors'`.
|
|
81
|
+
*/
|
|
82
|
+
readonly manageCsp?: boolean | 'frame-ancestors' | 'full';
|
|
83
|
+
/**
|
|
84
|
+
* Add `'strict-dynamic'` to the managed `script-src` (only with
|
|
85
|
+
* `manageCsp: 'full'`). ⚠️ Under CSP 3 this makes browsers ignore
|
|
86
|
+
* `'self'` and host sources — every script on the page must then
|
|
87
|
+
* carry the nonce or be loaded by a nonce-carrying script. Astro's
|
|
88
|
+
* own hydration scripts do NOT carry it, so leave this off unless
|
|
89
|
+
* you know your page is fully nonce-disciplined. Default `false`.
|
|
90
|
+
*/
|
|
91
|
+
readonly strictDynamic?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Extra `frame-ancestors` sources to merge with the auto-detected
|
|
94
|
+
* ones. Always includes `'self'` and every entry in `allowedOrigins`.
|
|
95
|
+
*/
|
|
96
|
+
readonly frameAncestorsExtra?: readonly string[];
|
|
97
|
+
/**
|
|
98
|
+
* Extra `script-src` sources (e.g., a CDN) appended after the nonce
|
|
99
|
+
* (only with `manageCsp: 'full'`).
|
|
100
|
+
*/
|
|
101
|
+
readonly scriptSrcExtra?: readonly string[];
|
|
102
|
+
/**
|
|
103
|
+
* Enable verbose debug logging in the injected client. Default: dev mode.
|
|
104
|
+
*/
|
|
105
|
+
readonly debug?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Debounce window for incoming updates. Default: 50 ms.
|
|
108
|
+
*/
|
|
109
|
+
readonly debounceMs?: number;
|
|
110
|
+
/**
|
|
111
|
+
* Heartbeat timeout in ms. Default `0` (disabled — the Payload admin
|
|
112
|
+
* sends no keepalive, so an idle timeout would cause false
|
|
113
|
+
* disconnects).
|
|
114
|
+
*/
|
|
115
|
+
readonly heartbeatMs?: number;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Astro middleware for Payload Live Preview.
|
|
120
|
+
*
|
|
121
|
+
* Responsibilities (per request):
|
|
122
|
+
*
|
|
123
|
+
* 1. Generate a per-response CSP nonce and stash it on
|
|
124
|
+
* `Astro.locals.livePreviewNonce` so consumers can reference it.
|
|
125
|
+
* 2. On **preview requests only** (see `isPreviewRequest`), rewrite
|
|
126
|
+
* the HTML response to inject the live-preview `<script>` tag.
|
|
127
|
+
* Normal production traffic streams through untouched — no
|
|
128
|
+
* buffering, no extra bytes.
|
|
129
|
+
* 3. On preview requests, merge `frame-ancestors` into the
|
|
130
|
+
* `Content-Security-Policy` header so the Payload admin may embed
|
|
131
|
+
* the page. Full `script-src` nonce management is opt-in via
|
|
132
|
+
* `manageCsp: 'full'`.
|
|
133
|
+
*
|
|
134
|
+
* Prerendered pages are skipped entirely: Astro runs middleware at
|
|
135
|
+
* build time for those, where per-request nonces and response headers
|
|
136
|
+
* are meaningless.
|
|
137
|
+
*
|
|
138
|
+
* @module @adapters/astro/middleware
|
|
139
|
+
*/
|
|
140
|
+
|
|
141
|
+
type MiddlewareNext = () => Promise<Response>;
|
|
142
|
+
interface MiddlewareContext {
|
|
143
|
+
readonly request: Request;
|
|
144
|
+
readonly locals: Record<string, unknown>;
|
|
145
|
+
/** Present in Astro ≥ 5: `true` while prerendering at build time. */
|
|
146
|
+
readonly isPrerendered?: boolean;
|
|
147
|
+
}
|
|
148
|
+
type LivePreviewMiddleware = (context: MiddlewareContext, next: MiddlewareNext) => Promise<Response>;
|
|
149
|
+
/**
|
|
150
|
+
* Key on `Astro.locals` carrying the request-scoped nonce. Consumers
|
|
151
|
+
* can read `Astro.locals.livePreviewNonce` from `.astro` templates to
|
|
152
|
+
* set the `nonce` attribute on their own scripts.
|
|
153
|
+
*/
|
|
154
|
+
declare const NONCE_LOCALS_KEY = "livePreviewNonce";
|
|
155
|
+
/**
|
|
156
|
+
* Build the Astro-compatible middleware. Register it in
|
|
157
|
+
* `src/middleware.ts`:
|
|
158
|
+
*
|
|
159
|
+
* ```ts
|
|
160
|
+
* import { createLivePreviewMiddleware } from 'payload-live-preview/astro';
|
|
161
|
+
* export const onRequest = createLivePreviewMiddleware({
|
|
162
|
+
* allowedOrigins: [import.meta.env.PUBLIC_PAYLOAD_ADMIN_ORIGIN],
|
|
163
|
+
* });
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
declare function createLivePreviewMiddleware(options?: LivePreviewAstroOptions): LivePreviewMiddleware;
|
|
167
|
+
|
|
168
|
+
export { type LivePreviewAstroOptions as L, NONCE_LOCALS_KEY as N, type LivePreviewMiddleware as a, createLivePreviewMiddleware as c };
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public types for the Astro adapter.
|
|
3
|
+
*
|
|
4
|
+
* @module @adapters/astro/types
|
|
5
|
+
*/
|
|
6
|
+
interface LivePreviewAstroOptions {
|
|
7
|
+
/**
|
|
8
|
+
* How the integration wires the preview:
|
|
9
|
+
*
|
|
10
|
+
* - `'inline'` (default) — bake the runtime into every page at
|
|
11
|
+
* build time. For `output: 'static'` projects.
|
|
12
|
+
* - `'middleware'` — auto-register the preview middleware:
|
|
13
|
+
* request-time injection into preview requests only, plus
|
|
14
|
+
* `frame-ancestors` CSP management. For `output: 'server'`.
|
|
15
|
+
* (`shouldInject` is unsupported here — options serialize into
|
|
16
|
+
* the build.)
|
|
17
|
+
*/
|
|
18
|
+
readonly mode?: 'inline' | 'middleware';
|
|
19
|
+
/**
|
|
20
|
+
* Trusted Payload admin origin(s). Required in production unless
|
|
21
|
+
* `document.referrer` reliably exposes the parent origin.
|
|
22
|
+
*/
|
|
23
|
+
readonly allowedOrigins?: readonly string[];
|
|
24
|
+
/**
|
|
25
|
+
* Payload server origin, e.g. `https://cms.example.com`. When set,
|
|
26
|
+
* incoming updates are re-fetched through the Payload REST API so
|
|
27
|
+
* relationship/upload fields render populated instead of as bare
|
|
28
|
+
* IDs. Strongly recommended for Payload 3.x.
|
|
29
|
+
*/
|
|
30
|
+
readonly serverURL?: string;
|
|
31
|
+
/** REST API route prefix used with `serverURL`. Defaults to `/api`. */
|
|
32
|
+
readonly apiRoute?: string;
|
|
33
|
+
/** Population depth used with `serverURL`. Defaults to `1`. */
|
|
34
|
+
readonly mergeDepth?: number;
|
|
35
|
+
/**
|
|
36
|
+
* Auto-inject the inline preview script. Default `true`. Disable to
|
|
37
|
+
* embed the script manually via `renderLivePreviewScript()`.
|
|
38
|
+
*/
|
|
39
|
+
readonly autoInject?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* When the middleware injects the script (integration-based
|
|
42
|
+
* injection is always build-time and unaffected):
|
|
43
|
+
*
|
|
44
|
+
* - `'preview-only'` (default) — only into responses that look
|
|
45
|
+
* like preview requests (`?preview=true` / `?draft=true` query,
|
|
46
|
+
* `Sec-Fetch-Dest: iframe`, or a referer from `allowedOrigins`).
|
|
47
|
+
* Normal production traffic passes through untouched.
|
|
48
|
+
* - `'always'` — into every HTML response.
|
|
49
|
+
*/
|
|
50
|
+
readonly inject?: 'preview-only' | 'always';
|
|
51
|
+
/**
|
|
52
|
+
* Query parameters that mark a request as a preview (values `true`
|
|
53
|
+
* or `1`). Default: `['preview', 'draft', 'livePreview']`.
|
|
54
|
+
*/
|
|
55
|
+
readonly previewQueryParams?: readonly string[];
|
|
56
|
+
/**
|
|
57
|
+
* Which signals count as a preview request. Default: all three
|
|
58
|
+
* (`query`, `fetch-dest`, `referer`). Restrict to `['query']` when
|
|
59
|
+
* an unsolicited iframe load must never trigger preview handling
|
|
60
|
+
* (e.g. sites that serve `frame-ancestors 'none'` and only allow
|
|
61
|
+
* preview via the explicit `?preview=true` URL).
|
|
62
|
+
*/
|
|
63
|
+
readonly previewSignals?: readonly ('query' | 'fetch-dest' | 'referer')[];
|
|
64
|
+
/**
|
|
65
|
+
* Restrict auto-inject to requests where this function returns
|
|
66
|
+
* `true`. Applied on top of the `inject` mode.
|
|
67
|
+
*/
|
|
68
|
+
readonly shouldInject?: (request: Request) => boolean;
|
|
69
|
+
/**
|
|
70
|
+
* Content-Security-Policy management on preview responses:
|
|
71
|
+
*
|
|
72
|
+
* - `'frame-ancestors'` (default) — merge a `frame-ancestors`
|
|
73
|
+
* directive allowing the admin origins to embed the page.
|
|
74
|
+
* Existing CSP directives are preserved (union merge).
|
|
75
|
+
* - `'full'` — additionally manage `script-src` with a
|
|
76
|
+
* per-request nonce. Only useful when your whole page is
|
|
77
|
+
* nonce-disciplined; see `strictDynamic`.
|
|
78
|
+
* - `false` — never touch CSP headers.
|
|
79
|
+
*
|
|
80
|
+
* `true` is accepted as a legacy alias for `'frame-ancestors'`.
|
|
81
|
+
*/
|
|
82
|
+
readonly manageCsp?: boolean | 'frame-ancestors' | 'full';
|
|
83
|
+
/**
|
|
84
|
+
* Add `'strict-dynamic'` to the managed `script-src` (only with
|
|
85
|
+
* `manageCsp: 'full'`). ⚠️ Under CSP 3 this makes browsers ignore
|
|
86
|
+
* `'self'` and host sources — every script on the page must then
|
|
87
|
+
* carry the nonce or be loaded by a nonce-carrying script. Astro's
|
|
88
|
+
* own hydration scripts do NOT carry it, so leave this off unless
|
|
89
|
+
* you know your page is fully nonce-disciplined. Default `false`.
|
|
90
|
+
*/
|
|
91
|
+
readonly strictDynamic?: boolean;
|
|
92
|
+
/**
|
|
93
|
+
* Extra `frame-ancestors` sources to merge with the auto-detected
|
|
94
|
+
* ones. Always includes `'self'` and every entry in `allowedOrigins`.
|
|
95
|
+
*/
|
|
96
|
+
readonly frameAncestorsExtra?: readonly string[];
|
|
97
|
+
/**
|
|
98
|
+
* Extra `script-src` sources (e.g., a CDN) appended after the nonce
|
|
99
|
+
* (only with `manageCsp: 'full'`).
|
|
100
|
+
*/
|
|
101
|
+
readonly scriptSrcExtra?: readonly string[];
|
|
102
|
+
/**
|
|
103
|
+
* Enable verbose debug logging in the injected client. Default: dev mode.
|
|
104
|
+
*/
|
|
105
|
+
readonly debug?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Debounce window for incoming updates. Default: 50 ms.
|
|
108
|
+
*/
|
|
109
|
+
readonly debounceMs?: number;
|
|
110
|
+
/**
|
|
111
|
+
* Heartbeat timeout in ms. Default `0` (disabled — the Payload admin
|
|
112
|
+
* sends no keepalive, so an idle timeout would cause false
|
|
113
|
+
* disconnects).
|
|
114
|
+
*/
|
|
115
|
+
readonly heartbeatMs?: number;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Astro middleware for Payload Live Preview.
|
|
120
|
+
*
|
|
121
|
+
* Responsibilities (per request):
|
|
122
|
+
*
|
|
123
|
+
* 1. Generate a per-response CSP nonce and stash it on
|
|
124
|
+
* `Astro.locals.livePreviewNonce` so consumers can reference it.
|
|
125
|
+
* 2. On **preview requests only** (see `isPreviewRequest`), rewrite
|
|
126
|
+
* the HTML response to inject the live-preview `<script>` tag.
|
|
127
|
+
* Normal production traffic streams through untouched — no
|
|
128
|
+
* buffering, no extra bytes.
|
|
129
|
+
* 3. On preview requests, merge `frame-ancestors` into the
|
|
130
|
+
* `Content-Security-Policy` header so the Payload admin may embed
|
|
131
|
+
* the page. Full `script-src` nonce management is opt-in via
|
|
132
|
+
* `manageCsp: 'full'`.
|
|
133
|
+
*
|
|
134
|
+
* Prerendered pages are skipped entirely: Astro runs middleware at
|
|
135
|
+
* build time for those, where per-request nonces and response headers
|
|
136
|
+
* are meaningless.
|
|
137
|
+
*
|
|
138
|
+
* @module @adapters/astro/middleware
|
|
139
|
+
*/
|
|
140
|
+
|
|
141
|
+
type MiddlewareNext = () => Promise<Response>;
|
|
142
|
+
interface MiddlewareContext {
|
|
143
|
+
readonly request: Request;
|
|
144
|
+
readonly locals: Record<string, unknown>;
|
|
145
|
+
/** Present in Astro ≥ 5: `true` while prerendering at build time. */
|
|
146
|
+
readonly isPrerendered?: boolean;
|
|
147
|
+
}
|
|
148
|
+
type LivePreviewMiddleware = (context: MiddlewareContext, next: MiddlewareNext) => Promise<Response>;
|
|
149
|
+
/**
|
|
150
|
+
* Key on `Astro.locals` carrying the request-scoped nonce. Consumers
|
|
151
|
+
* can read `Astro.locals.livePreviewNonce` from `.astro` templates to
|
|
152
|
+
* set the `nonce` attribute on their own scripts.
|
|
153
|
+
*/
|
|
154
|
+
declare const NONCE_LOCALS_KEY = "livePreviewNonce";
|
|
155
|
+
/**
|
|
156
|
+
* Build the Astro-compatible middleware. Register it in
|
|
157
|
+
* `src/middleware.ts`:
|
|
158
|
+
*
|
|
159
|
+
* ```ts
|
|
160
|
+
* import { createLivePreviewMiddleware } from 'payload-live-preview/astro';
|
|
161
|
+
* export const onRequest = createLivePreviewMiddleware({
|
|
162
|
+
* allowedOrigins: [import.meta.env.PUBLIC_PAYLOAD_ADMIN_ORIGIN],
|
|
163
|
+
* });
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
declare function createLivePreviewMiddleware(options?: LivePreviewAstroOptions): LivePreviewMiddleware;
|
|
167
|
+
|
|
168
|
+
export { type LivePreviewAstroOptions as L, NONCE_LOCALS_KEY as N, type LivePreviewMiddleware as a, createLivePreviewMiddleware as c };
|
package/dist/payload.cjs
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// src/payload/index.ts
|
|
4
|
+
function buildLivePreviewUrl(options) {
|
|
5
|
+
const base = options.baseUrl.replace(/\/+$/, "");
|
|
6
|
+
const fallback = options.fallback ?? "/";
|
|
7
|
+
const previewParam = options.previewParam === void 0 ? "preview" : options.previewParam;
|
|
8
|
+
return (args) => {
|
|
9
|
+
const locale = normaliseLocale(args.locale);
|
|
10
|
+
const context = { data: args.data, locale };
|
|
11
|
+
const resolver = (args.collectionConfig !== void 0 ? options.collections?.[args.collectionConfig.slug] : void 0) ?? (args.globalConfig !== void 0 ? options.globals?.[args.globalConfig.slug] : void 0);
|
|
12
|
+
let path = fallback;
|
|
13
|
+
if (resolver !== void 0) {
|
|
14
|
+
const resolved = typeof resolver === "function" ? resolver(context) : resolver;
|
|
15
|
+
if (resolved.length > 0) path = resolved;
|
|
16
|
+
}
|
|
17
|
+
if (!path.startsWith("/")) path = `/${path}`;
|
|
18
|
+
if (previewParam === null) return `${base}${path}`;
|
|
19
|
+
const separator = path.includes("?") ? "&" : "?";
|
|
20
|
+
return `${base}${path}${separator}${previewParam}=true`;
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function normaliseLocale(locale) {
|
|
24
|
+
if (locale === void 0) return void 0;
|
|
25
|
+
if (typeof locale === "string") return locale.length > 0 ? locale : void 0;
|
|
26
|
+
return locale.code.length > 0 ? locale.code : void 0;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
exports.buildLivePreviewUrl = buildLivePreviewUrl;
|
|
30
|
+
//# sourceMappingURL=payload.cjs.map
|
|
31
|
+
//# sourceMappingURL=payload.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/payload/index.ts"],"names":[],"mappings":";;;AAmFO,SAAS,oBACd,OAAA,EACsC;AACtC,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,OAAA,CAAQ,OAAA,CAAQ,QAAQ,EAAE,CAAA;AAC/C,EAAA,MAAM,QAAA,GAAW,QAAQ,QAAA,IAAY,GAAA;AACrC,EAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,YAAA,KAAiB,MAAA,GAAY,YAAY,OAAA,CAAQ,YAAA;AAE9E,EAAA,OAAO,CAAC,IAAA,KAAS;AACf,IAAA,MAAM,MAAA,GAAS,eAAA,CAAgB,IAAA,CAAK,MAAM,CAAA;AAC1C,IAAA,MAAM,OAAA,GAA+B,EAAE,IAAA,EAAM,IAAA,CAAK,MAAM,MAAA,EAAO;AAE/D,IAAA,MAAM,YACH,IAAA,CAAK,gBAAA,KAAqB,SACvB,OAAA,CAAQ,WAAA,GAAc,KAAK,gBAAA,CAAiB,IAAI,IAChD,MAAA,MACH,IAAA,CAAK,iBAAiB,MAAA,GAAY,OAAA,CAAQ,UAAU,IAAA,CAAK,YAAA,CAAa,IAAI,CAAA,GAAI,MAAA,CAAA;AAEjF,IAAA,IAAI,IAAA,GAAO,QAAA;AACX,IAAA,IAAI,aAAa,MAAA,EAAW;AAC1B,MAAA,MAAM,WAAW,OAAO,QAAA,KAAa,UAAA,GAAa,QAAA,CAAS,OAAO,CAAA,GAAI,QAAA;AACtE,MAAA,IAAI,QAAA,CAAS,MAAA,GAAS,CAAA,EAAG,IAAA,GAAO,QAAA;AAAA,IAClC;AACA,IAAA,IAAI,CAAC,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,EAAG,IAAA,GAAO,IAAI,IAAI,CAAA,CAAA;AAE1C,IAAA,IAAI,iBAAiB,IAAA,EAAM,OAAO,CAAA,EAAG,IAAI,GAAG,IAAI,CAAA,CAAA;AAChD,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,QAAA,CAAS,GAAG,IAAI,GAAA,GAAM,GAAA;AAC7C,IAAA,OAAO,GAAG,IAAI,CAAA,EAAG,IAAI,CAAA,EAAG,SAAS,GAAG,YAAY,CAAA,KAAA,CAAA;AAAA,EAClD,CAAA;AACF;AAEA,SAAS,gBAAgB,MAAA,EAA0D;AACjF,EAAA,IAAI,MAAA,KAAW,QAAW,OAAO,MAAA;AACjC,EAAA,IAAI,OAAO,MAAA,KAAW,QAAA,SAAiB,MAAA,CAAO,MAAA,GAAS,IAAI,MAAA,GAAS,MAAA;AACpE,EAAA,OAAO,MAAA,CAAO,IAAA,CAAK,MAAA,GAAS,CAAA,GAAI,OAAO,IAAA,GAAO,MAAA;AAChD","file":"payload.cjs","sourcesContent":["/**\n * Backend-side helpers for `payload.config.ts`.\n *\n * Payload's `admin.livePreview.url` callback maps the document being\n * edited to the frontend URL that should be shown in the preview\n * iframe. In practice every project hand-writes the same lookup-table\n * boilerplate (global → path, collection → localized route, fallback\n * when a new draft has no slug yet). `buildLivePreviewUrl` packages\n * that pattern.\n *\n * This module is dependency-free and framework-agnostic — it does not\n * import `payload`, it just produces a function with the documented\n * callback shape.\n *\n * ```ts\n * // payload.config.ts\n * import { buildLivePreviewUrl } from 'payload-live-preview/payload';\n *\n * admin: {\n * livePreview: {\n * url: buildLivePreviewUrl({\n * baseUrl: process.env.FRONTEND_URL ?? 'http://localhost:4321',\n * collections: {\n * services: ({ data, locale }) => `/${locale}/services/${String(data.slug ?? '')}`,\n * posts: ({ data }) => `/blog/${String(data.slug ?? '')}`,\n * },\n * globals: {\n * homepage: '/',\n * contact: ({ locale }) => `/${locale}/contact`,\n * },\n * fallback: '/',\n * }),\n * collections: ['services', 'posts'],\n * globals: ['homepage', 'contact'],\n * },\n * }\n * ```\n *\n * @module @payload\n */\n\n/** Arguments Payload passes to `admin.livePreview.url`. */\nexport interface LivePreviewUrlArgs {\n readonly data: Record<string, unknown>;\n readonly locale?: string | { readonly code: string };\n readonly collectionConfig?: { readonly slug: string };\n readonly globalConfig?: { readonly slug: string };\n readonly [extra: string]: unknown;\n}\n\n/** Context handed to per-slug path resolvers. */\nexport interface PathResolverContext {\n readonly data: Record<string, unknown>;\n /** Normalised locale code (string), or `undefined`. */\n readonly locale: string | undefined;\n}\n\nexport type PathResolver = string | ((context: PathResolverContext) => string);\n\nexport interface BuildLivePreviewUrlOptions {\n /** Frontend origin, e.g. `https://site.example` (no trailing slash needed). */\n readonly baseUrl: string;\n /** Per-collection path resolvers keyed by collection slug. */\n readonly collections?: Readonly<Record<string, PathResolver>>;\n /** Per-global path resolvers keyed by global slug. */\n readonly globals?: Readonly<Record<string, PathResolver>>;\n /**\n * Path used when no resolver matches, or when a resolver returns an\n * empty string (e.g. a brand-new draft without a slug). Default `/`.\n */\n readonly fallback?: string;\n /**\n * Query parameter appended so the frontend can recognise the request\n * as a preview (`isPreviewRequest` checks it). Set `null` to disable.\n * Default `'preview'` → `?preview=true`.\n */\n readonly previewParam?: string | null;\n}\n\n/**\n * Build an `admin.livePreview.url` callback from declarative slug →\n * path mappings.\n */\nexport function buildLivePreviewUrl(\n options: BuildLivePreviewUrlOptions,\n): (args: LivePreviewUrlArgs) => string {\n const base = options.baseUrl.replace(/\\/+$/, '');\n const fallback = options.fallback ?? '/';\n const previewParam = options.previewParam === undefined ? 'preview' : options.previewParam;\n\n return (args) => {\n const locale = normaliseLocale(args.locale);\n const context: PathResolverContext = { data: args.data, locale };\n\n const resolver =\n (args.collectionConfig !== undefined\n ? options.collections?.[args.collectionConfig.slug]\n : undefined) ??\n (args.globalConfig !== undefined ? options.globals?.[args.globalConfig.slug] : undefined);\n\n let path = fallback;\n if (resolver !== undefined) {\n const resolved = typeof resolver === 'function' ? resolver(context) : resolver;\n if (resolved.length > 0) path = resolved;\n }\n if (!path.startsWith('/')) path = `/${path}`;\n\n if (previewParam === null) return `${base}${path}`;\n const separator = path.includes('?') ? '&' : '?';\n return `${base}${path}${separator}${previewParam}=true`;\n };\n}\n\nfunction normaliseLocale(locale: LivePreviewUrlArgs['locale']): string | undefined {\n if (locale === undefined) return undefined;\n if (typeof locale === 'string') return locale.length > 0 ? locale : undefined;\n return locale.code.length > 0 ? locale.code : undefined;\n}\n"]}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend-side helpers for `payload.config.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Payload's `admin.livePreview.url` callback maps the document being
|
|
5
|
+
* edited to the frontend URL that should be shown in the preview
|
|
6
|
+
* iframe. In practice every project hand-writes the same lookup-table
|
|
7
|
+
* boilerplate (global → path, collection → localized route, fallback
|
|
8
|
+
* when a new draft has no slug yet). `buildLivePreviewUrl` packages
|
|
9
|
+
* that pattern.
|
|
10
|
+
*
|
|
11
|
+
* This module is dependency-free and framework-agnostic — it does not
|
|
12
|
+
* import `payload`, it just produces a function with the documented
|
|
13
|
+
* callback shape.
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* // payload.config.ts
|
|
17
|
+
* import { buildLivePreviewUrl } from 'payload-live-preview/payload';
|
|
18
|
+
*
|
|
19
|
+
* admin: {
|
|
20
|
+
* livePreview: {
|
|
21
|
+
* url: buildLivePreviewUrl({
|
|
22
|
+
* baseUrl: process.env.FRONTEND_URL ?? 'http://localhost:4321',
|
|
23
|
+
* collections: {
|
|
24
|
+
* services: ({ data, locale }) => `/${locale}/services/${String(data.slug ?? '')}`,
|
|
25
|
+
* posts: ({ data }) => `/blog/${String(data.slug ?? '')}`,
|
|
26
|
+
* },
|
|
27
|
+
* globals: {
|
|
28
|
+
* homepage: '/',
|
|
29
|
+
* contact: ({ locale }) => `/${locale}/contact`,
|
|
30
|
+
* },
|
|
31
|
+
* fallback: '/',
|
|
32
|
+
* }),
|
|
33
|
+
* collections: ['services', 'posts'],
|
|
34
|
+
* globals: ['homepage', 'contact'],
|
|
35
|
+
* },
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @module @payload
|
|
40
|
+
*/
|
|
41
|
+
/** Arguments Payload passes to `admin.livePreview.url`. */
|
|
42
|
+
interface LivePreviewUrlArgs {
|
|
43
|
+
readonly data: Record<string, unknown>;
|
|
44
|
+
readonly locale?: string | {
|
|
45
|
+
readonly code: string;
|
|
46
|
+
};
|
|
47
|
+
readonly collectionConfig?: {
|
|
48
|
+
readonly slug: string;
|
|
49
|
+
};
|
|
50
|
+
readonly globalConfig?: {
|
|
51
|
+
readonly slug: string;
|
|
52
|
+
};
|
|
53
|
+
readonly [extra: string]: unknown;
|
|
54
|
+
}
|
|
55
|
+
/** Context handed to per-slug path resolvers. */
|
|
56
|
+
interface PathResolverContext {
|
|
57
|
+
readonly data: Record<string, unknown>;
|
|
58
|
+
/** Normalised locale code (string), or `undefined`. */
|
|
59
|
+
readonly locale: string | undefined;
|
|
60
|
+
}
|
|
61
|
+
type PathResolver = string | ((context: PathResolverContext) => string);
|
|
62
|
+
interface BuildLivePreviewUrlOptions {
|
|
63
|
+
/** Frontend origin, e.g. `https://site.example` (no trailing slash needed). */
|
|
64
|
+
readonly baseUrl: string;
|
|
65
|
+
/** Per-collection path resolvers keyed by collection slug. */
|
|
66
|
+
readonly collections?: Readonly<Record<string, PathResolver>>;
|
|
67
|
+
/** Per-global path resolvers keyed by global slug. */
|
|
68
|
+
readonly globals?: Readonly<Record<string, PathResolver>>;
|
|
69
|
+
/**
|
|
70
|
+
* Path used when no resolver matches, or when a resolver returns an
|
|
71
|
+
* empty string (e.g. a brand-new draft without a slug). Default `/`.
|
|
72
|
+
*/
|
|
73
|
+
readonly fallback?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Query parameter appended so the frontend can recognise the request
|
|
76
|
+
* as a preview (`isPreviewRequest` checks it). Set `null` to disable.
|
|
77
|
+
* Default `'preview'` → `?preview=true`.
|
|
78
|
+
*/
|
|
79
|
+
readonly previewParam?: string | null;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Build an `admin.livePreview.url` callback from declarative slug →
|
|
83
|
+
* path mappings.
|
|
84
|
+
*/
|
|
85
|
+
declare function buildLivePreviewUrl(options: BuildLivePreviewUrlOptions): (args: LivePreviewUrlArgs) => string;
|
|
86
|
+
|
|
87
|
+
export { type BuildLivePreviewUrlOptions, type LivePreviewUrlArgs, type PathResolver, type PathResolverContext, buildLivePreviewUrl };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Backend-side helpers for `payload.config.ts`.
|
|
3
|
+
*
|
|
4
|
+
* Payload's `admin.livePreview.url` callback maps the document being
|
|
5
|
+
* edited to the frontend URL that should be shown in the preview
|
|
6
|
+
* iframe. In practice every project hand-writes the same lookup-table
|
|
7
|
+
* boilerplate (global → path, collection → localized route, fallback
|
|
8
|
+
* when a new draft has no slug yet). `buildLivePreviewUrl` packages
|
|
9
|
+
* that pattern.
|
|
10
|
+
*
|
|
11
|
+
* This module is dependency-free and framework-agnostic — it does not
|
|
12
|
+
* import `payload`, it just produces a function with the documented
|
|
13
|
+
* callback shape.
|
|
14
|
+
*
|
|
15
|
+
* ```ts
|
|
16
|
+
* // payload.config.ts
|
|
17
|
+
* import { buildLivePreviewUrl } from 'payload-live-preview/payload';
|
|
18
|
+
*
|
|
19
|
+
* admin: {
|
|
20
|
+
* livePreview: {
|
|
21
|
+
* url: buildLivePreviewUrl({
|
|
22
|
+
* baseUrl: process.env.FRONTEND_URL ?? 'http://localhost:4321',
|
|
23
|
+
* collections: {
|
|
24
|
+
* services: ({ data, locale }) => `/${locale}/services/${String(data.slug ?? '')}`,
|
|
25
|
+
* posts: ({ data }) => `/blog/${String(data.slug ?? '')}`,
|
|
26
|
+
* },
|
|
27
|
+
* globals: {
|
|
28
|
+
* homepage: '/',
|
|
29
|
+
* contact: ({ locale }) => `/${locale}/contact`,
|
|
30
|
+
* },
|
|
31
|
+
* fallback: '/',
|
|
32
|
+
* }),
|
|
33
|
+
* collections: ['services', 'posts'],
|
|
34
|
+
* globals: ['homepage', 'contact'],
|
|
35
|
+
* },
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @module @payload
|
|
40
|
+
*/
|
|
41
|
+
/** Arguments Payload passes to `admin.livePreview.url`. */
|
|
42
|
+
interface LivePreviewUrlArgs {
|
|
43
|
+
readonly data: Record<string, unknown>;
|
|
44
|
+
readonly locale?: string | {
|
|
45
|
+
readonly code: string;
|
|
46
|
+
};
|
|
47
|
+
readonly collectionConfig?: {
|
|
48
|
+
readonly slug: string;
|
|
49
|
+
};
|
|
50
|
+
readonly globalConfig?: {
|
|
51
|
+
readonly slug: string;
|
|
52
|
+
};
|
|
53
|
+
readonly [extra: string]: unknown;
|
|
54
|
+
}
|
|
55
|
+
/** Context handed to per-slug path resolvers. */
|
|
56
|
+
interface PathResolverContext {
|
|
57
|
+
readonly data: Record<string, unknown>;
|
|
58
|
+
/** Normalised locale code (string), or `undefined`. */
|
|
59
|
+
readonly locale: string | undefined;
|
|
60
|
+
}
|
|
61
|
+
type PathResolver = string | ((context: PathResolverContext) => string);
|
|
62
|
+
interface BuildLivePreviewUrlOptions {
|
|
63
|
+
/** Frontend origin, e.g. `https://site.example` (no trailing slash needed). */
|
|
64
|
+
readonly baseUrl: string;
|
|
65
|
+
/** Per-collection path resolvers keyed by collection slug. */
|
|
66
|
+
readonly collections?: Readonly<Record<string, PathResolver>>;
|
|
67
|
+
/** Per-global path resolvers keyed by global slug. */
|
|
68
|
+
readonly globals?: Readonly<Record<string, PathResolver>>;
|
|
69
|
+
/**
|
|
70
|
+
* Path used when no resolver matches, or when a resolver returns an
|
|
71
|
+
* empty string (e.g. a brand-new draft without a slug). Default `/`.
|
|
72
|
+
*/
|
|
73
|
+
readonly fallback?: string;
|
|
74
|
+
/**
|
|
75
|
+
* Query parameter appended so the frontend can recognise the request
|
|
76
|
+
* as a preview (`isPreviewRequest` checks it). Set `null` to disable.
|
|
77
|
+
* Default `'preview'` → `?preview=true`.
|
|
78
|
+
*/
|
|
79
|
+
readonly previewParam?: string | null;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Build an `admin.livePreview.url` callback from declarative slug →
|
|
83
|
+
* path mappings.
|
|
84
|
+
*/
|
|
85
|
+
declare function buildLivePreviewUrl(options: BuildLivePreviewUrlOptions): (args: LivePreviewUrlArgs) => string;
|
|
86
|
+
|
|
87
|
+
export { type BuildLivePreviewUrlOptions, type LivePreviewUrlArgs, type PathResolver, type PathResolverContext, buildLivePreviewUrl };
|
package/dist/payload.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// src/payload/index.ts
|
|
2
|
+
function buildLivePreviewUrl(options) {
|
|
3
|
+
const base = options.baseUrl.replace(/\/+$/, "");
|
|
4
|
+
const fallback = options.fallback ?? "/";
|
|
5
|
+
const previewParam = options.previewParam === void 0 ? "preview" : options.previewParam;
|
|
6
|
+
return (args) => {
|
|
7
|
+
const locale = normaliseLocale(args.locale);
|
|
8
|
+
const context = { data: args.data, locale };
|
|
9
|
+
const resolver = (args.collectionConfig !== void 0 ? options.collections?.[args.collectionConfig.slug] : void 0) ?? (args.globalConfig !== void 0 ? options.globals?.[args.globalConfig.slug] : void 0);
|
|
10
|
+
let path = fallback;
|
|
11
|
+
if (resolver !== void 0) {
|
|
12
|
+
const resolved = typeof resolver === "function" ? resolver(context) : resolver;
|
|
13
|
+
if (resolved.length > 0) path = resolved;
|
|
14
|
+
}
|
|
15
|
+
if (!path.startsWith("/")) path = `/${path}`;
|
|
16
|
+
if (previewParam === null) return `${base}${path}`;
|
|
17
|
+
const separator = path.includes("?") ? "&" : "?";
|
|
18
|
+
return `${base}${path}${separator}${previewParam}=true`;
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function normaliseLocale(locale) {
|
|
22
|
+
if (locale === void 0) return void 0;
|
|
23
|
+
if (typeof locale === "string") return locale.length > 0 ? locale : void 0;
|
|
24
|
+
return locale.code.length > 0 ? locale.code : void 0;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { buildLivePreviewUrl };
|
|
28
|
+
//# sourceMappingURL=payload.js.map
|
|
29
|
+
//# sourceMappingURL=payload.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/payload/index.ts"],"names":[],"mappings":";AAmFO,SAAS,oBACd,OAAA,EACsC;AACtC,EAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,OAAA,CAAQ,OAAA,CAAQ,QAAQ,EAAE,CAAA;AAC/C,EAAA,MAAM,QAAA,GAAW,QAAQ,QAAA,IAAY,GAAA;AACrC,EAAA,MAAM,YAAA,GAAe,OAAA,CAAQ,YAAA,KAAiB,MAAA,GAAY,YAAY,OAAA,CAAQ,YAAA;AAE9E,EAAA,OAAO,CAAC,IAAA,KAAS;AACf,IAAA,MAAM,MAAA,GAAS,eAAA,CAAgB,IAAA,CAAK,MAAM,CAAA;AAC1C,IAAA,MAAM,OAAA,GAA+B,EAAE,IAAA,EAAM,IAAA,CAAK,MAAM,MAAA,EAAO;AAE/D,IAAA,MAAM,YACH,IAAA,CAAK,gBAAA,KAAqB,SACvB,OAAA,CAAQ,WAAA,GAAc,KAAK,gBAAA,CAAiB,IAAI,IAChD,MAAA,MACH,IAAA,CAAK,iBAAiB,MAAA,GAAY,OAAA,CAAQ,UAAU,IAAA,CAAK,YAAA,CAAa,IAAI,CAAA,GAAI,MAAA,CAAA;AAEjF,IAAA,IAAI,IAAA,GAAO,QAAA;AACX,IAAA,IAAI,aAAa,MAAA,EAAW;AAC1B,MAAA,MAAM,WAAW,OAAO,QAAA,KAAa,UAAA,GAAa,QAAA,CAAS,OAAO,CAAA,GAAI,QAAA;AACtE,MAAA,IAAI,QAAA,CAAS,MAAA,GAAS,CAAA,EAAG,IAAA,GAAO,QAAA;AAAA,IAClC;AACA,IAAA,IAAI,CAAC,IAAA,CAAK,UAAA,CAAW,GAAG,CAAA,EAAG,IAAA,GAAO,IAAI,IAAI,CAAA,CAAA;AAE1C,IAAA,IAAI,iBAAiB,IAAA,EAAM,OAAO,CAAA,EAAG,IAAI,GAAG,IAAI,CAAA,CAAA;AAChD,IAAA,MAAM,SAAA,GAAY,IAAA,CAAK,QAAA,CAAS,GAAG,IAAI,GAAA,GAAM,GAAA;AAC7C,IAAA,OAAO,GAAG,IAAI,CAAA,EAAG,IAAI,CAAA,EAAG,SAAS,GAAG,YAAY,CAAA,KAAA,CAAA;AAAA,EAClD,CAAA;AACF;AAEA,SAAS,gBAAgB,MAAA,EAA0D;AACjF,EAAA,IAAI,MAAA,KAAW,QAAW,OAAO,MAAA;AACjC,EAAA,IAAI,OAAO,MAAA,KAAW,QAAA,SAAiB,MAAA,CAAO,MAAA,GAAS,IAAI,MAAA,GAAS,MAAA;AACpE,EAAA,OAAO,MAAA,CAAO,IAAA,CAAK,MAAA,GAAS,CAAA,GAAI,OAAO,IAAA,GAAO,MAAA;AAChD","file":"payload.js","sourcesContent":["/**\n * Backend-side helpers for `payload.config.ts`.\n *\n * Payload's `admin.livePreview.url` callback maps the document being\n * edited to the frontend URL that should be shown in the preview\n * iframe. In practice every project hand-writes the same lookup-table\n * boilerplate (global → path, collection → localized route, fallback\n * when a new draft has no slug yet). `buildLivePreviewUrl` packages\n * that pattern.\n *\n * This module is dependency-free and framework-agnostic — it does not\n * import `payload`, it just produces a function with the documented\n * callback shape.\n *\n * ```ts\n * // payload.config.ts\n * import { buildLivePreviewUrl } from 'payload-live-preview/payload';\n *\n * admin: {\n * livePreview: {\n * url: buildLivePreviewUrl({\n * baseUrl: process.env.FRONTEND_URL ?? 'http://localhost:4321',\n * collections: {\n * services: ({ data, locale }) => `/${locale}/services/${String(data.slug ?? '')}`,\n * posts: ({ data }) => `/blog/${String(data.slug ?? '')}`,\n * },\n * globals: {\n * homepage: '/',\n * contact: ({ locale }) => `/${locale}/contact`,\n * },\n * fallback: '/',\n * }),\n * collections: ['services', 'posts'],\n * globals: ['homepage', 'contact'],\n * },\n * }\n * ```\n *\n * @module @payload\n */\n\n/** Arguments Payload passes to `admin.livePreview.url`. */\nexport interface LivePreviewUrlArgs {\n readonly data: Record<string, unknown>;\n readonly locale?: string | { readonly code: string };\n readonly collectionConfig?: { readonly slug: string };\n readonly globalConfig?: { readonly slug: string };\n readonly [extra: string]: unknown;\n}\n\n/** Context handed to per-slug path resolvers. */\nexport interface PathResolverContext {\n readonly data: Record<string, unknown>;\n /** Normalised locale code (string), or `undefined`. */\n readonly locale: string | undefined;\n}\n\nexport type PathResolver = string | ((context: PathResolverContext) => string);\n\nexport interface BuildLivePreviewUrlOptions {\n /** Frontend origin, e.g. `https://site.example` (no trailing slash needed). */\n readonly baseUrl: string;\n /** Per-collection path resolvers keyed by collection slug. */\n readonly collections?: Readonly<Record<string, PathResolver>>;\n /** Per-global path resolvers keyed by global slug. */\n readonly globals?: Readonly<Record<string, PathResolver>>;\n /**\n * Path used when no resolver matches, or when a resolver returns an\n * empty string (e.g. a brand-new draft without a slug). Default `/`.\n */\n readonly fallback?: string;\n /**\n * Query parameter appended so the frontend can recognise the request\n * as a preview (`isPreviewRequest` checks it). Set `null` to disable.\n * Default `'preview'` → `?preview=true`.\n */\n readonly previewParam?: string | null;\n}\n\n/**\n * Build an `admin.livePreview.url` callback from declarative slug →\n * path mappings.\n */\nexport function buildLivePreviewUrl(\n options: BuildLivePreviewUrlOptions,\n): (args: LivePreviewUrlArgs) => string {\n const base = options.baseUrl.replace(/\\/+$/, '');\n const fallback = options.fallback ?? '/';\n const previewParam = options.previewParam === undefined ? 'preview' : options.previewParam;\n\n return (args) => {\n const locale = normaliseLocale(args.locale);\n const context: PathResolverContext = { data: args.data, locale };\n\n const resolver =\n (args.collectionConfig !== undefined\n ? options.collections?.[args.collectionConfig.slug]\n : undefined) ??\n (args.globalConfig !== undefined ? options.globals?.[args.globalConfig.slug] : undefined);\n\n let path = fallback;\n if (resolver !== undefined) {\n const resolved = typeof resolver === 'function' ? resolver(context) : resolver;\n if (resolved.length > 0) path = resolved;\n }\n if (!path.startsWith('/')) path = `/${path}`;\n\n if (previewParam === null) return `${base}${path}`;\n const separator = path.includes('?') ? '&' : '?';\n return `${base}${path}${separator}${previewParam}=true`;\n };\n}\n\nfunction normaliseLocale(locale: LivePreviewUrlArgs['locale']): string | undefined {\n if (locale === undefined) return undefined;\n if (typeof locale === 'string') return locale.length > 0 ? locale : undefined;\n return locale.code.length > 0 ? locale.code : undefined;\n}\n"]}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server-side preview-request detection.
|
|
3
|
+
*
|
|
4
|
+
* The live-preview runtime only ever activates inside the Payload
|
|
5
|
+
* admin's iframe, but the *server* still has to decide whether to
|
|
6
|
+
* inject the (sizeable) inline script into a given HTML response.
|
|
7
|
+
* Injecting it into every page for every visitor buffers every
|
|
8
|
+
* response and ships dead bytes to production traffic — so adapters
|
|
9
|
+
* gate injection on this predicate by default, and consumers with
|
|
10
|
+
* hand-rolled middleware can import it directly.
|
|
11
|
+
*
|
|
12
|
+
* A request counts as a preview when any of these hold:
|
|
13
|
+
*
|
|
14
|
+
* 1. A preview query parameter is present (`?preview=true`,
|
|
15
|
+
* `?draft=true`, `?livePreview=true` — configurable).
|
|
16
|
+
* 2. `Sec-Fetch-Dest: iframe` — the document is being loaded as an
|
|
17
|
+
* iframe, which is exactly how the Payload admin embeds it.
|
|
18
|
+
* (Sent by all evergreen browsers; absence never *excludes* a
|
|
19
|
+
* request, presence includes it.)
|
|
20
|
+
* 3. The `Referer` points at one of the given admin origins.
|
|
21
|
+
*
|
|
22
|
+
* @module @adapters/shared/preview-request
|
|
23
|
+
*/
|
|
24
|
+
/** The individual signals the predicate can consider. */
|
|
25
|
+
type PreviewSignal = 'query' | 'fetch-dest' | 'referer';
|
|
26
|
+
interface PreviewRequestOptions {
|
|
27
|
+
/**
|
|
28
|
+
* Which signals count as "this is a preview request". Default: all
|
|
29
|
+
* three. High-security setups that must never relax framing headers
|
|
30
|
+
* for unsolicited iframe loads can restrict to `['query']` — then
|
|
31
|
+
* only an explicit `?preview=true` (e.g. from `buildLivePreviewUrl`)
|
|
32
|
+
* activates preview handling.
|
|
33
|
+
*/
|
|
34
|
+
readonly signals?: readonly PreviewSignal[];
|
|
35
|
+
/**
|
|
36
|
+
* Query parameters (with values `true` or `1`) that mark a request
|
|
37
|
+
* as a preview. Default: `['preview', 'draft', 'livePreview']`.
|
|
38
|
+
*/
|
|
39
|
+
readonly queryParams?: readonly string[];
|
|
40
|
+
/**
|
|
41
|
+
* Treat `Sec-Fetch-Dest: iframe` as a preview signal. Default `true`.
|
|
42
|
+
* Legacy alias for excluding `'fetch-dest'` from `signals`.
|
|
43
|
+
*/
|
|
44
|
+
readonly checkFetchDest?: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Admin origins whose `Referer` marks a request as a preview,
|
|
47
|
+
* e.g. `['https://cms.example.com']`.
|
|
48
|
+
*/
|
|
49
|
+
readonly adminOrigins?: readonly string[];
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The minimal request surface the predicate needs. The standard
|
|
53
|
+
* `Request` satisfies it structurally, and server frameworks without
|
|
54
|
+
* fetch-style requests (e.g. Nitro/H3) can supply a tiny shim.
|
|
55
|
+
*/
|
|
56
|
+
interface PreviewRequestLike {
|
|
57
|
+
readonly url: string;
|
|
58
|
+
readonly headers: {
|
|
59
|
+
get(name: string): string | null;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Decide whether `request` is a live-preview request. See the module
|
|
64
|
+
* docblock for the exact signals.
|
|
65
|
+
*/
|
|
66
|
+
declare function isPreviewRequest(request: PreviewRequestLike, options?: PreviewRequestOptions): boolean;
|
|
67
|
+
|
|
68
|
+
export { type PreviewRequestLike as P, type PreviewRequestOptions as a, isPreviewRequest as i };
|