payload-live-preview 1.8.0 → 2.0.0-beta.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.
Files changed (98) hide show
  1. package/README.md +301 -93
  2. package/dist/adapters/astro/PreviewBoundary.astro +66 -0
  3. package/dist/adapters/astro/RichText.astro +6 -0
  4. package/dist/adapters/astro/index.d.ts +165 -134
  5. package/dist/adapters/astro/index.js +1 -1
  6. package/dist/adapters/astro/index.js.map +1 -1
  7. package/dist/adapters/astro/middleware-entry.d.ts +4 -9
  8. package/dist/adapters/astro/middleware-entry.js +1 -1
  9. package/dist/adapters/astro/middleware-entry.js.map +1 -1
  10. package/dist/adapters/nextjs/index.d.ts +13 -80
  11. package/dist/adapters/nextjs/index.js +1 -1
  12. package/dist/adapters/nextjs/index.js.map +1 -1
  13. package/dist/adapters/nuxt/index.d.ts +46 -106
  14. package/dist/adapters/nuxt/index.js +1 -1
  15. package/dist/adapters/nuxt/index.js.map +1 -1
  16. package/dist/adapters/sveltekit/index.d.ts +10 -75
  17. package/dist/adapters/sveltekit/index.js +1 -1
  18. package/dist/adapters/sveltekit/index.js.map +1 -1
  19. package/dist/client.cjs +2 -0
  20. package/dist/client.cjs.map +1 -0
  21. package/dist/client.d.cts +747 -0
  22. package/dist/client.d.ts +747 -0
  23. package/dist/client.js +2 -0
  24. package/dist/client.js.map +1 -0
  25. package/dist/codegen-astro.d.ts +5 -27
  26. package/dist/codegen-astro.js +1 -1
  27. package/dist/codegen-astro.js.map +1 -1
  28. package/dist/codegen-cli.d.ts +3 -16
  29. package/dist/codegen-cli.js +1 -1
  30. package/dist/codegen-cli.js.map +1 -1
  31. package/dist/codegen.cjs +1 -1
  32. package/dist/codegen.cjs.map +1 -1
  33. package/dist/codegen.d.cts +37 -124
  34. package/dist/codegen.d.ts +37 -124
  35. package/dist/codegen.js +1 -1
  36. package/dist/codegen.js.map +1 -1
  37. package/dist/core.cjs +1 -1
  38. package/dist/core.cjs.map +1 -1
  39. package/dist/core.d.cts +647 -1100
  40. package/dist/core.d.ts +647 -1100
  41. package/dist/core.js +1 -1
  42. package/dist/core.js.map +1 -1
  43. package/dist/doctor-cli.d.ts +3 -28
  44. package/dist/doctor-cli.js +1 -1
  45. package/dist/doctor-cli.js.map +1 -1
  46. package/dist/doctor.d.ts +16 -32
  47. package/dist/doctor.js +1 -1
  48. package/dist/doctor.js.map +1 -1
  49. package/dist/fragment.cjs +2 -0
  50. package/dist/fragment.cjs.map +1 -0
  51. package/dist/fragment.d.cts +333 -0
  52. package/dist/fragment.d.ts +333 -0
  53. package/dist/fragment.js +2 -0
  54. package/dist/fragment.js.map +1 -0
  55. package/dist/index.cjs +1 -1
  56. package/dist/index.cjs.map +1 -1
  57. package/dist/index.d.cts +885 -1531
  58. package/dist/index.d.ts +885 -1531
  59. package/dist/index.js +1 -1
  60. package/dist/index.js.map +1 -1
  61. package/dist/lexical.cjs +2 -0
  62. package/dist/lexical.cjs.map +1 -0
  63. package/dist/lexical.d.cts +90 -0
  64. package/dist/lexical.d.ts +90 -0
  65. package/dist/lexical.js +2 -0
  66. package/dist/lexical.js.map +1 -0
  67. package/dist/middleware-CEPsY1P7.d.ts +40 -0
  68. package/dist/migrate.d.ts +73 -0
  69. package/dist/migrate.js +2 -0
  70. package/dist/migrate.js.map +1 -0
  71. package/dist/options-CEzzfHJu.d.ts +197 -0
  72. package/dist/payload.cjs +1 -1
  73. package/dist/payload.cjs.map +1 -1
  74. package/dist/payload.d.cts +11 -80
  75. package/dist/payload.d.ts +11 -80
  76. package/dist/payload.js +1 -1
  77. package/dist/payload.js.map +1 -1
  78. package/dist/plugins.cjs +2 -0
  79. package/dist/plugins.cjs.map +1 -0
  80. package/dist/plugins.d.cts +509 -0
  81. package/dist/plugins.d.ts +509 -0
  82. package/dist/plugins.js +2 -0
  83. package/dist/plugins.js.map +1 -0
  84. package/dist/{probe-tMHdim5y.d.ts → probe-CYtvj_Lm.d.ts} +43 -77
  85. package/dist/server.cjs +2 -0
  86. package/dist/server.cjs.map +1 -0
  87. package/dist/server.d.cts +435 -0
  88. package/dist/server.d.ts +435 -0
  89. package/dist/server.js +2 -0
  90. package/dist/server.js.map +1 -0
  91. package/dist/structural.cjs +3 -0
  92. package/dist/structural.cjs.map +1 -0
  93. package/dist/structural.d.cts +243 -0
  94. package/dist/structural.d.ts +243 -0
  95. package/dist/structural.js +3 -0
  96. package/dist/structural.js.map +1 -0
  97. package/package.json +86 -7
  98. package/dist/middleware-DD4Hwomt.d.ts +0 -189
@@ -0,0 +1,197 @@
1
+ /**
2
+ * The two defaults profiles, row by row. 2.0 ships `v2`; `defaults: 'v1'` opts
3
+ * back into the 1.x table for a staged migration. Each row belongs to exactly
4
+ * one side — the adapter's request decision or the browser runtime — and a
5
+ * test asserts the profile assigns every readiness row that has an option, so
6
+ * a row added later cannot be forgotten. See ADR 0007.
7
+ */
8
+ /** `'v2'` is the 2.0 default; `'v1'` is the 1.x table. */
9
+ type DefaultsProfile = 'v1' | 'v2';
10
+ /** How the runtime decides which windows may post updates. */
11
+ type EventSourcePolicy = 'any' | 'parent-or-opener';
12
+
13
+ /**
14
+ * The authorized preview context's shape and brand, in the leaf `types` domain
15
+ * so producer and consumers recognise the same context. See ADR 0006.
16
+ */
17
+ /**
18
+ * The brand key, in the type and on the object.
19
+ *
20
+ * This was a `unique symbol` until 2026-08-30. A `unique symbol` is nominal per
21
+ * *declaration*, and the published entries each carry their own declaration
22
+ * file — so `AuthorizedPreviewContext` from the root barrel was a different
23
+ * type from the one in `./server` or in an adapter, and the documented flow
24
+ * (authorize once, hand the context to the adapter hook and to the draft read)
25
+ * did not type-check in any combination a consumer can write.
26
+ *
27
+ * That is the same trap the runtime brand below already avoids by using
28
+ * `Symbol.for` rather than `Symbol()`; the lesson simply had not reached the
29
+ * type. A shared string literal is identical in every bundle for the same
30
+ * reason a registry symbol is.
31
+ *
32
+ * It is not what makes a context trustworthy. `isAuthorizedPreviewContext`
33
+ * decides that, on the registry symbol and on `Object.isFrozen`, and every
34
+ * consumption point calls it — the adapter policy, the draft read and the
35
+ * binding emitter. Writing this key by hand buys nothing.
36
+ */
37
+ declare const AUTHORIZED_PREVIEW_BRAND_KEY = "payload-live-preview.authorized-preview-context";
38
+ type PreviewAuthorizationStrategyName = 'payload-session' | 'signed-token' | 'verifier';
39
+ /** What a context is bound to; every field is optional because every strategy binds differently. */
40
+ interface AuthorizedPreviewScope {
41
+ /** The site origin the credential was issued for. */
42
+ readonly audience?: string;
43
+ /** The request pathname the credential is valid for. */
44
+ readonly path?: string;
45
+ readonly locale?: string;
46
+ }
47
+ /** The verdict of a successful `authorizePreviewRequest()`: frozen, branded, produced only there. Carry it, do not rebuild it. */
48
+ interface AuthorizedPreviewContext {
49
+ readonly [AUTHORIZED_PREVIEW_BRAND_KEY]: true;
50
+ readonly strategy: PreviewAuthorizationStrategyName;
51
+ /** The user id, token subject, or whatever the verifier named. */
52
+ readonly subject: string | undefined;
53
+ /** Unix milliseconds. */
54
+ readonly authorizedAt: number;
55
+ /** Unix milliseconds, or `undefined` when the strategy does not know. */
56
+ readonly expiresAt: number | undefined;
57
+ readonly scope: AuthorizedPreviewScope;
58
+ /** The minimal request material a draft read forwards to Payload — one verified cookie, never the whole `Cookie` header. */
59
+ readonly payloadHeaders: Readonly<Record<string, string>>;
60
+ }
61
+
62
+ /**
63
+ * The verdict shape every authorization strategy produces, and the one error
64
+ * class that is allowed to escape it. Kept apart from the strategies so the
65
+ * adapters can import the error without pulling in HMAC or session code.
66
+ */
67
+
68
+ /** Why a request was refused. `'authorized'` is the one non-refusal. */
69
+ type PreviewAuthorizationOutcome = 'authorized' | 'missing-credential' | 'invalid' | 'expired' | 'wrong-audience' | 'wrong-path' | 'wrong-locale' | 'wrong-purpose' | 'replayed' | 'unavailable';
70
+ /** The result of `authorizePreviewRequest()`: a verdict, never an exception. */
71
+ type PreviewAuthorization = {
72
+ readonly authorized: true;
73
+ readonly outcome: 'authorized';
74
+ readonly context: AuthorizedPreviewContext;
75
+ } | {
76
+ readonly authorized: false;
77
+ readonly outcome: Exclude<PreviewAuthorizationOutcome, 'authorized'>;
78
+ readonly context: null;
79
+ };
80
+ /** The request shape every strategy reads: a URL and a header getter. */
81
+ interface PreviewAuthorizationRequest {
82
+ readonly url: string;
83
+ readonly headers: {
84
+ get(name: string): string | null;
85
+ };
86
+ }
87
+
88
+ /**
89
+ * Server-side preview-intent detection. Every signal here is client-controlled:
90
+ * a positive result is a delivery hint, never proof of identity or permission.
91
+ * Verify with `authorizePreviewRequest()` before anything privileged.
92
+ */
93
+ /** `query`: `?preview=true`; `fetch-dest`: `Sec-Fetch-Dest: iframe`; `referer`: an admin-origin Referer. */
94
+ type PreviewSignal = 'query' | 'fetch-dest' | 'referer';
95
+ interface PreviewRequestOptions {
96
+ /** Which signals count. Default: all three. `['query']` is the adapters' 2.0 default. */
97
+ readonly signals?: readonly PreviewSignal[];
98
+ /** Query parameters (value `true` or `1`) that signal intent. Default `['preview', 'draft', 'livePreview']`. */
99
+ readonly queryParams?: readonly string[];
100
+ /** Admin origins whose Referer signals intent, e.g. `['https://cms.example.com']`. */
101
+ readonly adminOrigins?: readonly string[];
102
+ }
103
+ /** The request surface the predicate reads; `Request` satisfies it, Nitro/H3 events need a shim. */
104
+ interface PreviewRequestLike {
105
+ readonly url: string;
106
+ readonly headers: {
107
+ get(name: string): string | null;
108
+ };
109
+ }
110
+ /** Whether `request` carries live-preview intent. Intent, not authorization. */
111
+ declare function hasPreviewIntent(request: PreviewRequestLike, options?: PreviewRequestOptions): boolean;
112
+
113
+ /**
114
+ * The options every framework adapter shares. Each adapter's public interface
115
+ * extends this with its request type, so the JSDoc lives here once. Defaults
116
+ * are the 2.0 table; `defaults: 'v1'` values are noted where they differ.
117
+ */
118
+
119
+ /**
120
+ * What `authorizePreview` may resolve to; anything that is not a context
121
+ * produced by `authorizePreviewRequest()` is a refusal, a
122
+ * `{ authorized: true }` literal included.
123
+ */
124
+ type PreviewAuthorizationHookResult = PreviewAuthorization | AuthorizedPreviewContext | null | undefined;
125
+ interface PreviewAdapterOptions<Req = Request> {
126
+ /**
127
+ * Payload admin origins allowed to post updates into the page and, with the
128
+ * `referer` signal, to count as intent. Required and `https:` under
129
+ * `strict`. Not HTTP authorization.
130
+ */
131
+ readonly allowedOrigins?: readonly string[];
132
+ /** Payload server origin; updates are then re-fetched through its REST API, which needs an explicit `mergeDepth`. */
133
+ readonly serverURL?: string;
134
+ /** REST API route prefix used with `serverURL`. Default `/api`. */
135
+ readonly apiRoute?: string;
136
+ /** Population depth for `serverURL` re-fetches, required with it (`0` for none); `defaults: 'v1'` falls back to `1`. */
137
+ readonly mergeDepth?: number;
138
+ /** Inject the runtime into preview responses. Default `true`; with `false`, CSP is still managed. */
139
+ readonly autoInject?: boolean;
140
+ /** `'preview-only'` (default) gates on an intent signal; `'always'` treats every request as intent, so the hook runs on each. */
141
+ readonly inject?: 'preview-only' | 'always';
142
+ /** Query parameters (value `true` or `1`) that signal intent. Default `['preview', 'draft', 'livePreview']`. */
143
+ readonly previewQueryParams?: readonly string[];
144
+ /** Client-controlled signals that count as intent. Default `['query']`; `defaults: 'v1'` restores all three, `strict` refuses `'referer'`. */
145
+ readonly previewSignals?: readonly PreviewSignal[];
146
+ /**
147
+ * Route or content filter for script injection only. Not authorization,
148
+ * and it never suppresses CSP handling.
149
+ */
150
+ readonly shouldInject?: (request: Req) => boolean;
151
+ /** `'frame-ancestors'` (default; `true` is an alias) widens that directive, `'full'` also manages a nonce'd `script-src`, `false` never touches CSP. */
152
+ readonly manageCsp?: boolean | 'frame-ancestors' | 'full';
153
+ /**
154
+ * Add `'strict-dynamic'` to the managed `script-src`. Default `false`:
155
+ * CSP 3 then ignores `'self'` and host sources, so every script on the
156
+ * page — framework hydration included — must carry the nonce.
157
+ */
158
+ readonly strictDynamic?: boolean;
159
+ /** Extra `frame-ancestors` sources beyond `'self'` and `allowedOrigins`. */
160
+ readonly frameAncestorsExtra?: readonly string[];
161
+ /** Extra `script-src` sources appended after the nonce (`manageCsp: 'full'`). */
162
+ readonly scriptSrcExtra?: readonly string[];
163
+ /** Verbose runtime logging. Default `false`. */
164
+ readonly debug?: boolean;
165
+ /** Debounce window for incoming updates. Default 50 ms. */
166
+ readonly debounceMs?: number;
167
+ /** Heartbeat timeout in ms. Default `0` (off): the Payload admin sends no keepalive. */
168
+ readonly heartbeatMs?: number;
169
+ /** Skip bindings whose value did not change. Default `true`; `defaults: 'v1'` restores `false`. */
170
+ readonly skipUnchanged?: boolean;
171
+ /** Scroll the preview to the field being edited. Default `false`. */
172
+ readonly revealEditedField?: boolean;
173
+ /** Patch only the bindings of the document an update names (`data-payload-owner`). Default `false`. */
174
+ readonly scopeBindingsByOwner?: boolean;
175
+ /** Sanitizer for rich text and HTML writes. Default `'strict'`; `defaults: 'v1'` restores `'compat'`. */
176
+ readonly sanitizerPolicy?: 'compat' | 'strict';
177
+ /** Which windows may post updates. Default `'parent-or-opener'`; `defaults: 'v1'` restores `'any'`. */
178
+ readonly eventSourcePolicy?: EventSourcePolicy;
179
+ /** Ignore `document.referrer` for origin detection. Default `true`; `defaults: 'v1'` restores `false`. */
180
+ readonly disableReferrerDetection?: boolean;
181
+ /** Turn off the dev-mode `localhost` origin matcher. Default `false`. */
182
+ readonly disableLocalhostMatching?: boolean;
183
+ /**
184
+ * Verify an intent-bearing request before anything privileged is decided:
185
+ * return the result of `authorizePreviewRequest()` (or its context), and
186
+ * anything else refuses injection, CSP changes and nonce exposure
187
+ * regardless of `autoInject` and `shouldInject`. Required under `strict`.
188
+ * See ADR 0006.
189
+ */
190
+ readonly authorizePreview?: (request: Req) => PreviewAuthorizationHookResult | Promise<PreviewAuthorizationHookResult>;
191
+ /** Refuse insecure configuration at startup: `authorizePreview`, https `allowedOrigins`, no referrer trust. Default `true`; `defaults: 'v1'` restores `false`. */
192
+ readonly strict?: boolean;
193
+ /** `'v2'` (default) is the 2.0 table, `'v1'` stages a migration on the 1.x one; explicit options win. See ADR 0007. */
194
+ readonly defaults?: DefaultsProfile;
195
+ }
196
+
197
+ export { type AuthorizedPreviewScope as A, type PreviewAuthorizationRequest as P, type AuthorizedPreviewContext as a, type PreviewAdapterOptions as b, type PreviewRequestLike as c, type PreviewRequestOptions as d, hasPreviewIntent as h };
package/dist/payload.cjs CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";var g=Object.defineProperty,o=(e,l)=>g(e,"name",{value:l,configurable:!0});function b(e){let l=e.baseUrl.replace(/\/+$/,""),o=void 0===e.fallback?"/":e.fallback,i=void 0===e.previewParam?"preview":e.previewParam;return t=>{let n=f(t.locale),r={data:t.data,locale:n},a=v(e,t),c=o;if(void 0!==a){let e="function"==typeof a?a(r):a;if(null===e)return null;e.length>0&&(c=e)}if(null===c)return null;if(c.startsWith("/")||(c=`/${c}`),null===i)return`${l}${c}`;let u=c.includes("?")?"&":"?";return`${l}${c}${u}${i}=true`}}function v(e,l){let o=l.collectionConfig?.slug;if(void 0!==o&&void 0!==e.collections&&Object.hasOwn(e.collections,o))return e.collections[o];let i=l.globalConfig?.slug;return void 0!==i&&void 0!==e.globals&&Object.hasOwn(e.globals,i)?e.globals[i]:void 0}function f(e){if(void 0!==e)return"string"==typeof e?e.length>0?e:void 0:e.code.length>0?e.code:void 0}o(b,"buildLivePreviewUrl"),o(v,"findResolver"),o(f,"normaliseLocale"),exports.buildLivePreviewUrl=b;
1
+ "use strict";exports.buildLivePreviewUrl=function(e){const l=e.baseUrl.replace(/\/+$/,""),n=void 0===e.fallback?"/":e.fallback,t=void 0===e.previewParam?"preview":e.previewParam;return o=>{const i=function(e){return void 0===e?void 0:"string"==typeof e?e.length>0?e:void 0:e.code.length>0?e.code:void 0}(o.locale),c={data:o.data,locale:i},r=function(e,l){const n=l.collectionConfig?.slug;if(void 0!==n&&void 0!==e.collections&&Object.hasOwn(e.collections,n))return e.collections[n];const t=l.globalConfig?.slug;return void 0!==t&&void 0!==e.globals&&Object.hasOwn(e.globals,t)?e.globals[t]:void 0}(e,o);let s=n;if(void 0!==r){const e="function"==typeof r?r(c):r;if(null===e)return null;e.length>0&&(s=e)}return null===s?null:(s.startsWith("/")||(s=`/${s}`),`${l}${null===t?s:function(e,l){const n=e.indexOf("#"),t=-1===n?"":e.slice(n),o=-1===n?e:e.slice(0,n),i=o.indexOf("?"),c=-1===i?"":o.slice(i+1);if("true"===new URLSearchParams(c).get(l))return e;return`${o}${-1===i?"?":0===c.length||c.endsWith("&")?"":"&"}${l}=true${t}`}(s,t)}`)}};
2
2
  //# sourceMappingURL=payload.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"payload.cjs","names":["buildLivePreviewUrl","options","base","baseUrl","replace","fallback","previewParam","args","locale","normaliseLocale","context","data","entry","findResolver","path","resolved","length","startsWith","separator","includes","collectionSlug","collectionConfig","slug","collections","Object","hasOwn","globalSlug","globalConfig","globals","code","__name","exports","b"],"sources":["../src/payload/index.ts"],"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\n/**\n * Path resolver that may decline a document entirely.\n *\n * `null` means \"this document has no preview target\" — Payload then shows no\n * preview iframe. An empty string keeps its 1.x meaning and falls back.\n */\nexport type NullablePathResolver =\n string | null | ((context: PathResolverContext) => string | null);\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 detect preview intent\n * (`isPreviewRequest` checks it). This client-controlled signal does\n * not authenticate or authorize draft access or response changes.\n * Set `null` to disable. Default `'preview'` → `?preview=true`.\n */\n readonly previewParam?: string | null;\n}\n\n/**\n * Options that let a document have no preview target at all.\n *\n * Identical to {@link BuildLivePreviewUrlOptions} except that resolvers and\n * `fallback` accept `null`. Using any of them widens the built callback's\n * return type to `string | null`, which is what Payload's own `url` callback\n * accepts — a document without a reachable route should show no iframe rather\n * than silently point at a public page.\n */\nexport interface BuildLivePreviewUrlNullableOptions {\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, NullablePathResolver>>;\n /** Per-global path resolvers keyed by global slug. */\n readonly globals?: Readonly<Record<string, NullablePathResolver>>;\n /**\n * Path used when no resolver matches, or when a resolver returns an empty\n * string. `null` declines every unmapped document instead of sending it to\n * a default route. Default `/`.\n */\n readonly fallback?: string | null;\n /** See {@link BuildLivePreviewUrlOptions.previewParam}. */\n readonly previewParam?: string | null;\n}\n\n/**\n * Build an `admin.livePreview.url` callback from declarative slug →\n * path mappings.\n *\n * With string-only resolvers the callback always produces a URL, exactly as\n * in 1.0. Let a resolver or `fallback` return `null` and the callback becomes\n * nullable, so a document with no reachable route resolves to no preview\n * instead of to a default public page.\n */\nexport function buildLivePreviewUrl(\n options: BuildLivePreviewUrlOptions,\n): (args: LivePreviewUrlArgs) => string;\nexport function buildLivePreviewUrl(\n options: BuildLivePreviewUrlNullableOptions,\n): (args: LivePreviewUrlArgs) => string | null;\nexport function buildLivePreviewUrl(\n options: BuildLivePreviewUrlNullableOptions,\n): (args: LivePreviewUrlArgs) => string | null {\n const base = options.baseUrl.replace(/\\/+$/, '');\n // `??` would swallow an explicit `null`, which is the whole point of the\n // nullable form: only an absent option falls back to the default path.\n const fallback = options.fallback === undefined ? '/' : 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 // A registered resolver whose value is `null` must not be mistaken for an\n // absent one, so presence is tested rather than coalesced.\n const entry = findResolver(options, args);\n\n let path: string | null = fallback;\n if (entry !== undefined) {\n const resolved = typeof entry === 'function' ? entry(context) : entry;\n // `null` is the resolver declining the document; an empty string keeps\n // its 1.x meaning and falls back.\n if (resolved === null) return null;\n if (resolved.length > 0) path = resolved;\n }\n if (path === null) return null;\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\n/**\n * Registered resolver for this document, or `undefined` when the slug is not\n * mapped at all. A mapped-but-`null` entry returns `null`, which is a resolver\n * value in its own right.\n */\nfunction findResolver(\n options: BuildLivePreviewUrlNullableOptions,\n args: LivePreviewUrlArgs,\n): NullablePathResolver | undefined {\n const collectionSlug = args.collectionConfig?.slug;\n if (\n collectionSlug !== undefined &&\n options.collections !== undefined &&\n Object.hasOwn(options.collections, collectionSlug)\n ) {\n return options.collections[collectionSlug];\n }\n const globalSlug = args.globalConfig?.slug;\n if (\n globalSlug !== undefined &&\n options.globals !== undefined &&\n Object.hasOwn(options.globals, globalSlug)\n ) {\n return options.globals[globalSlug];\n }\n return undefined;\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"],"mappings":"wFAkIO,SAASA,EACdC,GAEA,IAAMC,EAAOD,EAAQE,QAAQC,QAAQ,OAAQ,IAGvCC,OAAgC,IAArBJ,EAAQI,SAAyB,IAAMJ,EAAQI,SAC1DC,OAAwC,IAAzBL,EAAQK,aAA6B,UAAYL,EAAQK,aAE9E,OAAQC,IACN,IAAMC,EAASC,EAAgBF,EAAKC,QAC9BE,EAA+B,CAAEC,KAAMJ,EAAKI,KAAMH,UAIlDI,EAAQC,EAAaZ,EAASM,GAEhCO,EAAsBT,EAC1B,QAAc,IAAVO,EAAqB,CACvB,IAAMG,EAA4B,mBAAVH,EAAuBA,EAAMF,GAAWE,EAGhE,GAAiB,OAAbG,EAAmB,OAAO,KAC1BA,EAASC,OAAS,IAAGF,EAAOC,EAClC,CACA,GAAa,OAATD,EAAe,OAAO,KAG1B,GAFKA,EAAKG,WAAW,OAAMH,EAAO,IAAIA,KAEjB,OAAjBR,EAAuB,MAAO,GAAGJ,IAAOY,IAC5C,IAAMI,EAAYJ,EAAKK,SAAS,KAAO,IAAM,IAC7C,MAAO,GAAGjB,IAAOY,IAAOI,IAAYZ,SAExC,CAOA,SAASO,EACPZ,EACAM,GAEA,IAAMa,EAAiBb,EAAKc,kBAAkBC,KAC9C,QACqB,IAAnBF,QACwB,IAAxBnB,EAAQsB,aACRC,OAAOC,OAAOxB,EAAQsB,YAAaH,GAEnC,OAAOnB,EAAQsB,YAAYH,GAE7B,IAAMM,EAAanB,EAAKoB,cAAcL,KACtC,YACiB,IAAfI,QACoB,IAApBzB,EAAQ2B,SACRJ,OAAOC,OAAOxB,EAAQ2B,QAASF,GAExBzB,EAAQ2B,QAAQF,QALzB,CAQF,CAEA,SAASjB,EAAgBD,GACvB,QAAe,IAAXA,EACJ,MAAsB,iBAAXA,EAA4BA,EAAOQ,OAAS,EAAIR,OAAS,EAC7DA,EAAOqB,KAAKb,OAAS,EAAIR,EAAOqB,UAAO,CAChD,CAlEgBC,EAAA9B,EAAA,uBAuCP8B,EAAAjB,EAAA,gBAuBAiB,EAAArB,EAAA,mBAAAsB,QAAA/B,oBAAAgC","ignoreList":[]}
1
+ {"version":3,"file":"payload.cjs","names":["options","base","baseUrl","replace","fallback","previewParam","args","locale","length","code","normaliseLocale","context","data","entry","collectionSlug","collectionConfig","slug","collections","Object","hasOwn","globalSlug","globalConfig","globals","findResolver","path","resolved","startsWith","name","hashAt","indexOf","hash","slice","beforeHash","queryAt","query","URLSearchParams","get","endsWith","withPreviewParam"],"sources":["../src/payload/index.ts"],"sourcesContent":["/**\n * `payload-live-preview/payload` — helpers for `payload.config.ts`. It never\n * imports `payload`, only produces the callback `admin.livePreview.url` expects.\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, or `undefined`. */\n readonly locale: string | undefined;\n}\n\nexport type PathResolver = string | ((context: PathResolverContext) => string);\n\n/** A resolver that may decline a document: `null` means no preview target; `''` falls back. */\nexport type NullablePathResolver =\n string | null | ((context: PathResolverContext) => string | null);\n\nexport interface BuildLivePreviewUrlOptions {\n /** Frontend origin, e.g. `https://site.example`. */\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 /** Path when no resolver matches or one returns `''` (a draft without a slug). Default `/`. */\n readonly fallback?: string;\n /** Query parameter signalling preview intent; client-controlled, so it authorizes nothing. Default `'preview'`, `null` disables it. */\n readonly previewParam?: string | null;\n}\n\n/**\n * Like {@link BuildLivePreviewUrlOptions} with nullable resolvers: the callback\n * may return `null`, and no iframe beats one pointing at a public page.\n */\nexport interface BuildLivePreviewUrlNullableOptions {\n readonly baseUrl: string;\n readonly collections?: Readonly<Record<string, NullablePathResolver>>;\n readonly globals?: Readonly<Record<string, NullablePathResolver>>;\n /** `null` declines every unmapped document. Default `/`. */\n readonly fallback?: string | null;\n /** See {@link BuildLivePreviewUrlOptions.previewParam}. */\n readonly previewParam?: string | null;\n}\n\n/** Build an `admin.livePreview.url` callback from slug → path mappings. */\nexport function buildLivePreviewUrl(\n options: BuildLivePreviewUrlOptions,\n): (args: LivePreviewUrlArgs) => string;\nexport function buildLivePreviewUrl(\n options: BuildLivePreviewUrlNullableOptions,\n): (args: LivePreviewUrlArgs) => string | null;\nexport function buildLivePreviewUrl(\n options: BuildLivePreviewUrlNullableOptions,\n): (args: LivePreviewUrlArgs) => string | null {\n const base = options.baseUrl.replace(/\\/+$/, '');\n // `??` would swallow an explicit `null`, which is the whole point of the nullable form.\n const fallback = options.fallback === undefined ? '/' : 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 const entry = findResolver(options, args);\n\n let path: string | null = fallback;\n if (entry !== undefined) {\n const resolved = typeof entry === 'function' ? entry(context) : entry;\n if (resolved === null) return null;\n if (resolved.length > 0) path = resolved;\n }\n if (path === null) return null;\n if (!path.startsWith('/')) path = `/${path}`;\n return `${base}${previewParam === null ? path : withPreviewParam(path, previewParam)}`;\n };\n}\n\n/** `path` with `<name>=true` in its query — ahead of any `#fragment`, and not twice. */\nfunction withPreviewParam(path: string, name: string): string {\n const hashAt = path.indexOf('#');\n const hash = hashAt === -1 ? '' : path.slice(hashAt);\n const beforeHash = hashAt === -1 ? path : path.slice(0, hashAt);\n const queryAt = beforeHash.indexOf('?');\n const query = queryAt === -1 ? '' : beforeHash.slice(queryAt + 1);\n if (new URLSearchParams(query).get(name) === 'true') return path;\n const separator = queryAt === -1 ? '?' : query.length === 0 || query.endsWith('&') ? '' : '&';\n return `${beforeHash}${separator}${name}=true${hash}`;\n}\n\n/** The mapped resolver, or `undefined` when the slug is not mapped; a mapped `null` is a resolver in its own right. */\nfunction findResolver(\n options: BuildLivePreviewUrlNullableOptions,\n args: LivePreviewUrlArgs,\n): NullablePathResolver | undefined {\n const collectionSlug = args.collectionConfig?.slug;\n if (\n collectionSlug !== undefined &&\n options.collections !== undefined &&\n Object.hasOwn(options.collections, collectionSlug)\n ) {\n return options.collections[collectionSlug];\n }\n const globalSlug = args.globalConfig?.slug;\n if (\n globalSlug !== undefined &&\n options.globals !== undefined &&\n Object.hasOwn(options.globals, globalSlug)\n ) {\n return options.globals[globalSlug];\n }\n return undefined;\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"],"mappings":"yCA6DO,SACLA,GAEA,MAAMC,EAAOD,EAAQE,QAAQC,QAAQ,OAAQ,IAEvCC,OAAgC,IAArBJ,EAAQI,SAAyB,IAAMJ,EAAQI,SAC1DC,OAAwC,IAAzBL,EAAQK,aAA6B,UAAYL,EAAQK,aAE9E,OAAQC,IACN,MAAMC,EAoDV,SAAyBA,GACvB,YAAe,IAAXA,OAAsB,EACJ,iBAAXA,EAA4BA,EAAOC,OAAS,EAAID,OAAS,EAC7DA,EAAOE,KAAKD,OAAS,EAAID,EAAOE,UAAO,CAChD,CAxDmBC,CAAgBJ,EAAKC,QAC9BI,EAA+B,CAAEC,KAAMN,EAAKM,KAAML,UAClDM,EA2BV,SACEb,EACAM,GAEA,MAAMQ,EAAiBR,EAAKS,kBAAkBC,KAC9C,QACqB,IAAnBF,QACwB,IAAxBd,EAAQiB,aACRC,OAAOC,OAAOnB,EAAQiB,YAAaH,GAEnC,OAAOd,EAAQiB,YAAYH,GAE7B,MAAMM,EAAad,EAAKe,cAAcL,KACtC,YACiB,IAAfI,QACoB,IAApBpB,EAAQsB,SACRJ,OAAOC,OAAOnB,EAAQsB,QAASF,GAExBpB,EAAQsB,QAAQF,QALzB,CAQF,CAhDkBG,CAAavB,EAASM,GAEpC,IAAIkB,EAAsBpB,EAC1B,QAAc,IAAVS,EAAqB,CACvB,MAAMY,EAA4B,mBAAVZ,EAAuBA,EAAMF,GAAWE,EAChE,GAAiB,OAAbY,EAAmB,OAAO,KAC1BA,EAASjB,OAAS,IAAGgB,EAAOC,EAClC,CACA,OAAa,OAATD,EAAsB,MACrBA,EAAKE,WAAW,OAAMF,EAAO,IAAIA,KAC/B,GAAGvB,IAAwB,OAAjBI,EAAwBmB,EAK7C,SAA0BA,EAAcG,GACtC,MAAMC,EAASJ,EAAKK,QAAQ,KACtBC,GAAkB,IAAXF,EAAgB,GAAKJ,EAAKO,MAAMH,GACvCI,GAAwB,IAAXJ,EAAgBJ,EAAOA,EAAKO,MAAM,EAAGH,GAClDK,EAAUD,EAAWH,QAAQ,KAC7BK,GAAoB,IAAZD,EAAiB,GAAKD,EAAWD,MAAME,EAAU,GAC/D,GAA6C,SAAzC,IAAIE,gBAAgBD,GAAOE,IAAIT,GAAkB,OAAOH,EAE5D,MAAO,GAAGQ,KADoB,IAAZC,EAAiB,IAAuB,IAAjBC,EAAM1B,QAAgB0B,EAAMG,SAAS,KAAO,GAAK,MACvDV,SAAYG,GACjD,CAdoDQ,CAAiBd,EAAMnB,MAE3E","ignoreList":[]}
@@ -1,42 +1,6 @@
1
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
2
+ * `payload-live-preview/payload` — helpers for `payload.config.ts`. It never
3
+ * imports `payload`, only produces the callback `admin.livePreview.url` expects.
40
4
  */
41
5
  /** Arguments Payload passes to `admin.livePreview.url`. */
42
6
  interface LivePreviewUrlArgs {
@@ -55,71 +19,38 @@ interface LivePreviewUrlArgs {
55
19
  /** Context handed to per-slug path resolvers. */
56
20
  interface PathResolverContext {
57
21
  readonly data: Record<string, unknown>;
58
- /** Normalised locale code (string), or `undefined`. */
22
+ /** Normalised locale code, or `undefined`. */
59
23
  readonly locale: string | undefined;
60
24
  }
61
25
  type PathResolver = string | ((context: PathResolverContext) => string);
62
- /**
63
- * Path resolver that may decline a document entirely.
64
- *
65
- * `null` means "this document has no preview target" — Payload then shows no
66
- * preview iframe. An empty string keeps its 1.x meaning and falls back.
67
- */
26
+ /** A resolver that may decline a document: `null` means no preview target; `''` falls back. */
68
27
  type NullablePathResolver = string | null | ((context: PathResolverContext) => string | null);
69
28
  interface BuildLivePreviewUrlOptions {
70
- /** Frontend origin, e.g. `https://site.example` (no trailing slash needed). */
29
+ /** Frontend origin, e.g. `https://site.example`. */
71
30
  readonly baseUrl: string;
72
31
  /** Per-collection path resolvers keyed by collection slug. */
73
32
  readonly collections?: Readonly<Record<string, PathResolver>>;
74
33
  /** Per-global path resolvers keyed by global slug. */
75
34
  readonly globals?: Readonly<Record<string, PathResolver>>;
76
- /**
77
- * Path used when no resolver matches, or when a resolver returns an
78
- * empty string (e.g. a brand-new draft without a slug). Default `/`.
79
- */
35
+ /** Path when no resolver matches or one returns `''` (a draft without a slug). Default `/`. */
80
36
  readonly fallback?: string;
81
- /**
82
- * Query parameter appended so the frontend can detect preview intent
83
- * (`isPreviewRequest` checks it). This client-controlled signal does
84
- * not authenticate or authorize draft access or response changes.
85
- * Set `null` to disable. Default `'preview'` → `?preview=true`.
86
- */
37
+ /** Query parameter signalling preview intent; client-controlled, so it authorizes nothing. Default `'preview'`, `null` disables it. */
87
38
  readonly previewParam?: string | null;
88
39
  }
89
40
  /**
90
- * Options that let a document have no preview target at all.
91
- *
92
- * Identical to {@link BuildLivePreviewUrlOptions} except that resolvers and
93
- * `fallback` accept `null`. Using any of them widens the built callback's
94
- * return type to `string | null`, which is what Payload's own `url` callback
95
- * accepts — a document without a reachable route should show no iframe rather
96
- * than silently point at a public page.
41
+ * Like {@link BuildLivePreviewUrlOptions} with nullable resolvers: the callback
42
+ * may return `null`, and no iframe beats one pointing at a public page.
97
43
  */
98
44
  interface BuildLivePreviewUrlNullableOptions {
99
- /** Frontend origin, e.g. `https://site.example` (no trailing slash needed). */
100
45
  readonly baseUrl: string;
101
- /** Per-collection path resolvers keyed by collection slug. */
102
46
  readonly collections?: Readonly<Record<string, NullablePathResolver>>;
103
- /** Per-global path resolvers keyed by global slug. */
104
47
  readonly globals?: Readonly<Record<string, NullablePathResolver>>;
105
- /**
106
- * Path used when no resolver matches, or when a resolver returns an empty
107
- * string. `null` declines every unmapped document instead of sending it to
108
- * a default route. Default `/`.
109
- */
48
+ /** `null` declines every unmapped document. Default `/`. */
110
49
  readonly fallback?: string | null;
111
50
  /** See {@link BuildLivePreviewUrlOptions.previewParam}. */
112
51
  readonly previewParam?: string | null;
113
52
  }
114
- /**
115
- * Build an `admin.livePreview.url` callback from declarative slug →
116
- * path mappings.
117
- *
118
- * With string-only resolvers the callback always produces a URL, exactly as
119
- * in 1.0. Let a resolver or `fallback` return `null` and the callback becomes
120
- * nullable, so a document with no reachable route resolves to no preview
121
- * instead of to a default public page.
122
- */
53
+ /** Build an `admin.livePreview.url` callback from slug → path mappings. */
123
54
  declare function buildLivePreviewUrl(options: BuildLivePreviewUrlOptions): (args: LivePreviewUrlArgs) => string;
124
55
  declare function buildLivePreviewUrl(options: BuildLivePreviewUrlNullableOptions): (args: LivePreviewUrlArgs) => string | null;
125
56
 
package/dist/payload.d.ts CHANGED
@@ -1,42 +1,6 @@
1
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
2
+ * `payload-live-preview/payload` — helpers for `payload.config.ts`. It never
3
+ * imports `payload`, only produces the callback `admin.livePreview.url` expects.
40
4
  */
41
5
  /** Arguments Payload passes to `admin.livePreview.url`. */
42
6
  interface LivePreviewUrlArgs {
@@ -55,71 +19,38 @@ interface LivePreviewUrlArgs {
55
19
  /** Context handed to per-slug path resolvers. */
56
20
  interface PathResolverContext {
57
21
  readonly data: Record<string, unknown>;
58
- /** Normalised locale code (string), or `undefined`. */
22
+ /** Normalised locale code, or `undefined`. */
59
23
  readonly locale: string | undefined;
60
24
  }
61
25
  type PathResolver = string | ((context: PathResolverContext) => string);
62
- /**
63
- * Path resolver that may decline a document entirely.
64
- *
65
- * `null` means "this document has no preview target" — Payload then shows no
66
- * preview iframe. An empty string keeps its 1.x meaning and falls back.
67
- */
26
+ /** A resolver that may decline a document: `null` means no preview target; `''` falls back. */
68
27
  type NullablePathResolver = string | null | ((context: PathResolverContext) => string | null);
69
28
  interface BuildLivePreviewUrlOptions {
70
- /** Frontend origin, e.g. `https://site.example` (no trailing slash needed). */
29
+ /** Frontend origin, e.g. `https://site.example`. */
71
30
  readonly baseUrl: string;
72
31
  /** Per-collection path resolvers keyed by collection slug. */
73
32
  readonly collections?: Readonly<Record<string, PathResolver>>;
74
33
  /** Per-global path resolvers keyed by global slug. */
75
34
  readonly globals?: Readonly<Record<string, PathResolver>>;
76
- /**
77
- * Path used when no resolver matches, or when a resolver returns an
78
- * empty string (e.g. a brand-new draft without a slug). Default `/`.
79
- */
35
+ /** Path when no resolver matches or one returns `''` (a draft without a slug). Default `/`. */
80
36
  readonly fallback?: string;
81
- /**
82
- * Query parameter appended so the frontend can detect preview intent
83
- * (`isPreviewRequest` checks it). This client-controlled signal does
84
- * not authenticate or authorize draft access or response changes.
85
- * Set `null` to disable. Default `'preview'` → `?preview=true`.
86
- */
37
+ /** Query parameter signalling preview intent; client-controlled, so it authorizes nothing. Default `'preview'`, `null` disables it. */
87
38
  readonly previewParam?: string | null;
88
39
  }
89
40
  /**
90
- * Options that let a document have no preview target at all.
91
- *
92
- * Identical to {@link BuildLivePreviewUrlOptions} except that resolvers and
93
- * `fallback` accept `null`. Using any of them widens the built callback's
94
- * return type to `string | null`, which is what Payload's own `url` callback
95
- * accepts — a document without a reachable route should show no iframe rather
96
- * than silently point at a public page.
41
+ * Like {@link BuildLivePreviewUrlOptions} with nullable resolvers: the callback
42
+ * may return `null`, and no iframe beats one pointing at a public page.
97
43
  */
98
44
  interface BuildLivePreviewUrlNullableOptions {
99
- /** Frontend origin, e.g. `https://site.example` (no trailing slash needed). */
100
45
  readonly baseUrl: string;
101
- /** Per-collection path resolvers keyed by collection slug. */
102
46
  readonly collections?: Readonly<Record<string, NullablePathResolver>>;
103
- /** Per-global path resolvers keyed by global slug. */
104
47
  readonly globals?: Readonly<Record<string, NullablePathResolver>>;
105
- /**
106
- * Path used when no resolver matches, or when a resolver returns an empty
107
- * string. `null` declines every unmapped document instead of sending it to
108
- * a default route. Default `/`.
109
- */
48
+ /** `null` declines every unmapped document. Default `/`. */
110
49
  readonly fallback?: string | null;
111
50
  /** See {@link BuildLivePreviewUrlOptions.previewParam}. */
112
51
  readonly previewParam?: string | null;
113
52
  }
114
- /**
115
- * Build an `admin.livePreview.url` callback from declarative slug →
116
- * path mappings.
117
- *
118
- * With string-only resolvers the callback always produces a URL, exactly as
119
- * in 1.0. Let a resolver or `fallback` return `null` and the callback becomes
120
- * nullable, so a document with no reachable route resolves to no preview
121
- * instead of to a default public page.
122
- */
53
+ /** Build an `admin.livePreview.url` callback from slug → path mappings. */
123
54
  declare function buildLivePreviewUrl(options: BuildLivePreviewUrlOptions): (args: LivePreviewUrlArgs) => string;
124
55
  declare function buildLivePreviewUrl(options: BuildLivePreviewUrlNullableOptions): (args: LivePreviewUrlArgs) => string | null;
125
56
 
package/dist/payload.js CHANGED
@@ -1,2 +1,2 @@
1
- var e=Object.defineProperty,l=(l,i)=>e(l,"name",{value:i,configurable:!0});function i(e){let l=e.baseUrl.replace(/\/+$/,""),i=void 0===e.fallback?"/":e.fallback,t=void 0===e.previewParam?"preview":e.previewParam;return r=>{let a=n(r.locale),c={data:r.data,locale:a},u=o(e,r),v=i;if(void 0!==u){let e="function"==typeof u?u(c):u;if(null===e)return null;e.length>0&&(v=e)}if(null===v)return null;if(v.startsWith("/")||(v=`/${v}`),null===t)return`${l}${v}`;let d=v.includes("?")?"&":"?";return`${l}${v}${d}${t}=true`}}function o(e,l){let i=l.collectionConfig?.slug;if(void 0!==i&&void 0!==e.collections&&Object.hasOwn(e.collections,i))return e.collections[i];let o=l.globalConfig?.slug;return void 0!==o&&void 0!==e.globals&&Object.hasOwn(e.globals,o)?e.globals[o]:void 0}function n(e){if(void 0!==e)return"string"==typeof e?e.length>0?e:void 0:e.code.length>0?e.code:void 0}l(i,"buildLivePreviewUrl"),l(o,"findResolver"),l(n,"normaliseLocale");export{i as buildLivePreviewUrl};
1
+ function e(e){const l=e.baseUrl.replace(/\/+$/,""),n=void 0===e.fallback?"/":e.fallback,o=void 0===e.previewParam?"preview":e.previewParam;return t=>{const i=function(e){return void 0===e?void 0:"string"==typeof e?e.length>0?e:void 0:e.code.length>0?e.code:void 0}(t.locale),c={data:t.data,locale:i},r=function(e,l){const n=l.collectionConfig?.slug;if(void 0!==n&&void 0!==e.collections&&Object.hasOwn(e.collections,n))return e.collections[n];const o=l.globalConfig?.slug;return void 0!==o&&void 0!==e.globals&&Object.hasOwn(e.globals,o)?e.globals[o]:void 0}(e,t);let a=n;if(void 0!==r){const e="function"==typeof r?r(c):r;if(null===e)return null;e.length>0&&(a=e)}return null===a?null:(a.startsWith("/")||(a=`/${a}`),`${l}${null===o?a:function(e,l){const n=e.indexOf("#"),o=-1===n?"":e.slice(n),t=-1===n?e:e.slice(0,n),i=t.indexOf("?"),c=-1===i?"":t.slice(i+1);if("true"===new URLSearchParams(c).get(l))return e;return`${t}${-1===i?"?":0===c.length||c.endsWith("&")?"":"&"}${l}=true${o}`}(a,o)}`)}}export{e as buildLivePreviewUrl};
2
2
  //# sourceMappingURL=payload.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"payload.js","names":["buildLivePreviewUrl","options","base","baseUrl","replace","fallback","previewParam","args","locale","normaliseLocale","context","data","entry","findResolver","path","resolved","length","startsWith","separator","includes","collectionSlug","collectionConfig","slug","collections","Object","hasOwn","globalSlug","globalConfig","globals","code","__name","b"],"sources":["../src/payload/index.ts"],"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\n/**\n * Path resolver that may decline a document entirely.\n *\n * `null` means \"this document has no preview target\" — Payload then shows no\n * preview iframe. An empty string keeps its 1.x meaning and falls back.\n */\nexport type NullablePathResolver =\n string | null | ((context: PathResolverContext) => string | null);\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 detect preview intent\n * (`isPreviewRequest` checks it). This client-controlled signal does\n * not authenticate or authorize draft access or response changes.\n * Set `null` to disable. Default `'preview'` → `?preview=true`.\n */\n readonly previewParam?: string | null;\n}\n\n/**\n * Options that let a document have no preview target at all.\n *\n * Identical to {@link BuildLivePreviewUrlOptions} except that resolvers and\n * `fallback` accept `null`. Using any of them widens the built callback's\n * return type to `string | null`, which is what Payload's own `url` callback\n * accepts — a document without a reachable route should show no iframe rather\n * than silently point at a public page.\n */\nexport interface BuildLivePreviewUrlNullableOptions {\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, NullablePathResolver>>;\n /** Per-global path resolvers keyed by global slug. */\n readonly globals?: Readonly<Record<string, NullablePathResolver>>;\n /**\n * Path used when no resolver matches, or when a resolver returns an empty\n * string. `null` declines every unmapped document instead of sending it to\n * a default route. Default `/`.\n */\n readonly fallback?: string | null;\n /** See {@link BuildLivePreviewUrlOptions.previewParam}. */\n readonly previewParam?: string | null;\n}\n\n/**\n * Build an `admin.livePreview.url` callback from declarative slug →\n * path mappings.\n *\n * With string-only resolvers the callback always produces a URL, exactly as\n * in 1.0. Let a resolver or `fallback` return `null` and the callback becomes\n * nullable, so a document with no reachable route resolves to no preview\n * instead of to a default public page.\n */\nexport function buildLivePreviewUrl(\n options: BuildLivePreviewUrlOptions,\n): (args: LivePreviewUrlArgs) => string;\nexport function buildLivePreviewUrl(\n options: BuildLivePreviewUrlNullableOptions,\n): (args: LivePreviewUrlArgs) => string | null;\nexport function buildLivePreviewUrl(\n options: BuildLivePreviewUrlNullableOptions,\n): (args: LivePreviewUrlArgs) => string | null {\n const base = options.baseUrl.replace(/\\/+$/, '');\n // `??` would swallow an explicit `null`, which is the whole point of the\n // nullable form: only an absent option falls back to the default path.\n const fallback = options.fallback === undefined ? '/' : 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 // A registered resolver whose value is `null` must not be mistaken for an\n // absent one, so presence is tested rather than coalesced.\n const entry = findResolver(options, args);\n\n let path: string | null = fallback;\n if (entry !== undefined) {\n const resolved = typeof entry === 'function' ? entry(context) : entry;\n // `null` is the resolver declining the document; an empty string keeps\n // its 1.x meaning and falls back.\n if (resolved === null) return null;\n if (resolved.length > 0) path = resolved;\n }\n if (path === null) return null;\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\n/**\n * Registered resolver for this document, or `undefined` when the slug is not\n * mapped at all. A mapped-but-`null` entry returns `null`, which is a resolver\n * value in its own right.\n */\nfunction findResolver(\n options: BuildLivePreviewUrlNullableOptions,\n args: LivePreviewUrlArgs,\n): NullablePathResolver | undefined {\n const collectionSlug = args.collectionConfig?.slug;\n if (\n collectionSlug !== undefined &&\n options.collections !== undefined &&\n Object.hasOwn(options.collections, collectionSlug)\n ) {\n return options.collections[collectionSlug];\n }\n const globalSlug = args.globalConfig?.slug;\n if (\n globalSlug !== undefined &&\n options.globals !== undefined &&\n Object.hasOwn(options.globals, globalSlug)\n ) {\n return options.globals[globalSlug];\n }\n return undefined;\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"],"mappings":"2EAkIO,SAASA,EACdC,GAEA,IAAMC,EAAOD,EAAQE,QAAQC,QAAQ,OAAQ,IAGvCC,OAAgC,IAArBJ,EAAQI,SAAyB,IAAMJ,EAAQI,SAC1DC,OAAwC,IAAzBL,EAAQK,aAA6B,UAAYL,EAAQK,aAE9E,OAAQC,IACN,IAAMC,EAASC,EAAgBF,EAAKC,QAC9BE,EAA+B,CAAEC,KAAMJ,EAAKI,KAAMH,UAIlDI,EAAQC,EAAaZ,EAASM,GAEhCO,EAAsBT,EAC1B,QAAc,IAAVO,EAAqB,CACvB,IAAMG,EAA4B,mBAAVH,EAAuBA,EAAMF,GAAWE,EAGhE,GAAiB,OAAbG,EAAmB,OAAO,KAC1BA,EAASC,OAAS,IAAGF,EAAOC,EAClC,CACA,GAAa,OAATD,EAAe,OAAO,KAG1B,GAFKA,EAAKG,WAAW,OAAMH,EAAO,IAAIA,KAEjB,OAAjBR,EAAuB,MAAO,GAAGJ,IAAOY,IAC5C,IAAMI,EAAYJ,EAAKK,SAAS,KAAO,IAAM,IAC7C,MAAO,GAAGjB,IAAOY,IAAOI,IAAYZ,SAExC,CAOA,SAASO,EACPZ,EACAM,GAEA,IAAMa,EAAiBb,EAAKc,kBAAkBC,KAC9C,QACqB,IAAnBF,QACwB,IAAxBnB,EAAQsB,aACRC,OAAOC,OAAOxB,EAAQsB,YAAaH,GAEnC,OAAOnB,EAAQsB,YAAYH,GAE7B,IAAMM,EAAanB,EAAKoB,cAAcL,KACtC,YACiB,IAAfI,QACoB,IAApBzB,EAAQ2B,SACRJ,OAAOC,OAAOxB,EAAQ2B,QAASF,GAExBzB,EAAQ2B,QAAQF,QALzB,CAQF,CAEA,SAASjB,EAAgBD,GACvB,QAAe,IAAXA,EACJ,MAAsB,iBAAXA,EAA4BA,EAAOQ,OAAS,EAAIR,OAAS,EAC7DA,EAAOqB,KAAKb,OAAS,EAAIR,EAAOqB,UAAO,CAChD,CAlEgBC,EAAA9B,EAAA,uBAuCP8B,EAAAjB,EAAA,gBAuBAiB,EAAArB,EAAA,0BAAAsB","ignoreList":[]}
1
+ {"version":3,"file":"payload.js","names":["buildLivePreviewUrl","options","base","baseUrl","replace","fallback","previewParam","args","locale","length","code","normaliseLocale","context","data","entry","collectionSlug","collectionConfig","slug","collections","Object","hasOwn","globalSlug","globalConfig","globals","findResolver","path","resolved","startsWith","name","hashAt","indexOf","hash","slice","beforeHash","queryAt","query","URLSearchParams","get","endsWith","withPreviewParam"],"sources":["../src/payload/index.ts"],"sourcesContent":["/**\n * `payload-live-preview/payload` — helpers for `payload.config.ts`. It never\n * imports `payload`, only produces the callback `admin.livePreview.url` expects.\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, or `undefined`. */\n readonly locale: string | undefined;\n}\n\nexport type PathResolver = string | ((context: PathResolverContext) => string);\n\n/** A resolver that may decline a document: `null` means no preview target; `''` falls back. */\nexport type NullablePathResolver =\n string | null | ((context: PathResolverContext) => string | null);\n\nexport interface BuildLivePreviewUrlOptions {\n /** Frontend origin, e.g. `https://site.example`. */\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 /** Path when no resolver matches or one returns `''` (a draft without a slug). Default `/`. */\n readonly fallback?: string;\n /** Query parameter signalling preview intent; client-controlled, so it authorizes nothing. Default `'preview'`, `null` disables it. */\n readonly previewParam?: string | null;\n}\n\n/**\n * Like {@link BuildLivePreviewUrlOptions} with nullable resolvers: the callback\n * may return `null`, and no iframe beats one pointing at a public page.\n */\nexport interface BuildLivePreviewUrlNullableOptions {\n readonly baseUrl: string;\n readonly collections?: Readonly<Record<string, NullablePathResolver>>;\n readonly globals?: Readonly<Record<string, NullablePathResolver>>;\n /** `null` declines every unmapped document. Default `/`. */\n readonly fallback?: string | null;\n /** See {@link BuildLivePreviewUrlOptions.previewParam}. */\n readonly previewParam?: string | null;\n}\n\n/** Build an `admin.livePreview.url` callback from slug → path mappings. */\nexport function buildLivePreviewUrl(\n options: BuildLivePreviewUrlOptions,\n): (args: LivePreviewUrlArgs) => string;\nexport function buildLivePreviewUrl(\n options: BuildLivePreviewUrlNullableOptions,\n): (args: LivePreviewUrlArgs) => string | null;\nexport function buildLivePreviewUrl(\n options: BuildLivePreviewUrlNullableOptions,\n): (args: LivePreviewUrlArgs) => string | null {\n const base = options.baseUrl.replace(/\\/+$/, '');\n // `??` would swallow an explicit `null`, which is the whole point of the nullable form.\n const fallback = options.fallback === undefined ? '/' : 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 const entry = findResolver(options, args);\n\n let path: string | null = fallback;\n if (entry !== undefined) {\n const resolved = typeof entry === 'function' ? entry(context) : entry;\n if (resolved === null) return null;\n if (resolved.length > 0) path = resolved;\n }\n if (path === null) return null;\n if (!path.startsWith('/')) path = `/${path}`;\n return `${base}${previewParam === null ? path : withPreviewParam(path, previewParam)}`;\n };\n}\n\n/** `path` with `<name>=true` in its query — ahead of any `#fragment`, and not twice. */\nfunction withPreviewParam(path: string, name: string): string {\n const hashAt = path.indexOf('#');\n const hash = hashAt === -1 ? '' : path.slice(hashAt);\n const beforeHash = hashAt === -1 ? path : path.slice(0, hashAt);\n const queryAt = beforeHash.indexOf('?');\n const query = queryAt === -1 ? '' : beforeHash.slice(queryAt + 1);\n if (new URLSearchParams(query).get(name) === 'true') return path;\n const separator = queryAt === -1 ? '?' : query.length === 0 || query.endsWith('&') ? '' : '&';\n return `${beforeHash}${separator}${name}=true${hash}`;\n}\n\n/** The mapped resolver, or `undefined` when the slug is not mapped; a mapped `null` is a resolver in its own right. */\nfunction findResolver(\n options: BuildLivePreviewUrlNullableOptions,\n args: LivePreviewUrlArgs,\n): NullablePathResolver | undefined {\n const collectionSlug = args.collectionConfig?.slug;\n if (\n collectionSlug !== undefined &&\n options.collections !== undefined &&\n Object.hasOwn(options.collections, collectionSlug)\n ) {\n return options.collections[collectionSlug];\n }\n const globalSlug = args.globalConfig?.slug;\n if (\n globalSlug !== undefined &&\n options.globals !== undefined &&\n Object.hasOwn(options.globals, globalSlug)\n ) {\n return options.globals[globalSlug];\n }\n return undefined;\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"],"mappings":"AA6DO,SAASA,EACdC,GAEA,MAAMC,EAAOD,EAAQE,QAAQC,QAAQ,OAAQ,IAEvCC,OAAgC,IAArBJ,EAAQI,SAAyB,IAAMJ,EAAQI,SAC1DC,OAAwC,IAAzBL,EAAQK,aAA6B,UAAYL,EAAQK,aAE9E,OAAQC,IACN,MAAMC,EAoDV,SAAyBA,GACvB,YAAe,IAAXA,OAAsB,EACJ,iBAAXA,EAA4BA,EAAOC,OAAS,EAAID,OAAS,EAC7DA,EAAOE,KAAKD,OAAS,EAAID,EAAOE,UAAO,CAChD,CAxDmBC,CAAgBJ,EAAKC,QAC9BI,EAA+B,CAAEC,KAAMN,EAAKM,KAAML,UAClDM,EA2BV,SACEb,EACAM,GAEA,MAAMQ,EAAiBR,EAAKS,kBAAkBC,KAC9C,QACqB,IAAnBF,QACwB,IAAxBd,EAAQiB,aACRC,OAAOC,OAAOnB,EAAQiB,YAAaH,GAEnC,OAAOd,EAAQiB,YAAYH,GAE7B,MAAMM,EAAad,EAAKe,cAAcL,KACtC,YACiB,IAAfI,QACoB,IAApBpB,EAAQsB,SACRJ,OAAOC,OAAOnB,EAAQsB,QAASF,GAExBpB,EAAQsB,QAAQF,QALzB,CAQF,CAhDkBG,CAAavB,EAASM,GAEpC,IAAIkB,EAAsBpB,EAC1B,QAAc,IAAVS,EAAqB,CACvB,MAAMY,EAA4B,mBAAVZ,EAAuBA,EAAMF,GAAWE,EAChE,GAAiB,OAAbY,EAAmB,OAAO,KAC1BA,EAASjB,OAAS,IAAGgB,EAAOC,EAClC,CACA,OAAa,OAATD,EAAsB,MACrBA,EAAKE,WAAW,OAAMF,EAAO,IAAIA,KAC/B,GAAGvB,IAAwB,OAAjBI,EAAwBmB,EAK7C,SAA0BA,EAAcG,GACtC,MAAMC,EAASJ,EAAKK,QAAQ,KACtBC,GAAkB,IAAXF,EAAgB,GAAKJ,EAAKO,MAAMH,GACvCI,GAAwB,IAAXJ,EAAgBJ,EAAOA,EAAKO,MAAM,EAAGH,GAClDK,EAAUD,EAAWH,QAAQ,KAC7BK,GAAoB,IAAZD,EAAiB,GAAKD,EAAWD,MAAME,EAAU,GAC/D,GAA6C,SAAzC,IAAIE,gBAAgBD,GAAOE,IAAIT,GAAkB,OAAOH,EAE5D,MAAO,GAAGQ,KADoB,IAAZC,EAAiB,IAAuB,IAAjBC,EAAM1B,QAAgB0B,EAAMG,SAAS,KAAO,GAAK,MACvDV,SAAYG,GACjD,CAdoDQ,CAAiBd,EAAMnB,MAE3E,Q","ignoreList":[]}
@@ -0,0 +1,2 @@
1
+ "use strict";function e(e){return("object"==typeof e&&null!==e||"function"==typeof e)&&"function"==typeof e.then&&(Promise.resolve(e).catch(()=>{}),!0)}function t(t){return(...n)=>{try{e(t(...n))}catch{}}}var n=t((...e)=>console.error(...e)),i=()=>{},s=class{#e;#t;#n=/* @__PURE__ */new Set;#i="staging";constructor(e,t){this.#e=e,this.#t=t}assertOpen(){if("closed"===this.#i)throw new Error(`Plugin context for "${this.#e}" is no longer active`)}get active(){return"active"===this.#i}eligible(){return"closed"!==this.#i}own(e){return this.assertOpen(),this.#s({kind:"cleanup",acquire:void 0,finalize:void 0,cleanup:e,disposed:!1})}stage(e,t,n){this.assertOpen();const i={kind:e,acquire:t,finalize:n,cleanup:void 0,disposed:!1},s=this.#s(i);if("active"===this.#i)try{this.#r(i)}catch(e){throw this.#o(i),e}return s}counts(){const e={transform:0,renderer:0,subscription:0,cleanup:0};for(const t of this.#n)e[t.kind]+=1;return e}commit(){if("staging"!==this.#i)throw new Error(`Plugin context for "${this.#e}" cannot be committed`);this.#i="committing";try{for(const e of this.#n)this.#r(e);this.#i="active"}catch(e){throw this.close(),e}}close(){if("closed"!==this.#i){this.#i="closed";for(const e of[...this.#n].reverse())this.#o(e);this.#n.clear()}}#s(e){return this.#n.add(e),()=>{this.#o(e)}}#r(e){if(e.disposed||void 0!==e.cleanup||void 0===e.acquire)return;const t=e.acquire();e.cleanup=t??i}#o(t){if(t.disposed)return;t.disposed=!0,this.#n.delete(t);const n=t.cleanup;if(t.cleanup=void 0,void 0!==n)try{e(n())&&this.#t(`plugin "${this.#e}" cleanup returned a Promise/thenable; cleanups must be synchronous`)}catch(e){this.#t(`plugin "${this.#e}" cleanup failed:`,e)}try{t.finalize?.()}catch(e){this.#t(`plugin "${this.#e}" cleanup failed:`,e)}}},r=class{#a;#l;#c=/* @__PURE__ */new Set;constructor(e,t){this.#a=e,this.#l=t}on(e,t){return this.#u("on",e,t)}once(e,t){return this.#u("once",e,t)}off(e,t){const n=t;for(const t of[...this.#c])t.event===e&&t.handler===n&&t.dispose()}emit(e,t){return this.#l.assertOpen(),this.#a.emit(e,t)}emitWhile(e,t,n){return this.#l.assertOpen(),this.#a.emitWhile(e,t,()=>{if(!this.#l.eligible())return!1;const e=n();return!!this.#l.eligible()&&e})}listenerCount(e){let t=0;for(const n of this.#c)n.event===e&&(t+=1);return t}removeAllListeners(e){for(const t of[...this.#c])void 0!==e&&t.event!==e||t.dispose()}eventNames(){const e=/* @__PURE__ */new Set;for(const t of this.#c)e.add(t.event);return[...e]}#u(e,t,n){this.#l.assertOpen();const s=n,r=[...this.#c].find(n=>n.kind===e&&n.event===t&&n.handler===s);if(void 0!==r)return r.dispose;let o=i,a=!0;const l=async t=>{this.#l.active&&a&&("once"===e&&o(),await n(t))},c={kind:e,event:t,handler:s,dispose:i};return o=this.#l.stage("subscription",()=>this.#a[e](t,l),()=>{a=!1,this.#c.delete(c)}),c.dispose=o,this.#c.add(c),o}},o="payload-live-preview-highlight",a=/* @__PURE__ */new WeakMap,l=/* @__PURE__ */new WeakMap,c={name:"highlight",version:"1.0.0",init:e=>{if("undefined"==typeof document||"undefined"==typeof window)return;const t="function"==typeof window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches,n=function(e,t){let n=a.get(e);void 0===n||n.connected()||(a.delete(e),n=void 0),void 0===n&&(n=function(e,t){const n=e.getElementById(o);if(n instanceof HTMLStyleElement)return{pluginOwned:!1,connected:()=>n.isConnected,remove:()=>{},owners:0};const i=function(e,t){try{if(!Array.isArray(e.adoptedStyleSheets)||"function"!=typeof CSSStyleSheet)return;const n=new CSSStyleSheet;if("function"!=typeof n.replaceSync)return;return n.replaceSync(t),e.adoptedStyleSheets=[...e.adoptedStyleSheets,n],n}catch{return}}(e,t);if(void 0!==i)return{pluginOwned:!0,connected:()=>e.adoptedStyleSheets.includes(i),remove:()=>{e.adoptedStyleSheets=e.adoptedStyleSheets.filter(e=>e!==i)},owners:0};const s=e.createElement("style");return s.id=o,s.textContent=t,e.head.appendChild(s),{pluginOwned:!0,connected:()=>s.isConnected,remove:()=>{s.remove()},owners:0}}(e,t?".lp-highlight{outline:2px solid rgba(0,102,204,0.85);outline-offset:2px;}":"@keyframes lp-highlight{0%{outline:2px solid rgba(0,102,204,0.85);outline-offset:2px}100%{outline:2px solid transparent;outline-offset:2px}}.lp-highlight{animation:lp-highlight 0.6s ease-out;}"),a.set(e,n));const i=n;i.owners+=1;let s=!0;return()=>{s&&(s=!1,i.owners-=1,i.owners>0||(a.get(e)===i&&a.delete(e),i.pluginOwned&&i.remove()))}}(document,t),i=t?1e3:600,s=/* @__PURE__ */new WeakMap,r=/* @__PURE__ */new Set;e.registerCleanup?.(()=>{for(const e of[...r])e();n()}),e.events.on("elementUpdate",e=>{const t=e.element;s.get(t)?.();const n=function(e){let t=l.get(e);void 0===t&&(t={owners:0,pluginAdded:!e.classList.contains("lp-highlight")},l.set(e,t));const n=t;n.owners+=1,e.classList.add("lp-highlight");let i=!0;return()=>{i&&(i=!1,n.owners-=1,n.owners>0||(l.delete(e),n.pluginAdded&&e.classList.remove("lp-highlight")))}}(t);let o=!1;const a=()=>{o||(o=!0,window.clearTimeout(c),s.get(t)===a&&s.delete(t),r.delete(a),n())},c=window.setTimeout(a,i);r.add(a),s.set(t,a)})}},u={name:"debug",version:"1.0.0",init:e=>{e.events.on("init",()=>{e.log("init")}),e.events.on("connect",t=>{e.log("connect ←",t.origin)}),e.events.on("disconnect",t=>{e.log("disconnect (",t.reason,")")}),e.events.on("cacheRefresh",t=>{e.log("cacheRefresh",t.elementCount,"elements,",t.fieldCount,"fields")}),e.events.on("beforeUpdate",t=>{e.log("beforeUpdate",Object.keys(t.data.fields).length,"fields")}),e.events.on("afterUpdate",t=>{e.log("afterUpdate",t.updatedCount,"in",t.durationMs.toFixed(2),"ms")}),e.events.on("error",t=>{e.log("error in",t.context,":",t.error.message)})}},d="payload-live-preview:scroll";function h(){"undefined"!=typeof window&&(function(){if("undefined"!=typeof window)try{sessionStorage.setItem(d,JSON.stringify({href:window.location.href,x:window.scrollX,y:window.scrollY}))}catch{}}(),window.location.reload())}var f=Object.freeze({NoTrustedOrigin:"LP0101",ReferrerOnlyTrust:"LP0102",PluginIncompatible:"LP0103",OrphanField:"LP0201",UnattributableUpdate:"LP0202",VisibilityGateDeferred:"LP0301",UnsafeAttributeWrite:"LP0401",TextTargetHasChildren:"LP0402",MissingArrayTemplate:"LP0403",StructuralItemUnkeyed:"LP0404",StructuralDuplicateKey:"LP0405",StructuralUnstableKeys:"LP0406",UnsupportedStrategy:"LP0407",MessageRejected:"LP0501",TokenRejected:"LP0502",HandlerThrew:"LP0601",TransformThrew:"LP0602",RendererThrew:"LP0603",StartupFailed:"LP0605",ReadyFailed:"LP0606",AuditRuntimeMissing:"LP0701",AuditNoFrameAncestors:"LP0702",AuditFrameOptionsBlocks:"LP0703",AuditBindingsExposed:"LP0704",AuditGateThresholdExceeded:"LP0705",AuditUnownedBindings:"LP0706",AuditNoBindings:"LP0707",AuditNotAPage:"LP0708",FragmentRequestFailed:"LP0801",FragmentResponseInvalid:"LP0802",FragmentUnauthorized:"LP0803",FragmentSuperseded:"LP0804",RouteRefreshLoop:"LP0805",FragmentStrategyUnavailable:"LP0806",V2ReadinessGap:"LP0709",RuntimeOnPublicPage:"LP0710"});exports.DIAGNOSTIC_CODES=f,exports.EventEmitter=class{#d=/* @__PURE__ */new Map;#h=/* @__PURE__ */new Map;on(e,t){return this.#f(this.#d,e,t)}once(e,t){return this.#f(this.#h,e,t)}off(e,t){this.#g(this.#d,e,t),this.#g(this.#h,e,t)}async emit(e,t){await this.#p(e,t)}async emitWhile(e,t,n){return this.#p(e,t,n)}async#p(e,t,n){const i=()=>n?.()??!0;if(!i())return!1;const s=this.#d.get(e);if(s)for(const n of[...s]){if(!i())return!1;if(await this.#m(n,t,e),!i())return!1}if(!i())return!1;const r=this.#h.get(e);if(r&&r.size>0){const n=[...r];this.#h.delete(e);for(const s of n){if(!i())return!1;if(await this.#m(s,t,e),!i())return!1}}return i()}listenerCount(e){return(this.#d.get(e)?.size??0)+(this.#h.get(e)?.size??0)}removeAllListeners(e){if(void 0===e)return this.#d.clear(),void this.#h.clear();this.#d.delete(e),this.#h.delete(e)}eventNames(){const e=/* @__PURE__ */new Set;for(const t of this.#d.keys())e.add(t);for(const t of this.#h.keys())e.add(t);return[...e]}#f(e,t,n){let i=e.get(t);return i||(i=/* @__PURE__ */new Set,e.set(t,i)),i.add(n),()=>{this.#g(e,t,n)}}#g(e,t,n){const i=e.get(t);void 0!==i&&(i.delete(n),0===i.size&&e.delete(t))}async#m(e,t,i){try{await e(t)}catch(e){const t="string"==typeof i||"number"==typeof i?String(i):"<event>";n(`[live-preview] LP0601: handler for "${t}" threw:`,e)}}},exports.PluginManager=class{#a;#v;#w;#y;#t;#b=/* @__PURE__ */new Map;#P=/* @__PURE__ */new Map;#S=/* @__PURE__ */new Map;#L=/* @__PURE__ */new Set;#k=/* @__PURE__ */new Map;#N=Promise.resolve();#x=0;constructor(e){this.#a=e.events,this.#v=e.config,this.#w=e.registerFieldRenderer,this.#t=t(e.log);const n=e.onTransformError;this.#y=void 0===n?void 0:t((...e)=>{const t=e[0];try{return n(t)}catch(e){return void this.#t("transform error reporter failed:",e)}})}register(e){return this.#x>0?this.#T(e,!1):this.#A(()=>this.#T(e,!0))}unregister(e){return this.#x>0?this.#C(e,!1):this.#A(async()=>{await this.#C(e,!0)})}destroyAll(){return this.#x>0?this.#O(!1):this.#A(async()=>{await this.#O(!0)})}applyTransforms(t,n,i,s){const r=this.#k.get(t);if(void 0===r||0===r.length)return n;let o=n;for(const a of[...r]){if(!a.active)continue;const{transform:r}=a;if(!1===s?.())return n;try{o=r(o,{fieldName:t,element:i.element,allFields:i.allFields});const a=e(o);if(!1===s?.())return n;if(a)throw new TypeError(`Transform for "${t}" returned a Promise/thenable; transforms must be synchronous`)}catch(e){if(!1===s?.())return n;const i=e instanceof Error?e:new Error(String(e));return this.#t(`transform for "${t}" failed:`,i),this.#y?.(i),n}}return o}list(){return[...this.#b.keys()]}snapshot(){const e=[],t=(e,t,n,i)=>{const s=i.counts();return{name:e,version:t,state:n,registrations:{transforms:s.transform,renderers:s.renderer,subscriptions:s.subscription,cleanups:s.cleanup}}};for(const[n,i]of this.#P)e.push(t(n,void 0,"initializing",i.scope));for(const[n,i]of this.#b)e.push(t(n,i.plugin.version,"active",i.scope));for(const t of this.#S.keys())e.push({name:t,version:void 0,state:"tearing-down",registrations:{transforms:0,renderers:0,subscriptions:0,cleanups:0}});return e}get size(){return this.#b.size}async#T(e,t){if(this.#b.has(e.name)||this.#P.has(e.name)||this.#S.has(e.name))return void this.#t(`plugin "${e.name}" already registered`);const n=function(e,t){if(void 0!==e){if(void 0!==e.runtime&&!function(e,t){const n=function(e){const t=/^v?(\d+)\.(\d+)\.(\d+)/.exec(e.trim());if(null!==t)return[Number(t[1]),Number(t[2]),Number(t[3])]}(e);return void 0!==n&&t.split("||").some(e=>{const i=e.trim().split(/\s+/).filter(e=>e.length>0);return 0===i.length?""===e.trim()&&""===t.trim():i.every(e=>function(e,t){const n=t.trim();if(""===n||"*"===n)return!0;const i=/^(\^|~|>=|<=|>|<|=)?\s*v?(\d+)\.(\d+)\.(\d+)/.exec(n);if(null===i)return!1;const s=i[1]??"=",r=[Number(i[2]),Number(i[3]),Number(i[4])],o=function(e,t){for(let n=0;n<3;n+=1){const i=(e[n]??0)-(t[n]??0);if(0!==i)return i}return 0}(e,r);switch(s){case"=":return 0===o;case">":return o>0;case">=":return o>=0;case"<":return o<0;case"<=":return o<=0;case"~":return o>=0&&e[0]===r[0]&&e[1]===r[1];case"^":return!(o<0)&&(r[0]>0?e[0]===r[0]:r[1]>0?0===e[0]&&e[1]===r[1]:0===e[0]&&0===e[1]&&e[2]===r[2]);default:return!1}}(n,e))})}(t,e.runtime))return`declares runtime ${e.runtime}, this runtime is ${t}`;if(void 0!==e.protocol){if(!Number.isInteger(e.protocol)||e.protocol<1)return`declares an invalid protocol version ${String(e.protocol)}`;if(e.protocol<4)return`declares protocol ${String(e.protocol)}, this runtime speaks ${String(4)}`}}}(e.compat,"2.0.0-beta.0");if(void 0!==n)return void this.#z(e.name,n);const i=new s(e.name,this.#t),r=this.#M(e.name,i),o={scope:i,cancelled:!1};this.#P.set(e.name,o);try{if(await this.#R(()=>e.init(r),t),o.cancelled)return;i.commit()}catch(t){return i.close(),void(o.cancelled||this.#t(`plugin "${e.name}" init failed:`,t))}finally{this.#P.get(e.name)===o&&this.#P.delete(e.name)}this.#b.set(e.name,{plugin:e,scope:i}),this.#t(`plugin "${e.name}" registered`)}#z(e,t){const n=`plugin "${e}" refused: ${t}`;this.#t(n),this.#a.emit("error",{error:new Error(n),context:"plugin",code:"LP0103"})}#M(e,t){return{events:new r(this.#a,t),registerFieldRenderer:e=>{t.stage("renderer",()=>{const t=this.#w(e);return"function"==typeof t?t:void 0})},registerTransform:(e,n)=>{const i={transform:n,active:!1};t.stage("transform",()=>{i.active=!0;const t=this.#k.get(e);return void 0===t?this.#k.set(e,[i]):t.push(i),()=>{i.active=!1;const t=this.#k.get(e);if(void 0===t)return;const n=t.indexOf(i);n>=0&&t.splice(n,1),0===t.length&&this.#k.delete(e)}})},registerCleanup:e=>{t.own(e)},getConfig:()=>this.#v,log:(...t)=>{this.#t(`[${e}]`,...t)}}}#C(e,t){const n=this.#P.get(e);if(void 0!==n)return n.cancelled=!0,n.scope.close(),Promise.resolve();const i=this.#S.get(e);if(void 0!==i)return this.#L.has(e)?Promise.resolve():t?this.#$(i):i;const s=this.#b.get(e);if(void 0===s)return Promise.resolve();this.#b.delete(e),s.scope.close();const r=Promise.resolve().then(()=>this.#E(e,s.plugin,t));return this.#S.set(e,r),r.then(()=>{this.#S.get(e)===r&&this.#S.delete(e)}),r}async#E(e,t,n){if(void 0!==t.destroy){this.#L.add(e);try{await this.#R(()=>t.destroy?.(),n)}catch(t){this.#t(`plugin "${e}" destroy failed:`,t)}finally{this.#L.delete(e)}}}async#O(e){for(const e of this.#P.values())e.cancelled=!0,e.scope.close();const t=[...this.#b.keys()];for(const n of t)await this.#C(n,e)}async#R(e,t){t?await this.#$(Promise.resolve().then(e)):await e()}async#$(e){this.#x+=1;try{await e}finally{this.#x-=1}}#A(e){const t=this.#N.then(e);return this.#N=t.catch(e=>{this.#t("plugin manager mutation failed:",e)}),t}},exports.createAnalyticsPlugin=function(){let e=0,t=0,n=0;return{name:"analytics",version:"1.0.0",init:i=>{i.events.on("afterUpdate",i=>{e+=1,t+=i.updatedCount,n+=i.durationMs})},getStats:()=>({updateCount:e,totalElements:t,totalDurationMs:n,averageDurationMs:0===e?0:n/e})}},exports.debugPlugin=u,exports.documentSavePlugin=function(e={}){const t=e.strategy??"silent";return{name:"document-save",version:"1.1.0",init:n=>{let i=!0;const s=/* @__PURE__ */new Set;n.registerCleanup?.(()=>{i=!1;for(const e of s)e.abort();s.clear()}),function(){if("undefined"!=typeof window)try{const e=sessionStorage.getItem(d);if(null===e)return;sessionStorage.removeItem(d);const t=JSON.parse(e);if(t.href!==window.location.href)return;window.scrollTo(t.x??0,t.y??0)}catch{}}(),n.events.on("documentSave",()=>{if(!i)return;if("silent"===t)return;if("reload"===t)return void h();const r=new AbortController;s.add(r);const o="fetch"===t?async function(e,t,n){if(e.handler)try{await e.handler(n)}catch(e){if(!i||n.aborted)return;t("document-save handler threw:",e)}else t("document-save: fetch strategy selected but no handler supplied")}(e,n.log,r.signal):async function(e,t,n,i){if("undefined"==typeof fetch)return void t("document-save: revalidate strategy needs fetch — not available");const s=e.revalidateUrl??"/api/revalidate";let r=!1;try{const o=await fetch(s,{method:"POST",signal:n,headers:{"Content-Type":"application/json",...e.revalidateHeaders??{}},body:JSON.stringify({source:"payload-live-preview"})});if(!i()||n.aborted)return;r=o.ok,r||t("document-save revalidate non-2xx:",o.status)}catch(e){if(!i()||n.aborted)return;t("document-save revalidate failed:",e)}r||"reload"!==e.onRevalidateFailure||h()}(e,n.log,r.signal,()=>i);o.finally(()=>{s.delete(r)})})}}},exports.highlightPlugin=c;
2
+ //# sourceMappingURL=plugins.cjs.map