@elytracms/next 0.0.1 → 0.0.2
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/dist/client.js +1 -44
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +14 -1548
- package/dist/index.js +26 -3578
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
package/dist/index.js
CHANGED
|
@@ -1,2573 +1,19 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
"invalid-collection-config",
|
|
18
|
-
"invalid-field-config",
|
|
19
|
-
"missing-required-field",
|
|
20
|
-
"invalid-field-value",
|
|
21
|
-
// relations & assets (EC-017)
|
|
22
|
-
"unknown-relation-target",
|
|
23
|
-
// broken published references after a weak unpublish (EC-173)
|
|
24
|
-
"unpublished-relation-target",
|
|
25
|
-
"cardinality-violation",
|
|
26
|
-
"unknown-asset",
|
|
27
|
-
"duplicate-document",
|
|
28
|
-
// localization (EC-018)
|
|
29
|
-
"unknown-locale",
|
|
30
|
-
"missing-localized-value",
|
|
31
|
-
// routes / redirects / resolution (EC-019, EC-020)
|
|
32
|
-
"route-conflict",
|
|
33
|
-
"redirect-loop",
|
|
34
|
-
"unknown-route-target",
|
|
35
|
-
// page hierarchy (EC-218): a parent chain that loops
|
|
36
|
-
"hierarchy-cycle",
|
|
37
|
-
// versions (EC-021)
|
|
38
|
-
"unknown-version"
|
|
39
|
-
]);
|
|
40
|
-
var cmsIssueSeveritySchema = z.enum(["error", "warning"]);
|
|
41
|
-
z.object({
|
|
42
|
-
code: cmsIssueCodeSchema,
|
|
43
|
-
severity: cmsIssueSeveritySchema,
|
|
44
|
-
message: z.string(),
|
|
45
|
-
/** Path into the CMS data, e.g. ['collections', 'post', 'fields', 'author']. */
|
|
46
|
-
path: z.array(z.union([z.string(), z.number()])),
|
|
47
|
-
/** The offending collection id, when applicable. */
|
|
48
|
-
collectionId: z.string().optional(),
|
|
49
|
-
/** The offending document id, when applicable. */
|
|
50
|
-
documentId: z.string().optional(),
|
|
51
|
-
meta: z.record(z.string(), z.unknown()).optional()
|
|
52
|
-
});
|
|
53
|
-
function cmsIssue(init) {
|
|
54
|
-
return {
|
|
55
|
-
code: init.code,
|
|
56
|
-
severity: init.severity ?? "error",
|
|
57
|
-
message: init.message,
|
|
58
|
-
path: [...init.path],
|
|
59
|
-
...init.collectionId ? { collectionId: init.collectionId } : {},
|
|
60
|
-
...init.documentId ? { documentId: init.documentId } : {},
|
|
61
|
-
...init.meta ? { meta: init.meta } : {}
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
z.enum([
|
|
65
|
-
"text",
|
|
66
|
-
"number",
|
|
67
|
-
"boolean",
|
|
68
|
-
"date",
|
|
69
|
-
"select",
|
|
70
|
-
"richText",
|
|
71
|
-
"relation",
|
|
72
|
-
"asset",
|
|
73
|
-
// Composition field (EC-186, AD-12): the value is a constrained tree of
|
|
74
|
-
// registry components (a canvas), not a scalar. The vocabulary is code.
|
|
75
|
-
"blocks",
|
|
76
|
-
// Object / repeater field (EC-253): a nested group of field-defs — a single
|
|
77
|
-
// object (`cardinality: 'one'`) or an ordered array of them (`'many'`). The
|
|
78
|
-
// missing array-of-objects primitive real sites lean on (menus, forms, galleries).
|
|
79
|
-
"object"
|
|
80
|
-
]);
|
|
81
|
-
var cardinalitySchema = z.enum(["one", "many"]);
|
|
82
|
-
var fieldContextSchema = z.enum(["document", "prop", "both"]);
|
|
83
|
-
var fieldControlSchema = z.enum(["input", "textarea", "color", "json"]);
|
|
84
|
-
var fieldFormMetaSchema = z.object({
|
|
85
|
-
/** Human label shown in the editor; falls back to the field name when omitted. */
|
|
86
|
-
label: z.string().optional(),
|
|
87
|
-
/** Helper/description text shown under the field. */
|
|
88
|
-
description: z.string().optional(),
|
|
89
|
-
/** Placeholder for text-like inputs. */
|
|
90
|
-
placeholder: z.string().optional(),
|
|
91
|
-
/** Grouping section the field belongs to (a fieldset). */
|
|
92
|
-
group: z.string().optional(),
|
|
93
|
-
/** Tab the field belongs to (for tabbed editors). */
|
|
94
|
-
tab: z.string().optional(),
|
|
95
|
-
/** Sort order within its group/tab; lower comes first. */
|
|
96
|
-
order: z.number().optional(),
|
|
97
|
-
/** When true the editor renders the field read-only. */
|
|
98
|
-
readOnly: z.boolean().optional(),
|
|
99
|
-
/** When true the editor hides the field (still validated/stored). */
|
|
100
|
-
hidden: z.boolean().optional(),
|
|
101
|
-
/** Preferred input control when the field type maps to several (EC-190). */
|
|
102
|
-
control: fieldControlSchema.optional(),
|
|
103
|
-
/**
|
|
104
|
-
* When true the editor edits this value inline on the canvas (EC-158) rather
|
|
105
|
-
* than as a form row — used for text-like component props rendered as visible
|
|
106
|
-
* content. The single home for inline-editing across documents and props.
|
|
107
|
-
*/
|
|
108
|
-
inlineEditable: z.boolean().optional()
|
|
109
|
-
});
|
|
110
|
-
var fieldValidationSchema = z.object({
|
|
111
|
-
/** When true a value must be present (and non-empty for strings/arrays). */
|
|
112
|
-
required: z.boolean().optional(),
|
|
113
|
-
/** Minimum string length / array length / numeric value (type-dependent). */
|
|
114
|
-
min: z.number().optional(),
|
|
115
|
-
/** Maximum string length / array length / numeric value (type-dependent). */
|
|
116
|
-
max: z.number().optional(),
|
|
117
|
-
/** RegExp source string a text value must match. */
|
|
118
|
-
pattern: z.string().optional(),
|
|
119
|
-
/** Value must be unique across the collection's documents. */
|
|
120
|
-
unique: z.boolean().optional()
|
|
121
|
-
});
|
|
122
|
-
var selectOptionSchema = z.object({
|
|
123
|
-
value: z.string(),
|
|
124
|
-
label: z.string().optional()
|
|
125
|
-
});
|
|
126
|
-
var baseFieldShape = {
|
|
127
|
-
/** Stable field name (the key in a document's values; survives label changes). */
|
|
128
|
-
name: z.string().min(1),
|
|
129
|
-
/** When true the field stores a separate value per locale (EC-018). */
|
|
130
|
-
localized: z.boolean().optional(),
|
|
131
|
-
/**
|
|
132
|
-
* Marks the field as filterable/sortable for the closed-form list accessors
|
|
133
|
-
* (EC-143, vision AD-3). `listDocuments` filters and sorts are permitted only
|
|
134
|
-
* on declared fields — this keeps backend indexes honest and stops filter
|
|
135
|
-
* vocabulary creep. Never affects stored values or validation.
|
|
136
|
-
*/
|
|
137
|
-
filterable: z.boolean().optional(),
|
|
138
|
-
/**
|
|
139
|
-
* Where this field-def is valid (EC-190): a CMS document field, a component
|
|
140
|
-
* prop, or both. Defaults to `document` when omitted (back-compat — every
|
|
141
|
-
* existing collection field-def stays a document field). Document-only
|
|
142
|
-
* attributes (`filterable`, `validation.unique`) declared on a `context:'prop'`
|
|
143
|
-
* field are a structured issue (`assertPropFieldDef`), never silent.
|
|
144
|
-
*/
|
|
145
|
-
context: fieldContextSchema.optional(),
|
|
146
|
-
/**
|
|
147
|
-
* Default value applied when none is set (EC-190). On a component prop this is
|
|
148
|
-
* the renderer's static fallback (EC-015); on a document field it seeds new
|
|
149
|
-
* documents — the per-field starter-content mechanism (no template entity
|
|
150
|
-
* needed). Never affects whether a value is *stored*, only what fills a gap.
|
|
151
|
-
*/
|
|
152
|
-
default: z.unknown().optional(),
|
|
153
|
-
form: fieldFormMetaSchema.optional(),
|
|
154
|
-
validation: fieldValidationSchema.optional()
|
|
155
|
-
};
|
|
156
|
-
z.object(baseFieldShape);
|
|
157
|
-
var fieldDefSchema = z.discriminatedUnion("type", [
|
|
158
|
-
z.object({ ...baseFieldShape, type: z.literal("text") }),
|
|
159
|
-
z.object({ ...baseFieldShape, type: z.literal("number") }),
|
|
160
|
-
z.object({ ...baseFieldShape, type: z.literal("boolean") }),
|
|
161
|
-
z.object({ ...baseFieldShape, type: z.literal("date") }),
|
|
162
|
-
z.object({
|
|
163
|
-
...baseFieldShape,
|
|
164
|
-
type: z.literal("select"),
|
|
165
|
-
options: z.array(selectOptionSchema).min(1),
|
|
166
|
-
/** Allow selecting multiple options (stored as an array). */
|
|
167
|
-
multiple: z.boolean().optional()
|
|
168
|
-
}),
|
|
169
|
-
z.object({
|
|
170
|
-
...baseFieldShape,
|
|
171
|
-
type: z.literal("richText"),
|
|
172
|
-
/**
|
|
173
|
-
* Embeddable composition vocabulary (EC-189, AD-12): component ids editors
|
|
174
|
-
* may embed as `componentEmbed` blocks inside the prose — the SAME `allow`
|
|
175
|
-
* mechanism a `blocks` field uses, applied to rich-text embeds. Each embed
|
|
176
|
-
* holds a single `ComponentNode` validated against this list by
|
|
177
|
-
* `validateCompositionValue` (`@elytracms/project-graph`). Omit to allow any
|
|
178
|
-
* registered component; deeper nesting is governed by each component's slot
|
|
179
|
-
* `allow`. The leash is code.
|
|
180
|
-
*/
|
|
181
|
-
allow: z.array(z.string().min(1)).optional()
|
|
182
|
-
}),
|
|
183
|
-
z.object({
|
|
184
|
-
...baseFieldShape,
|
|
185
|
-
type: z.literal("relation"),
|
|
186
|
-
/** Id of the collection this relation points at. */
|
|
187
|
-
target: z.string().min(1),
|
|
188
|
-
cardinality: cardinalitySchema,
|
|
189
|
-
/**
|
|
190
|
-
* Delivery-shape population declaration (EC-142). Defaults to populated:
|
|
191
|
-
* delivery resolution expands the relation to a depth-1 populated reference.
|
|
192
|
-
* Set to `false` to opt out and receive reference stubs
|
|
193
|
-
* (`{ id, collection }`) instead. Never affects stored values.
|
|
194
|
-
*/
|
|
195
|
-
populate: z.boolean().optional(),
|
|
196
|
-
/**
|
|
197
|
-
* Strict referential integrity (EC-173). References are **weak by
|
|
198
|
-
* default**: unpublishing/deleting a referenced target is allowed and the
|
|
199
|
-
* reference degrades to an explicit validation issue plus a delivery
|
|
200
|
-
* fallback. `strict: true` is the deliberate opt-in that inverts this for
|
|
201
|
-
* one field: while any document references a target through this field,
|
|
202
|
-
* deleting the target is blocked — and while a *published* document does,
|
|
203
|
-
* unpublishing it is blocked too (enforced in `@elytracms/operations`).
|
|
204
|
-
*/
|
|
205
|
-
strict: z.boolean().optional()
|
|
206
|
-
}),
|
|
207
|
-
z.object({
|
|
208
|
-
...baseFieldShape,
|
|
209
|
-
type: z.literal("asset"),
|
|
210
|
-
cardinality: cardinalitySchema.default("one"),
|
|
211
|
-
/** Restrict to specific asset kinds (e.g. 'image', 'file'); empty = any. */
|
|
212
|
-
accept: z.array(z.string()).optional()
|
|
213
|
-
}),
|
|
214
|
-
z.object({
|
|
215
|
-
...baseFieldShape,
|
|
216
|
-
type: z.literal("blocks"),
|
|
217
|
-
/**
|
|
218
|
-
* The composition vocabulary (EC-186, AD-12): allowed component ids editors
|
|
219
|
-
* may place as TOP-LEVEL blocks. Omit to allow any registered component;
|
|
220
|
-
* deeper nesting is governed by each component's slot `allow`. The value
|
|
221
|
-
* (a `ComponentNode` tree) is validated against this by
|
|
222
|
-
* `validateCompositionValue` (`@elytracms/project-graph`). The leash is code.
|
|
223
|
-
*/
|
|
224
|
-
allow: z.array(z.string().min(1)).optional(),
|
|
225
|
-
/** `one` = a single root component; `many` = an ordered list (default). */
|
|
226
|
-
cardinality: cardinalitySchema.default("many")
|
|
227
|
-
}),
|
|
228
|
-
z.object({
|
|
229
|
-
...baseFieldShape,
|
|
230
|
-
type: z.literal("object"),
|
|
231
|
-
/**
|
|
232
|
-
* The nested field-defs. Recursive via `z.lazy` (a subfield may itself be an
|
|
233
|
-
* `object`), so Konditorei's nested menu, the form builder's groups→fields,
|
|
234
|
-
* and multi-entry settings model faithfully. Subfields carry their own `name`
|
|
235
|
-
* (they are object keys), so this is the full `FieldDef`, not the prop shape.
|
|
236
|
-
*/
|
|
237
|
-
fields: z.lazy(() => z.array(fieldDefSchema)),
|
|
238
|
-
/** `one` = a single nested object; `many` = an ordered array (the repeater). */
|
|
239
|
-
cardinality: cardinalitySchema.default("one")
|
|
240
|
-
})
|
|
241
|
-
]);
|
|
242
|
-
var collectionKindSchema = z.enum(["document", "asset"]);
|
|
243
|
-
var collectionFormMetaSchema = z.object({
|
|
244
|
-
/** Plural human label, e.g. "Blog Posts". */
|
|
245
|
-
label: z.string().optional(),
|
|
246
|
-
/** Singular human label, e.g. "Blog Post". */
|
|
247
|
-
labelSingular: z.string().optional(),
|
|
248
|
-
description: z.string().optional(),
|
|
249
|
-
/** Declared ordering of form groups (fieldsets). */
|
|
250
|
-
groups: z.array(z.string()).optional(),
|
|
251
|
-
/** Declared ordering of form tabs. */
|
|
252
|
-
tabs: z.array(z.string()).optional()
|
|
253
|
-
});
|
|
254
|
-
z.object({
|
|
255
|
-
id: z.string().min(1),
|
|
256
|
-
kind: collectionKindSchema.default("document"),
|
|
257
|
-
fields: z.array(fieldDefSchema).default([]),
|
|
258
|
-
form: collectionFormMetaSchema.optional(),
|
|
259
|
-
/** When true, documents in this collection support localized variants (EC-018). */
|
|
260
|
-
localized: z.boolean().optional(),
|
|
261
|
-
/** Name of the field used as the document's display title (defaults to `title`). */
|
|
262
|
-
titleField: z.string().optional(),
|
|
263
|
-
/**
|
|
264
|
-
* When true, this collection holds exactly ONE fixed document — Settings,
|
|
265
|
-
* Menus, Footer, Homepage SEO… (EC-217). The studio skips the list view and
|
|
266
|
-
* opens the detail editor directly on the single document. Pure authoring
|
|
267
|
-
* sugar: internally identical to a `document` collection, so the engine,
|
|
268
|
-
* delivery, and validation paths are unchanged. Meaningless on an `asset`
|
|
269
|
-
* collection (flagged as `invalid-collection-config`).
|
|
270
|
-
*/
|
|
271
|
-
singleton: z.boolean().optional()
|
|
272
|
-
});
|
|
273
|
-
function fieldOf(collection, name) {
|
|
274
|
-
return collection.fields.find((f) => f.name === name);
|
|
275
|
-
}
|
|
276
|
-
var localeSchema = z.string().min(1);
|
|
277
|
-
var documentRefSchema = z.object({
|
|
278
|
-
collection: z.string().min(1),
|
|
279
|
-
id: z.string().min(1)
|
|
280
|
-
});
|
|
281
|
-
documentRefSchema.extend({
|
|
282
|
-
locale: localeSchema
|
|
283
|
-
});
|
|
284
|
-
function documentKey(ref) {
|
|
285
|
-
return `${ref.collection}:${ref.id}`;
|
|
286
|
-
}
|
|
287
|
-
z.enum(["draft", "published"]);
|
|
288
|
-
var documentSchema = z.object({
|
|
289
|
-
collection: z.string().min(1),
|
|
290
|
-
id: z.string().min(1),
|
|
291
|
-
/** Non-localized field values shared across locales. */
|
|
292
|
-
values: z.record(z.string(), z.unknown()).default({}),
|
|
293
|
-
/** Per-locale overrides for localized fields: `{ [locale]: { [field]: value } }`. */
|
|
294
|
-
localized: z.record(localeSchema, z.record(z.string(), z.unknown())).optional(),
|
|
295
|
-
/** The default locale this document was authored in (EC-018 fallback source). */
|
|
296
|
-
defaultLocale: localeSchema.optional()
|
|
297
|
-
});
|
|
298
|
-
function refOf(doc) {
|
|
299
|
-
return { collection: doc.collection, id: doc.id };
|
|
300
|
-
}
|
|
301
|
-
var localeConfigSchema = z.object({
|
|
302
|
-
default: z.string().min(1),
|
|
303
|
-
locales: z.array(z.string().min(1)).min(1)
|
|
304
|
-
});
|
|
305
|
-
function isKnownLocale(config, locale) {
|
|
306
|
-
return config.locales.includes(locale);
|
|
307
|
-
}
|
|
308
|
-
function isFieldLocalized(collection, fieldName) {
|
|
309
|
-
return fieldOf(collection, fieldName)?.localized === true;
|
|
310
|
-
}
|
|
311
|
-
function documentDefaultLocale(doc, config) {
|
|
312
|
-
return doc.defaultLocale ?? config.default;
|
|
313
|
-
}
|
|
314
|
-
function resolveField(collection, doc, fieldName, locale, config) {
|
|
315
|
-
if (!isFieldLocalized(collection, fieldName)) {
|
|
316
|
-
return { value: doc.values[fieldName], sourceLocale: locale, fellBack: false };
|
|
317
|
-
}
|
|
318
|
-
const requested = doc.localized?.[locale];
|
|
319
|
-
if (requested && fieldName in requested) {
|
|
320
|
-
return { value: requested[fieldName], sourceLocale: locale, fellBack: false };
|
|
321
|
-
}
|
|
322
|
-
const fallbackLocale = documentDefaultLocale(doc, config);
|
|
323
|
-
if (fallbackLocale !== locale) {
|
|
324
|
-
const fallback = doc.localized?.[fallbackLocale];
|
|
325
|
-
if (fallback && fieldName in fallback) {
|
|
326
|
-
return { value: fallback[fieldName], sourceLocale: fallbackLocale, fellBack: true };
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
if (fieldName in doc.values) {
|
|
330
|
-
return { value: doc.values[fieldName], sourceLocale: fallbackLocale, fellBack: true };
|
|
331
|
-
}
|
|
332
|
-
return { value: void 0, sourceLocale: fallbackLocale, fellBack: fallbackLocale !== locale };
|
|
333
|
-
}
|
|
334
|
-
function validateLocale(config, locale, path = ["locale"]) {
|
|
335
|
-
if (isKnownLocale(config, locale)) return [];
|
|
336
|
-
return [
|
|
337
|
-
cmsIssue({
|
|
338
|
-
code: "unknown-locale",
|
|
339
|
-
message: `Locale "${locale}" is not in the configured locale set.`,
|
|
340
|
-
path,
|
|
341
|
-
meta: { locale, configured: config.locales }
|
|
342
|
-
})
|
|
343
|
-
];
|
|
344
|
-
}
|
|
345
|
-
var routeRecordSchema = z.object({
|
|
346
|
-
id: z.string().min(1),
|
|
347
|
-
/** Path pattern, leading slash, with optional `:param` segments. */
|
|
348
|
-
pattern: z.string().min(1),
|
|
349
|
-
/** Locale this route serves (EC-018). Omitted = locale-agnostic. */
|
|
350
|
-
locale: localeSchema.optional(),
|
|
351
|
-
/**
|
|
352
|
-
* The document this route resolves to: a `page`-collection document (whose
|
|
353
|
-
* `body` composition renders into a layout), or a content-collection document
|
|
354
|
-
* (rendered by a dev route component). The `id` may be a `:param` placeholder
|
|
355
|
-
* materialized from the matched URL (EC-166).
|
|
356
|
-
*/
|
|
357
|
-
document: z.object({ collection: z.string().min(1), id: z.string().min(1) }).optional()
|
|
358
|
-
});
|
|
359
|
-
var redirectRecordSchema = z.object({
|
|
360
|
-
id: z.string().min(1),
|
|
361
|
-
from: z.string().min(1),
|
|
362
|
-
to: z.string().min(1),
|
|
363
|
-
/** 301 permanent (default) or 302 temporary. */
|
|
364
|
-
permanent: z.boolean().default(true)
|
|
365
|
-
});
|
|
366
|
-
function splitPath(path) {
|
|
367
|
-
const [pathname] = path.split(/[?#]/, 1);
|
|
368
|
-
return (pathname ?? "").split("/").filter((s) => s.length > 0);
|
|
369
|
-
}
|
|
370
|
-
function compilePattern(pattern) {
|
|
371
|
-
return splitPath(pattern).map(
|
|
372
|
-
(seg) => seg.startsWith(":") ? { kind: "param", name: seg.slice(1) } : { kind: "static", value: seg }
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
function matchSegments(segments, urlSegments) {
|
|
376
|
-
if (segments.length !== urlSegments.length) return void 0;
|
|
377
|
-
const params = {};
|
|
378
|
-
for (let i = 0; i < segments.length; i++) {
|
|
379
|
-
const seg = segments[i];
|
|
380
|
-
const value = urlSegments[i];
|
|
381
|
-
if (seg === void 0 || value === void 0) return void 0;
|
|
382
|
-
if (seg.kind === "static") {
|
|
383
|
-
if (seg.value !== value) return void 0;
|
|
384
|
-
} else {
|
|
385
|
-
params[seg.name] = decodeURIComponent(value);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
return params;
|
|
389
|
-
}
|
|
390
|
-
function specificity(segments) {
|
|
391
|
-
return segments.map((s) => s.kind === "static" ? "S" : "P").join("");
|
|
392
|
-
}
|
|
393
|
-
function normalizePath(path) {
|
|
394
|
-
const [pathname] = path.split(/[?#]/, 1);
|
|
395
|
-
const segs = splitPath(pathname ?? "");
|
|
396
|
-
return "/" + segs.join("/");
|
|
397
|
-
}
|
|
398
|
-
function createRouter(routes, redirects = [], options = {}) {
|
|
399
|
-
const issues = [];
|
|
400
|
-
const compiled = routes.map((route) => ({
|
|
401
|
-
route,
|
|
402
|
-
segments: compilePattern(route.pattern)
|
|
403
|
-
}));
|
|
404
|
-
const conflictKey = (c) => {
|
|
405
|
-
const locale = c.route.locale ?? "*";
|
|
406
|
-
const sig = c.segments.map((s) => s.kind === "static" ? `s:${s.value}` : "p").join("/");
|
|
407
|
-
return `${locale}|${sig}`;
|
|
408
|
-
};
|
|
409
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
410
|
-
for (const c of compiled) {
|
|
411
|
-
const key = conflictKey(c);
|
|
412
|
-
const existing = byKey.get(key);
|
|
413
|
-
if (existing) {
|
|
414
|
-
issues.push(
|
|
415
|
-
cmsIssue({
|
|
416
|
-
code: "route-conflict",
|
|
417
|
-
message: `Routes "${existing.route.id}" and "${c.route.id}" both match the same URLs (pattern "${c.route.pattern}").`,
|
|
418
|
-
path: ["routes", c.route.id],
|
|
419
|
-
meta: { conflictsWith: existing.route.id, pattern: c.route.pattern }
|
|
420
|
-
})
|
|
421
|
-
);
|
|
422
|
-
} else {
|
|
423
|
-
byKey.set(key, c);
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
const redirectMap = /* @__PURE__ */ new Map();
|
|
427
|
-
for (const r of redirects) {
|
|
428
|
-
redirectMap.set(normalizePath(r.from), r);
|
|
429
|
-
}
|
|
430
|
-
const followRedirects = (from) => {
|
|
431
|
-
const chain = [];
|
|
432
|
-
const seen = /* @__PURE__ */ new Set();
|
|
433
|
-
let current = normalizePath(from);
|
|
434
|
-
let permanent = true;
|
|
435
|
-
chain.push(current);
|
|
436
|
-
while (true) {
|
|
437
|
-
const hop = redirectMap.get(current);
|
|
438
|
-
if (!hop) {
|
|
439
|
-
return { target: current, chain, loop: false, permanent };
|
|
440
|
-
}
|
|
441
|
-
if (!hop.permanent) permanent = false;
|
|
442
|
-
const next = normalizePath(hop.to);
|
|
443
|
-
if (seen.has(next) || next === current) {
|
|
444
|
-
chain.push(next);
|
|
445
|
-
return { target: next, chain, loop: true, permanent };
|
|
446
|
-
}
|
|
447
|
-
seen.add(current);
|
|
448
|
-
current = next;
|
|
449
|
-
chain.push(current);
|
|
450
|
-
}
|
|
451
|
-
};
|
|
452
|
-
const loopReported = /* @__PURE__ */ new Set();
|
|
453
|
-
for (const r of redirects) {
|
|
454
|
-
const res = followRedirects(r.from);
|
|
455
|
-
if (res.loop && !loopReported.has(res.target)) {
|
|
456
|
-
loopReported.add(res.target);
|
|
457
|
-
issues.push(
|
|
458
|
-
cmsIssue({
|
|
459
|
-
code: "redirect-loop",
|
|
460
|
-
message: `Redirect chain starting at "${r.from}" loops (\u2026${res.chain.slice(-3).join(" -> ")}).`,
|
|
461
|
-
path: ["redirects", r.id],
|
|
462
|
-
meta: { chain: res.chain }
|
|
463
|
-
})
|
|
464
|
-
);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
const orderedMatches = (urlSegments, locale) => {
|
|
468
|
-
const matches = [];
|
|
469
|
-
for (const c of compiled) {
|
|
470
|
-
if (locale !== void 0 && c.route.locale !== void 0 && c.route.locale !== locale) {
|
|
471
|
-
continue;
|
|
472
|
-
}
|
|
473
|
-
const params = matchSegments(c.segments, urlSegments);
|
|
474
|
-
if (params) matches.push({ compiled: c, params });
|
|
475
|
-
}
|
|
476
|
-
matches.sort((a, b) => specificity(a.compiled.segments).localeCompare(specificity(b.compiled.segments)) * -1);
|
|
477
|
-
return matches;
|
|
478
|
-
};
|
|
479
|
-
const resolveUrl = (url, locale) => {
|
|
480
|
-
const path = normalizePath(url);
|
|
481
|
-
if (redirectMap.has(path)) {
|
|
482
|
-
const res = followRedirects(path);
|
|
483
|
-
return {
|
|
484
|
-
status: "redirect",
|
|
485
|
-
redirectTarget: res.target,
|
|
486
|
-
permanent: res.permanent,
|
|
487
|
-
dynamicParams: {},
|
|
488
|
-
fallback: false
|
|
489
|
-
};
|
|
490
|
-
}
|
|
491
|
-
const urlSegments = splitPath(path);
|
|
492
|
-
let fallback = false;
|
|
493
|
-
let matches = orderedMatches(urlSegments, locale);
|
|
494
|
-
if (matches.length === 0 && locale && options.defaultLocale && locale !== options.defaultLocale) {
|
|
495
|
-
const fallbackMatches = orderedMatches(urlSegments, options.defaultLocale);
|
|
496
|
-
if (fallbackMatches.length > 0) {
|
|
497
|
-
matches = fallbackMatches;
|
|
498
|
-
fallback = true;
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
if (matches.length === 0 && locale === void 0) {
|
|
502
|
-
matches = orderedMatches(urlSegments, void 0);
|
|
503
|
-
}
|
|
504
|
-
const best = matches[0];
|
|
505
|
-
if (!best) {
|
|
506
|
-
return { status: "notFound", dynamicParams: {}, fallback };
|
|
507
|
-
}
|
|
508
|
-
const route = best.compiled.route;
|
|
509
|
-
return {
|
|
510
|
-
status: "ok",
|
|
511
|
-
route,
|
|
512
|
-
...route.document ? { documentRef: route.document } : {},
|
|
513
|
-
dynamicParams: best.params,
|
|
514
|
-
fallback
|
|
515
|
-
};
|
|
516
|
-
};
|
|
517
|
-
return { issues, resolveUrl, followRedirects };
|
|
518
|
-
}
|
|
519
|
-
|
|
520
|
-
// ../cms-core/src/hierarchy.ts
|
|
521
|
-
var DEFAULT_PARENT_FIELD = "parent";
|
|
522
|
-
var DEFAULT_SLUG_FIELD = "slug";
|
|
523
|
-
function hierarchyFields(config = {}) {
|
|
524
|
-
return {
|
|
525
|
-
parentField: config.parentField ?? DEFAULT_PARENT_FIELD,
|
|
526
|
-
slugField: config.slugField ?? DEFAULT_SLUG_FIELD
|
|
527
|
-
};
|
|
528
|
-
}
|
|
529
|
-
function parentRelationField(collection, config = {}) {
|
|
530
|
-
const { parentField } = hierarchyFields(config);
|
|
531
|
-
const field = collection.fields.find((f) => f.name === parentField);
|
|
532
|
-
if (field && field.type === "relation" && field.target === collection.id && field.cardinality === "one") {
|
|
533
|
-
return field;
|
|
534
|
-
}
|
|
535
|
-
return void 0;
|
|
536
|
-
}
|
|
537
|
-
function isHierarchical(collection, config = {}) {
|
|
538
|
-
return parentRelationField(collection, config) !== void 0;
|
|
539
|
-
}
|
|
540
|
-
function readParentId(doc, parentField) {
|
|
541
|
-
const value = doc.values[parentField];
|
|
542
|
-
if (value == null) return void 0;
|
|
543
|
-
if (typeof value === "string") return value.length > 0 ? value : void 0;
|
|
544
|
-
if (typeof value === "object" && "id" in value) {
|
|
545
|
-
const id = value.id;
|
|
546
|
-
return typeof id === "string" && id.length > 0 ? id : void 0;
|
|
547
|
-
}
|
|
548
|
-
return void 0;
|
|
549
|
-
}
|
|
550
|
-
function readSlug(doc, slugField) {
|
|
551
|
-
const value = doc.values[slugField];
|
|
552
|
-
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
553
|
-
}
|
|
554
|
-
function composePath(doc, getById, config = {}) {
|
|
555
|
-
const { parentField, slugField } = hierarchyFields(config);
|
|
556
|
-
const segments = [];
|
|
557
|
-
const seen = /* @__PURE__ */ new Set([doc.id]);
|
|
558
|
-
let cycle = false;
|
|
559
|
-
let missingSlug = false;
|
|
560
|
-
let danglingParent = false;
|
|
561
|
-
let current = doc;
|
|
562
|
-
while (current) {
|
|
563
|
-
const slug = readSlug(current, slugField);
|
|
564
|
-
if (slug === void 0) missingSlug = true;
|
|
565
|
-
else segments.unshift(slug);
|
|
566
|
-
const parentId = readParentId(current, parentField);
|
|
567
|
-
if (parentId === void 0) break;
|
|
568
|
-
const parent = getById(parentId);
|
|
569
|
-
if (!parent) {
|
|
570
|
-
danglingParent = true;
|
|
571
|
-
break;
|
|
572
|
-
}
|
|
573
|
-
if (seen.has(parent.id)) {
|
|
574
|
-
cycle = true;
|
|
575
|
-
break;
|
|
576
|
-
}
|
|
577
|
-
seen.add(parent.id);
|
|
578
|
-
current = parent;
|
|
579
|
-
}
|
|
580
|
-
return { segments, ok: !cycle && !missingSlug && !danglingParent, cycle, missingSlug, danglingParent };
|
|
581
|
-
}
|
|
582
|
-
function isHierarchyTemplate(route) {
|
|
583
|
-
if (!route.document) return false;
|
|
584
|
-
const segments = splitPath(route.pattern);
|
|
585
|
-
const last = segments[segments.length - 1];
|
|
586
|
-
return last !== void 0 && last.startsWith(":") && last.endsWith("*");
|
|
587
|
-
}
|
|
588
|
-
function hierarchyMount(route) {
|
|
589
|
-
if (!isHierarchyTemplate(route) || !route.document) return void 0;
|
|
590
|
-
const prefix = splitPath(route.pattern).slice(0, -1);
|
|
591
|
-
if (prefix.some((segment) => segment.startsWith(":"))) return void 0;
|
|
592
|
-
return {
|
|
593
|
-
collection: route.document.collection,
|
|
594
|
-
prefix,
|
|
595
|
-
...route.locale ? { locale: route.locale } : {}
|
|
596
|
-
};
|
|
597
|
-
}
|
|
598
|
-
function resolveByPath(segments, docs, config = {}) {
|
|
599
|
-
if (segments.length === 0) return void 0;
|
|
600
|
-
const byId = new Map(docs.map((d) => [d.id, d]));
|
|
601
|
-
const getById = (id) => byId.get(id);
|
|
602
|
-
const target = segments.join("/");
|
|
603
|
-
let match;
|
|
604
|
-
for (const doc of docs) {
|
|
605
|
-
const composed = composePath(doc, getById, config);
|
|
606
|
-
if (!composed.ok) continue;
|
|
607
|
-
if (composed.segments.join("/") === target && (!match || doc.id < match.id)) {
|
|
608
|
-
match = doc;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
return match;
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
// ../cms-core/src/validate-document.ts
|
|
615
|
-
function isEmpty(value) {
|
|
616
|
-
if (value === void 0 || value === null) return true;
|
|
617
|
-
if (typeof value === "string") return value.length === 0;
|
|
618
|
-
if (Array.isArray(value)) return value.length === 0;
|
|
619
|
-
if (isPlainObject(value)) return Object.keys(value).length === 0;
|
|
620
|
-
return false;
|
|
621
|
-
}
|
|
622
|
-
function isPlainObject(value) {
|
|
623
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
624
|
-
}
|
|
625
|
-
function checkScalarType(field, value) {
|
|
626
|
-
switch (field.type) {
|
|
627
|
-
case "text":
|
|
628
|
-
return typeof value === "string" ? null : "expects a string";
|
|
629
|
-
case "richText":
|
|
630
|
-
return typeof value === "string" || typeof value === "object" && value !== null ? null : "expects rich-text content (a string or a rich-text value)";
|
|
631
|
-
case "number":
|
|
632
|
-
return typeof value === "number" && !Number.isNaN(value) ? null : "expects a number";
|
|
633
|
-
case "boolean":
|
|
634
|
-
return typeof value === "boolean" ? null : "expects a boolean";
|
|
635
|
-
case "date":
|
|
636
|
-
if (typeof value === "string") {
|
|
637
|
-
return Number.isNaN(Date.parse(value)) ? "expects a valid date string" : null;
|
|
638
|
-
}
|
|
639
|
-
return typeof value === "number" ? null : "expects a date (ISO string or epoch number)";
|
|
640
|
-
case "select": {
|
|
641
|
-
const allowed = new Set(field.options.map((o) => o.value));
|
|
642
|
-
const values = field.multiple ? Array.isArray(value) ? value : [value] : [value];
|
|
643
|
-
for (const v of values) {
|
|
644
|
-
if (typeof v !== "string" || !allowed.has(v)) {
|
|
645
|
-
return `has value "${String(v)}" not among its options`;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
return null;
|
|
649
|
-
}
|
|
650
|
-
case "object":
|
|
651
|
-
if (field.cardinality === "many") {
|
|
652
|
-
return Array.isArray(value) && value.every((item) => isPlainObject(item)) ? null : "expects a list of objects";
|
|
653
|
-
}
|
|
654
|
-
return isPlainObject(value) ? null : "expects an object";
|
|
655
|
-
case "relation":
|
|
656
|
-
case "asset":
|
|
657
|
-
case "blocks":
|
|
658
|
-
return null;
|
|
659
|
-
default: {
|
|
660
|
-
return null;
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
}
|
|
664
|
-
function checkConstraints(field, value) {
|
|
665
|
-
const out = [];
|
|
666
|
-
const rules = field.validation;
|
|
667
|
-
if (!rules) return out;
|
|
668
|
-
if (typeof value === "string") {
|
|
669
|
-
if (rules.min !== void 0 && value.length < rules.min) out.push(`is shorter than min length ${rules.min}`);
|
|
670
|
-
if (rules.max !== void 0 && value.length > rules.max) out.push(`is longer than max length ${rules.max}`);
|
|
671
|
-
if (rules.pattern !== void 0) {
|
|
672
|
-
let re;
|
|
673
|
-
try {
|
|
674
|
-
re = new RegExp(rules.pattern);
|
|
675
|
-
} catch {
|
|
676
|
-
re = void 0;
|
|
677
|
-
}
|
|
678
|
-
if (re && !re.test(value)) out.push(`does not match pattern ${rules.pattern}`);
|
|
679
|
-
}
|
|
680
|
-
} else if (typeof value === "number") {
|
|
681
|
-
if (rules.min !== void 0 && value < rules.min) out.push(`is less than min ${rules.min}`);
|
|
682
|
-
if (rules.max !== void 0 && value > rules.max) out.push(`is greater than max ${rules.max}`);
|
|
683
|
-
} else if (Array.isArray(value)) {
|
|
684
|
-
if (rules.min !== void 0 && value.length < rules.min) out.push(`has fewer than ${rules.min} items`);
|
|
685
|
-
if (rules.max !== void 0 && value.length > rules.max) out.push(`has more than ${rules.max} items`);
|
|
686
|
-
}
|
|
687
|
-
return out;
|
|
688
|
-
}
|
|
689
|
-
function fieldValueValidator(field) {
|
|
690
|
-
return (value) => {
|
|
691
|
-
if (isEmpty(value)) return true;
|
|
692
|
-
return checkScalarType(field, value) === null && checkConstraints(field, value).length === 0;
|
|
693
|
-
};
|
|
694
|
-
}
|
|
695
|
-
z.object({
|
|
696
|
-
/** Monotonic version number, starting at 1. */
|
|
697
|
-
version: z.number().int().positive(),
|
|
698
|
-
/** Full document snapshot at this version. */
|
|
699
|
-
snapshot: documentSchema,
|
|
700
|
-
/** When the snapshot was recorded (epoch ms). */
|
|
701
|
-
createdAt: z.number().int().nonnegative(),
|
|
702
|
-
/** Optional label, e.g. "published" or an author note. */
|
|
703
|
-
label: z.string().optional()
|
|
704
|
-
});
|
|
705
|
-
|
|
706
|
-
// ../cms-core/src/fixtures.ts
|
|
707
|
-
var assetCollection = {
|
|
708
|
-
id: "asset",
|
|
709
|
-
kind: "asset",
|
|
710
|
-
titleField: "filename",
|
|
711
|
-
fields: [
|
|
712
|
-
{ name: "filename", type: "text", validation: { required: true } },
|
|
713
|
-
{ name: "alt", type: "text", localized: true },
|
|
714
|
-
{ name: "url", type: "text", validation: { required: true } }
|
|
715
|
-
]
|
|
716
|
-
};
|
|
717
|
-
var authorCollection = {
|
|
718
|
-
id: "author",
|
|
719
|
-
kind: "document",
|
|
720
|
-
titleField: "name",
|
|
721
|
-
fields: [
|
|
722
|
-
{ name: "name", type: "text", validation: { required: true } },
|
|
723
|
-
{ name: "bio", type: "richText" }
|
|
724
|
-
]
|
|
725
|
-
};
|
|
726
|
-
var postCollection = {
|
|
727
|
-
id: "post",
|
|
728
|
-
kind: "document",
|
|
729
|
-
localized: true,
|
|
730
|
-
titleField: "title",
|
|
731
|
-
form: {
|
|
732
|
-
label: "Posts",
|
|
733
|
-
labelSingular: "Post",
|
|
734
|
-
groups: ["content", "meta"],
|
|
735
|
-
tabs: ["main", "seo"]
|
|
736
|
-
},
|
|
737
|
-
fields: [
|
|
738
|
-
{
|
|
739
|
-
name: "title",
|
|
740
|
-
type: "text",
|
|
741
|
-
localized: true,
|
|
742
|
-
validation: { required: true, min: 1, max: 120 },
|
|
743
|
-
form: { label: "Title", group: "content", tab: "main", order: 1 }
|
|
744
|
-
},
|
|
745
|
-
{
|
|
746
|
-
name: "slug",
|
|
747
|
-
type: "text",
|
|
748
|
-
validation: { required: true, pattern: "^[a-z0-9-]+$", unique: true },
|
|
749
|
-
form: { label: "Slug", group: "content", tab: "main", order: 2 }
|
|
750
|
-
},
|
|
751
|
-
{
|
|
752
|
-
name: "body",
|
|
753
|
-
type: "richText",
|
|
754
|
-
localized: true,
|
|
755
|
-
form: { label: "Body", group: "content", tab: "main", order: 3 }
|
|
756
|
-
},
|
|
757
|
-
{
|
|
758
|
-
name: "views",
|
|
759
|
-
type: "number",
|
|
760
|
-
validation: { min: 0 },
|
|
761
|
-
form: { label: "Views", group: "meta", tab: "main" }
|
|
762
|
-
},
|
|
763
|
-
{
|
|
764
|
-
name: "featured",
|
|
765
|
-
type: "boolean",
|
|
766
|
-
form: { label: "Featured", group: "meta", tab: "main" }
|
|
767
|
-
},
|
|
768
|
-
{
|
|
769
|
-
name: "publishedAt",
|
|
770
|
-
type: "date",
|
|
771
|
-
form: { label: "Published at", group: "meta", tab: "main" }
|
|
772
|
-
},
|
|
773
|
-
{
|
|
774
|
-
name: "status",
|
|
775
|
-
type: "select",
|
|
776
|
-
options: [
|
|
777
|
-
{ value: "news", label: "News" },
|
|
778
|
-
{ value: "guide", label: "Guide" }
|
|
779
|
-
],
|
|
780
|
-
form: { label: "Status", group: "meta", tab: "main" }
|
|
781
|
-
},
|
|
782
|
-
{
|
|
783
|
-
name: "author",
|
|
784
|
-
type: "relation",
|
|
785
|
-
target: "author",
|
|
786
|
-
cardinality: "one",
|
|
787
|
-
validation: { required: true },
|
|
788
|
-
form: { label: "Author", group: "meta", tab: "main" }
|
|
789
|
-
},
|
|
790
|
-
{
|
|
791
|
-
name: "related",
|
|
792
|
-
type: "relation",
|
|
793
|
-
target: "post",
|
|
794
|
-
cardinality: "many",
|
|
795
|
-
form: { label: "Related posts", group: "meta", tab: "main" }
|
|
796
|
-
},
|
|
797
|
-
{
|
|
798
|
-
name: "cover",
|
|
799
|
-
type: "asset",
|
|
800
|
-
cardinality: "one",
|
|
801
|
-
accept: ["image"],
|
|
802
|
-
form: { label: "Cover image", group: "content", tab: "main" }
|
|
803
|
-
}
|
|
804
|
-
]
|
|
805
|
-
};
|
|
806
|
-
var pageCollection = {
|
|
807
|
-
id: "page",
|
|
808
|
-
kind: "document",
|
|
809
|
-
titleField: "name",
|
|
810
|
-
form: {
|
|
811
|
-
label: "Pages",
|
|
812
|
-
labelSingular: "Page",
|
|
813
|
-
tabs: ["main", "canvas", "seo"]
|
|
814
|
-
},
|
|
815
|
-
fields: [
|
|
816
|
-
{
|
|
817
|
-
name: "name",
|
|
818
|
-
type: "text",
|
|
819
|
-
validation: { required: true },
|
|
820
|
-
form: { tab: "main" }
|
|
821
|
-
},
|
|
822
|
-
{
|
|
823
|
-
name: "body",
|
|
824
|
-
type: "blocks",
|
|
825
|
-
allow: [
|
|
826
|
-
"base.primitives.Heading",
|
|
827
|
-
"base.primitives.Text",
|
|
828
|
-
"base.primitives.Image",
|
|
829
|
-
"base.primitives.Button",
|
|
830
|
-
"base.primitives.Stack",
|
|
831
|
-
"project.MarketingHero",
|
|
832
|
-
"project.FeatureCard",
|
|
833
|
-
"project.PricingCallout"
|
|
834
|
-
],
|
|
835
|
-
cardinality: "many",
|
|
836
|
-
form: { label: "Canvas", tab: "canvas" }
|
|
837
|
-
},
|
|
838
|
-
{ name: "layoutId", type: "text", form: { tab: "main" } },
|
|
839
|
-
{ name: "seoTitle", type: "text", form: { tab: "seo" } },
|
|
840
|
-
{ name: "seoDescription", type: "text", form: { tab: "seo" } },
|
|
841
|
-
{ name: "seoCanonical", type: "text", form: { tab: "seo" } },
|
|
842
|
-
{ name: "seoOgTitle", type: "text", form: { tab: "seo" } },
|
|
843
|
-
{ name: "seoOgImage", type: "text", form: { tab: "seo" } },
|
|
844
|
-
{ name: "seoNoindex", type: "boolean", form: { tab: "seo" } }
|
|
845
|
-
]
|
|
846
|
-
};
|
|
847
|
-
var sampleCollections = [
|
|
848
|
-
assetCollection,
|
|
849
|
-
authorCollection,
|
|
850
|
-
postCollection,
|
|
851
|
-
pageCollection
|
|
852
|
-
];
|
|
853
|
-
var sampleFilterableFields = {
|
|
854
|
-
author: ["name"],
|
|
855
|
-
post: ["slug", "views", "featured", "publishedAt", "status", "author"]
|
|
856
|
-
};
|
|
857
|
-
function withSampleFilterable(collection) {
|
|
858
|
-
const filterable = new Set(sampleFilterableFields[collection.id] ?? []);
|
|
859
|
-
if (filterable.size === 0) return collection;
|
|
860
|
-
return {
|
|
861
|
-
...collection,
|
|
862
|
-
fields: collection.fields.map(
|
|
863
|
-
(field) => filterable.has(field.name) ? { ...field, filterable: true } : field
|
|
864
|
-
)
|
|
865
|
-
};
|
|
866
|
-
}
|
|
867
|
-
sampleCollections.map(withSampleFilterable);
|
|
868
|
-
var coverAsset = {
|
|
869
|
-
collection: "asset",
|
|
870
|
-
id: "asset-cover",
|
|
871
|
-
values: { filename: "cover.png", url: "https://cdn/cover.png" },
|
|
872
|
-
localized: { en: { alt: "A cover" }, de: { alt: "Ein Titelbild" } },
|
|
873
|
-
defaultLocale: "en"
|
|
874
|
-
};
|
|
875
|
-
var libraryCoverAsset = {
|
|
876
|
-
collection: "asset",
|
|
877
|
-
id: "asset_cover",
|
|
878
|
-
values: { filename: "cover.jpg", url: "https://cdn/cover.jpg" },
|
|
879
|
-
localized: { en: { alt: "Cover image" }, de: { alt: "Titelbild" } },
|
|
880
|
-
defaultLocale: "en"
|
|
881
|
-
};
|
|
882
|
-
var authorAda = {
|
|
883
|
-
collection: "author",
|
|
884
|
-
id: "author-ada",
|
|
885
|
-
values: { name: "Ada Lovelace", bio: "Pioneer." }
|
|
886
|
-
};
|
|
887
|
-
var validPost = {
|
|
888
|
-
collection: "post",
|
|
889
|
-
id: "post-hello",
|
|
890
|
-
values: {
|
|
891
|
-
slug: "hello-world",
|
|
892
|
-
views: 10,
|
|
893
|
-
featured: true,
|
|
894
|
-
publishedAt: "2026-01-02T00:00:00.000Z",
|
|
895
|
-
status: "news",
|
|
896
|
-
author: { collection: "author", id: "author-ada" },
|
|
897
|
-
related: [],
|
|
898
|
-
cover: "asset-cover"
|
|
899
|
-
},
|
|
900
|
-
localized: {
|
|
901
|
-
en: { title: "Hello World", body: "<p>Hi</p>" },
|
|
902
|
-
de: { title: "Hallo Welt", body: "<p>Hallo</p>" }
|
|
903
|
-
},
|
|
904
|
-
defaultLocale: "en"
|
|
905
|
-
};
|
|
906
|
-
var partiallyLocalizedPost = {
|
|
907
|
-
collection: "post",
|
|
908
|
-
id: "post-fallback",
|
|
909
|
-
values: {
|
|
910
|
-
slug: "fallback",
|
|
911
|
-
author: { collection: "author", id: "author-ada" }
|
|
912
|
-
},
|
|
913
|
-
localized: {
|
|
914
|
-
en: { title: "Only English", body: "<p>en</p>" }
|
|
915
|
-
// no `de` variant — `de` requests must fall back to `en`
|
|
916
|
-
},
|
|
917
|
-
defaultLocale: "en"
|
|
918
|
-
};
|
|
919
|
-
var samplePageHome = {
|
|
920
|
-
collection: "page",
|
|
921
|
-
id: "home",
|
|
922
|
-
values: {
|
|
923
|
-
name: "Home",
|
|
924
|
-
layoutId: "layout.main",
|
|
925
|
-
seoTitle: "Welcome to Elytra",
|
|
926
|
-
seoDescription: "A code-first website builder.",
|
|
927
|
-
body: [
|
|
928
|
-
{
|
|
929
|
-
id: "p-home-heading",
|
|
930
|
-
componentId: "base.primitives.Heading",
|
|
931
|
-
props: {
|
|
932
|
-
level: { kind: "static", value: 1 },
|
|
933
|
-
text: { kind: "static", value: "Welcome to Elytra" }
|
|
934
|
-
}
|
|
935
|
-
},
|
|
936
|
-
{
|
|
937
|
-
id: "p-home-intro",
|
|
938
|
-
componentId: "base.primitives.Text",
|
|
939
|
-
props: { value: { kind: "static", value: "A code-first website builder." } }
|
|
940
|
-
}
|
|
941
|
-
]
|
|
942
|
-
},
|
|
943
|
-
defaultLocale: "en"
|
|
944
|
-
};
|
|
945
|
-
var sampleDocuments = [
|
|
946
|
-
coverAsset,
|
|
947
|
-
authorAda,
|
|
948
|
-
validPost,
|
|
949
|
-
partiallyLocalizedPost,
|
|
950
|
-
libraryCoverAsset,
|
|
951
|
-
samplePageHome
|
|
952
|
-
];
|
|
953
|
-
sampleDocuments.map(
|
|
954
|
-
(doc) => documentKey(refOf(doc))
|
|
955
|
-
);
|
|
956
|
-
|
|
957
|
-
// ../component-registry/src/manifest.ts
|
|
958
|
-
function defineComponent(manifest) {
|
|
959
|
-
return manifest;
|
|
960
|
-
}
|
|
961
|
-
function manifestPropField(manifest, name) {
|
|
962
|
-
const field = manifest.props[name];
|
|
963
|
-
return field ? { ...field, name } : void 0;
|
|
964
|
-
}
|
|
965
|
-
function manifestSlots(manifest) {
|
|
966
|
-
const compositionValueProp = manifest.composition?.valueProp;
|
|
967
|
-
const fromBlocksProps = [];
|
|
968
|
-
for (const [name, field] of Object.entries(manifest.props)) {
|
|
969
|
-
if (field.type !== "blocks") continue;
|
|
970
|
-
if (name === compositionValueProp) continue;
|
|
971
|
-
fromBlocksProps.push({
|
|
972
|
-
name,
|
|
973
|
-
...field.form?.label ? { label: field.form.label } : {},
|
|
974
|
-
...field.validation?.required ? { required: true } : {},
|
|
975
|
-
...field.allow ? { allow: field.allow } : {}
|
|
976
|
-
});
|
|
977
|
-
}
|
|
978
|
-
return fromBlocksProps.length === 0 ? manifest.slots : [...manifest.slots, ...fromBlocksProps];
|
|
979
|
-
}
|
|
980
|
-
function propValueValidator(manifest, name) {
|
|
981
|
-
const field = manifestPropField(manifest, name);
|
|
982
|
-
return field ? fieldValueValidator(field) : void 0;
|
|
983
|
-
}
|
|
984
|
-
function namespaceOf(id) {
|
|
985
|
-
if (id.startsWith("base.primitives.")) return "base.primitives";
|
|
986
|
-
const first = id.split(".")[0];
|
|
987
|
-
return first ?? "";
|
|
988
|
-
}
|
|
989
|
-
function isPrimitive(id) {
|
|
990
|
-
return id.startsWith("base.primitives.");
|
|
991
|
-
}
|
|
992
|
-
function toManifestView(manifest) {
|
|
993
|
-
const props = {};
|
|
994
|
-
for (const [name, field] of Object.entries(manifest.props)) {
|
|
995
|
-
props[name] = {
|
|
996
|
-
// EC-190: props are static; the binding path retires in slice 5. Until then
|
|
997
|
-
// every prop reports bindable so the graph validator's binding branch stays
|
|
998
|
-
// inert (no false `binding-not-allowed`). `validate` is the field-def value
|
|
999
|
-
// validator — the SAME check the CMS uses for a document field value.
|
|
1000
|
-
bindable: true,
|
|
1001
|
-
validate: fieldValueValidator({ ...field})
|
|
1002
|
-
};
|
|
1003
|
-
}
|
|
1004
|
-
return {
|
|
1005
|
-
id: manifest.id,
|
|
1006
|
-
props,
|
|
1007
|
-
// EC-191: carry canvas-eligibility so `validateCompositionValue` can reject a
|
|
1008
|
-
// server-only component placed in a composition.
|
|
1009
|
-
...manifest.clientRenderable === false ? { clientRenderable: false } : {},
|
|
1010
|
-
// EC-186: carry the slot's placement vocabulary (`allow`) through to the
|
|
1011
|
-
// graph validator so it can enforce `forbidden-component` — previously
|
|
1012
|
-
// dropped here, leaving slot `allow` declared-but-unenforced. EC-190:
|
|
1013
|
-
// `manifestSlots` folds in `blocks`-type props as slots, so a blocks prop's
|
|
1014
|
-
// `allow` leash is enforced through the same path.
|
|
1015
|
-
slots: manifestSlots(manifest).map((s) => ({
|
|
1016
|
-
name: s.name,
|
|
1017
|
-
required: s.required,
|
|
1018
|
-
...s.allow ? { allow: s.allow } : {}
|
|
1019
|
-
}))
|
|
1020
|
-
};
|
|
1021
|
-
}
|
|
1022
|
-
var jsonValueSchema = z.lazy(
|
|
1023
|
-
() => z.union([
|
|
1024
|
-
z.string(),
|
|
1025
|
-
z.number(),
|
|
1026
|
-
z.boolean(),
|
|
1027
|
-
z.null(),
|
|
1028
|
-
z.array(jsonValueSchema),
|
|
1029
|
-
z.record(z.string(), jsonValueSchema)
|
|
1030
|
-
])
|
|
1031
|
-
);
|
|
1032
|
-
var idSchema = z.string().min(1);
|
|
1033
|
-
var COMPONENT_ID_PATTERN = /^(base\.primitives|project)\.[A-Za-z][A-Za-z0-9]*(\.[A-Za-z][A-Za-z0-9]*)*$/;
|
|
1034
|
-
function isValidComponentId(componentId) {
|
|
1035
|
-
return COMPONENT_ID_PATTERN.test(componentId);
|
|
1036
|
-
}
|
|
1037
|
-
var DEFAULT_SLOT = "children";
|
|
1038
|
-
var OUTLET_SLOT = "outlet";
|
|
1039
|
-
var bindingModeSchema = z.enum(["value", "object", "spread", "repeaterItem", "condition"]);
|
|
1040
|
-
var bindingReferenceSchema = z.object({
|
|
1041
|
-
/** Id of a data source (REST / CMS query / Convex query). */
|
|
1042
|
-
sourceId: idSchema,
|
|
1043
|
-
/** Stable path token (JSON Pointer / JSONPath) that survives UI label changes. */
|
|
1044
|
-
token: z.string().min(1),
|
|
1045
|
-
mode: bindingModeSchema
|
|
1046
|
-
});
|
|
1047
|
-
var conditionOperatorSchema = z.enum([
|
|
1048
|
-
"truthy",
|
|
1049
|
-
"exists",
|
|
1050
|
-
"eq",
|
|
1051
|
-
"neq",
|
|
1052
|
-
"gt",
|
|
1053
|
-
"gte",
|
|
1054
|
-
"lt",
|
|
1055
|
-
"lte"
|
|
1056
|
-
]);
|
|
1057
|
-
var conditionSchema = z.object({
|
|
1058
|
-
source: bindingReferenceSchema,
|
|
1059
|
-
operator: conditionOperatorSchema,
|
|
1060
|
-
/** Right-hand comparison value for binary operators (omitted for truthy/exists). */
|
|
1061
|
-
value: jsonValueSchema.optional()
|
|
1062
|
-
});
|
|
1063
|
-
var propValueSchema = z.discriminatedUnion("kind", [
|
|
1064
|
-
z.object({ kind: z.literal("static"), value: jsonValueSchema }),
|
|
1065
|
-
z.object({ kind: z.literal("binding"), binding: bindingReferenceSchema })
|
|
1066
|
-
]);
|
|
1067
|
-
var componentNodeSchema = z.lazy(
|
|
1068
|
-
() => z.object({
|
|
1069
|
-
id: idSchema,
|
|
1070
|
-
componentId: idSchema,
|
|
1071
|
-
props: z.record(z.string(), propValueSchema).optional(),
|
|
1072
|
-
slots: z.record(z.string(), z.array(componentNodeSchema)).optional(),
|
|
1073
|
-
condition: conditionSchema.optional()
|
|
1074
|
-
})
|
|
1075
|
-
);
|
|
1076
|
-
var layoutSchema = z.object({
|
|
1077
|
-
id: idSchema,
|
|
1078
|
-
name: z.string().min(1),
|
|
1079
|
-
root: componentNodeSchema
|
|
1080
|
-
});
|
|
1081
|
-
var PROJECT_GRAPH_SCHEMA_VERSION = 2;
|
|
1082
|
-
var projectGraphSchema = z.object({
|
|
1083
|
-
id: idSchema,
|
|
1084
|
-
schemaVersion: z.number().int().nonnegative(),
|
|
1085
|
-
metadata: z.object({ name: z.string().optional() }).optional(),
|
|
1086
|
-
/** Link to project settings owned by studio-core. */
|
|
1087
|
-
settingsRef: idSchema.optional(),
|
|
1088
|
-
layouts: z.array(layoutSchema).default([])
|
|
1089
|
-
});
|
|
1090
|
-
var STACK_COMPONENT_ID = "base.primitives.Stack";
|
|
1091
|
-
var GRID_COMPONENT_ID = "base.primitives.Grid";
|
|
1092
|
-
var SPACING_TOKEN_NAMES = ["px", "1", "2", "3", "4", "6", "8"];
|
|
1093
|
-
var spacingTokenNameSchema = z.enum(SPACING_TOKEN_NAMES);
|
|
1094
|
-
var SPACING_TOKEN_CSS = {
|
|
1095
|
-
px: "1px",
|
|
1096
|
-
"1": "0.25rem",
|
|
1097
|
-
"2": "0.5rem",
|
|
1098
|
-
"3": "0.75rem",
|
|
1099
|
-
"4": "1rem",
|
|
1100
|
-
"6": "1.5rem",
|
|
1101
|
-
"8": "2rem"
|
|
1102
|
-
};
|
|
1103
|
-
function isSpacingTokenName(value) {
|
|
1104
|
-
return typeof value === "string" && SPACING_TOKEN_NAMES.includes(value);
|
|
1105
|
-
}
|
|
1106
|
-
function spacingTokenCss(name) {
|
|
1107
|
-
return isSpacingTokenName(name) ? SPACING_TOKEN_CSS[name] : void 0;
|
|
1108
|
-
}
|
|
1109
|
-
z.boolean();
|
|
1110
|
-
var STACK_DIRECTIONS = ["vertical", "horizontal"];
|
|
1111
|
-
z.enum(STACK_DIRECTIONS);
|
|
1112
|
-
var CONTAINER_ALIGNMENTS = ["start", "center", "end", "stretch"];
|
|
1113
|
-
z.enum(CONTAINER_ALIGNMENTS);
|
|
1114
|
-
var GRID_MIN_COLUMNS = 1;
|
|
1115
|
-
var GRID_MAX_COLUMNS = 12;
|
|
1116
|
-
z.number().int().min(GRID_MIN_COLUMNS).max(GRID_MAX_COLUMNS);
|
|
1117
|
-
z.union([
|
|
1118
|
-
spacingTokenNameSchema,
|
|
1119
|
-
z.number().int().nonnegative()
|
|
1120
|
-
]);
|
|
1121
|
-
function staticProps(values) {
|
|
1122
|
-
const entries = Object.entries(values).filter(([, v]) => v !== void 0);
|
|
1123
|
-
if (entries.length === 0) return void 0;
|
|
1124
|
-
return Object.fromEntries(
|
|
1125
|
-
entries.map(([name, value]) => [name, { kind: "static", value }])
|
|
1126
|
-
);
|
|
1127
|
-
}
|
|
1128
|
-
function stackContainerNode(id, options, children) {
|
|
1129
|
-
const props = staticProps({
|
|
1130
|
-
direction: options.direction,
|
|
1131
|
-
gap: options.gap,
|
|
1132
|
-
align: options.align
|
|
1133
|
-
});
|
|
1134
|
-
return {
|
|
1135
|
-
id,
|
|
1136
|
-
componentId: STACK_COMPONENT_ID,
|
|
1137
|
-
...props ? { props } : {},
|
|
1138
|
-
slots: { [DEFAULT_SLOT]: children }
|
|
1139
|
-
};
|
|
1140
|
-
}
|
|
1141
|
-
function gridContainerNode(id, options, children) {
|
|
1142
|
-
const props = staticProps({
|
|
1143
|
-
columns: options.columns,
|
|
1144
|
-
gap: options.gap,
|
|
1145
|
-
align: options.align,
|
|
1146
|
-
stackOnMobile: options.stackOnMobile
|
|
1147
|
-
});
|
|
1148
|
-
return {
|
|
1149
|
-
id,
|
|
1150
|
-
componentId: GRID_COMPONENT_ID,
|
|
1151
|
-
...props ? { props } : {},
|
|
1152
|
-
slots: { [DEFAULT_SLOT]: children }
|
|
1153
|
-
};
|
|
1154
|
-
}
|
|
1155
|
-
var issueCodeSchema = z.enum([
|
|
1156
|
-
"invalid-component-name",
|
|
1157
|
-
"unknown-component",
|
|
1158
|
-
"duplicate-node-id",
|
|
1159
|
-
"unknown-prop",
|
|
1160
|
-
"invalid-prop",
|
|
1161
|
-
"binding-not-allowed",
|
|
1162
|
-
"unresolved-binding",
|
|
1163
|
-
"missing-required-slot",
|
|
1164
|
-
"unknown-slot",
|
|
1165
|
-
// Placement constraints (EC-186): a component appears where its slot's `allow`
|
|
1166
|
-
// vocabulary — or a composition field's `allow` — forbids it.
|
|
1167
|
-
"forbidden-component",
|
|
1168
|
-
// Canvas-eligibility (EC-191): a server-only (RSC / non-client-renderable)
|
|
1169
|
-
// component was placed in an editor composition, which is client-rendered.
|
|
1170
|
-
// Such components belong in dev frames (page.tsx / layout.tsx), not the canvas.
|
|
1171
|
-
"server-only-component",
|
|
1172
|
-
"unknown-layout",
|
|
1173
|
-
"route-conflict",
|
|
1174
|
-
// Layout containers (EC-161): malformed Stack/Grid canonical props.
|
|
1175
|
-
"invalid-container-direction",
|
|
1176
|
-
"invalid-container-alignment",
|
|
1177
|
-
"invalid-container-gap",
|
|
1178
|
-
"invalid-container-columns",
|
|
1179
|
-
// Responsive defaults (EC-163): malformed Grid stack-on-mobile override.
|
|
1180
|
-
"invalid-container-responsive"
|
|
1181
|
-
]);
|
|
1182
|
-
var issueSeveritySchema = z.enum(["error", "warning"]);
|
|
1183
|
-
z.object({
|
|
1184
|
-
code: issueCodeSchema,
|
|
1185
|
-
severity: issueSeveritySchema,
|
|
1186
|
-
message: z.string(),
|
|
1187
|
-
/** JSON path into the graph, e.g. ['pages', 0, 'root', 'slots', 'children', 1]. */
|
|
1188
|
-
path: z.array(z.union([z.string(), z.number()])),
|
|
1189
|
-
/** The offending node id, when applicable. */
|
|
1190
|
-
nodeId: idSchema.optional(),
|
|
1191
|
-
meta: z.record(z.string(), jsonValueSchema).optional()
|
|
1192
|
-
});
|
|
1193
|
-
|
|
1194
|
-
// ../project-graph/src/serialize.ts
|
|
1195
|
-
function parseProjectGraph(input) {
|
|
1196
|
-
let json;
|
|
1197
|
-
try {
|
|
1198
|
-
json = typeof input === "string" ? JSON.parse(input) : input;
|
|
1199
|
-
} catch {
|
|
1200
|
-
json = void 0;
|
|
1201
|
-
}
|
|
1202
|
-
const result = projectGraphSchema.safeParse(json);
|
|
1203
|
-
return result.success ? { ok: true, graph: result.data } : { ok: false, error: result.error };
|
|
1204
|
-
}
|
|
1205
|
-
var textNode = (id, value) => ({
|
|
1206
|
-
id,
|
|
1207
|
-
componentId: "base.primitives.Text",
|
|
1208
|
-
props: { value: { kind: "static", value } }
|
|
1209
|
-
});
|
|
1210
|
-
function containerGraph(id, root) {
|
|
1211
|
-
return {
|
|
1212
|
-
id,
|
|
1213
|
-
schemaVersion: PROJECT_GRAPH_SCHEMA_VERSION,
|
|
1214
|
-
layouts: [{ id: `${id}.layout`, name: "Layout", root }]
|
|
1215
|
-
};
|
|
1216
|
-
}
|
|
1217
|
-
containerGraph(
|
|
1218
|
-
"proj-containers",
|
|
1219
|
-
stackContainerNode("n-root", { direction: "vertical", gap: "4", align: "start" }, [
|
|
1220
|
-
gridContainerNode("n-grid", { columns: 2, gap: "2", align: "stretch" }, [
|
|
1221
|
-
textNode("n-cell-1", "Cell one"),
|
|
1222
|
-
{
|
|
1223
|
-
id: "n-cell-2",
|
|
1224
|
-
componentId: "base.primitives.Heading",
|
|
1225
|
-
props: { level: { kind: "static", value: 2 }, text: { kind: "static", value: "Cell two" } }
|
|
1226
|
-
}
|
|
1227
|
-
]),
|
|
1228
|
-
stackContainerNode("n-row", { direction: "horizontal", gap: "2", align: "center" }, [
|
|
1229
|
-
textNode("n-row-a", "Left"),
|
|
1230
|
-
textNode("n-row-b", "Right")
|
|
1231
|
-
])
|
|
1232
|
-
])
|
|
1233
|
-
);
|
|
1234
|
-
containerGraph(
|
|
1235
|
-
"proj-soup",
|
|
1236
|
-
stackContainerNode("n-root", { gap: "4" }, [
|
|
1237
|
-
stackContainerNode("n-wrap-1", { gap: "2" }, [
|
|
1238
|
-
stackContainerNode("n-wrap-2", {}, [
|
|
1239
|
-
gridContainerNode("n-wrap-3", { columns: 2 }, [textNode("n-deep", "Deeply wrapped")])
|
|
1240
|
-
])
|
|
1241
|
-
]),
|
|
1242
|
-
gridContainerNode("n-empty-grid", { columns: 3, gap: "2" }, []),
|
|
1243
|
-
textNode("n-plain", "Plain sibling")
|
|
1244
|
-
])
|
|
1245
|
-
);
|
|
1246
|
-
|
|
1247
|
-
// ../component-registry/src/registry.ts
|
|
1248
|
-
var ComponentRegistry = class {
|
|
1249
|
-
byId = /* @__PURE__ */ new Map();
|
|
1250
|
-
viewCache = /* @__PURE__ */ new Map();
|
|
1251
|
-
issues = [];
|
|
1252
|
-
constructor(manifests = []) {
|
|
1253
|
-
for (const manifest of manifests) this.register(manifest);
|
|
1254
|
-
}
|
|
1255
|
-
register(manifest) {
|
|
1256
|
-
if (!isValidComponentId(manifest.id)) {
|
|
1257
|
-
this.issues.push({
|
|
1258
|
-
code: "invalid-id",
|
|
1259
|
-
componentId: manifest.id,
|
|
1260
|
-
message: `Component id "${manifest.id}" is not namespaced under base.primitives.* or project.*.`
|
|
1261
|
-
});
|
|
1262
|
-
}
|
|
1263
|
-
if (this.byId.has(manifest.id)) {
|
|
1264
|
-
this.issues.push({
|
|
1265
|
-
code: "duplicate-id",
|
|
1266
|
-
componentId: manifest.id,
|
|
1267
|
-
message: `Duplicate component id "${manifest.id}" \u2014 keeping the first registration.`
|
|
1268
|
-
});
|
|
1269
|
-
return;
|
|
1270
|
-
}
|
|
1271
|
-
this.byId.set(manifest.id, manifest);
|
|
1272
|
-
}
|
|
1273
|
-
has(id) {
|
|
1274
|
-
return this.byId.has(id);
|
|
1275
|
-
}
|
|
1276
|
-
getManifest(id) {
|
|
1277
|
-
return this.byId.get(id);
|
|
1278
|
-
}
|
|
1279
|
-
/** Required lookup — throws when the component is not registered. */
|
|
1280
|
-
require(id) {
|
|
1281
|
-
const manifest = this.byId.get(id);
|
|
1282
|
-
if (!manifest) throw new Error(`Unknown component "${id}".`);
|
|
1283
|
-
return manifest;
|
|
1284
|
-
}
|
|
1285
|
-
list(filter = {}) {
|
|
1286
|
-
const q = filter.query?.toLowerCase();
|
|
1287
|
-
return [...this.byId.values()].filter((m) => {
|
|
1288
|
-
if (filter.namespace && namespaceOf(m.id) !== filter.namespace) return false;
|
|
1289
|
-
if (filter.category && m.category !== filter.category) return false;
|
|
1290
|
-
if (q) {
|
|
1291
|
-
const hay = `${m.id} ${m.title ?? ""} ${m.category ?? ""}`.toLowerCase();
|
|
1292
|
-
if (!hay.includes(q)) return false;
|
|
1293
|
-
}
|
|
1294
|
-
return true;
|
|
1295
|
-
});
|
|
1296
|
-
}
|
|
1297
|
-
/** Platform primitives (`base.primitives.*`). */
|
|
1298
|
-
primitives() {
|
|
1299
|
-
return this.list().filter((m) => isPrimitive(m.id));
|
|
1300
|
-
}
|
|
1301
|
-
/** Project-level components (everything not under `base.primitives.*`). */
|
|
1302
|
-
projectComponents() {
|
|
1303
|
-
return this.list().filter((m) => !isPrimitive(m.id));
|
|
1304
|
-
}
|
|
1305
|
-
get size() {
|
|
1306
|
-
return this.byId.size;
|
|
1307
|
-
}
|
|
1308
|
-
/** A `ComponentLookup` view for the project-graph validator. */
|
|
1309
|
-
get lookup() {
|
|
1310
|
-
return {
|
|
1311
|
-
has: (id) => this.byId.has(id),
|
|
1312
|
-
get: (id) => {
|
|
1313
|
-
if (!this.byId.has(id)) return void 0;
|
|
1314
|
-
let view = this.viewCache.get(id);
|
|
1315
|
-
if (!view) {
|
|
1316
|
-
view = toManifestView(this.byId.get(id));
|
|
1317
|
-
this.viewCache.set(id, view);
|
|
1318
|
-
}
|
|
1319
|
-
return view;
|
|
1320
|
-
}
|
|
1321
|
-
};
|
|
1322
|
-
}
|
|
1323
|
-
};
|
|
1324
|
-
|
|
1325
|
-
// ../runtime-renderer/src/context.ts
|
|
1326
|
-
function getImplementation(implementations, id) {
|
|
1327
|
-
if (implementations instanceof Map) return implementations.get(id);
|
|
1328
|
-
return implementations[id];
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
// ../runtime-renderer/src/condition.ts
|
|
1332
|
-
function evaluateCondition(condition, resolveBinding, item) {
|
|
1333
|
-
let left;
|
|
1334
|
-
try {
|
|
1335
|
-
left = resolveBinding(condition.source, item);
|
|
1336
|
-
} catch {
|
|
1337
|
-
return false;
|
|
1338
|
-
}
|
|
1339
|
-
switch (condition.operator) {
|
|
1340
|
-
case "truthy":
|
|
1341
|
-
return Boolean(left);
|
|
1342
|
-
case "exists":
|
|
1343
|
-
return left !== void 0 && left !== null;
|
|
1344
|
-
case "eq":
|
|
1345
|
-
return left === condition.value;
|
|
1346
|
-
case "neq":
|
|
1347
|
-
return left !== condition.value;
|
|
1348
|
-
case "gt":
|
|
1349
|
-
return compare(left, condition.value, (a, b) => a > b);
|
|
1350
|
-
case "gte":
|
|
1351
|
-
return compare(left, condition.value, (a, b) => a >= b);
|
|
1352
|
-
case "lt":
|
|
1353
|
-
return compare(left, condition.value, (a, b) => a < b);
|
|
1354
|
-
case "lte":
|
|
1355
|
-
return compare(left, condition.value, (a, b) => a <= b);
|
|
1356
|
-
default: {
|
|
1357
|
-
const _never = condition.operator;
|
|
1358
|
-
return Boolean(_never);
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
}
|
|
1362
|
-
function compare(left, right, op) {
|
|
1363
|
-
if (typeof left !== "number" || typeof right !== "number") return false;
|
|
1364
|
-
return op(left, right);
|
|
1365
|
-
}
|
|
1366
|
-
var ContentClientContext = createContext(null);
|
|
1367
|
-
function ContentClientProvider({
|
|
1368
|
-
client,
|
|
1369
|
-
children
|
|
1370
|
-
}) {
|
|
1371
|
-
return createElement(ContentClientContext.Provider, { value: client }, children);
|
|
1372
|
-
}
|
|
1373
|
-
function MissingComponentFallback(props) {
|
|
1374
|
-
return /* @__PURE__ */ jsx(
|
|
1375
|
-
"div",
|
|
1376
|
-
{
|
|
1377
|
-
"data-ec-fallback": "missing-component",
|
|
1378
|
-
"data-ec-node-id": props.nodeId,
|
|
1379
|
-
"data-ec-component-id": props.componentId,
|
|
1380
|
-
children: `Missing component "${props.componentId}" (node ${props.nodeId})`
|
|
1381
|
-
}
|
|
1382
|
-
);
|
|
1383
|
-
}
|
|
1384
|
-
function MissingSlotPlaceholder(props) {
|
|
1385
|
-
return /* @__PURE__ */ jsx("div", { "data-ec-fallback": "missing-slot", "data-ec-node-id": props.nodeId, "data-ec-slot": props.slot, children: `Missing required slot "${props.slot}" (node ${props.nodeId})` });
|
|
1386
|
-
}
|
|
1387
|
-
function RenderErrorFallback(props) {
|
|
1388
|
-
return /* @__PURE__ */ jsx("div", { "data-ec-fallback": "render-error", "data-ec-node-id": props.nodeId, children: props.message ? `Render error in node ${props.nodeId}: ${props.message}` : `Render error in node ${props.nodeId}` });
|
|
1389
|
-
}
|
|
1390
|
-
var RenderErrorBoundary = class extends Component {
|
|
1391
|
-
constructor(props) {
|
|
1392
|
-
super(props);
|
|
1393
|
-
this.state = { error: null };
|
|
1394
|
-
}
|
|
1395
|
-
static getDerivedStateFromError(error) {
|
|
1396
|
-
return { error };
|
|
1397
|
-
}
|
|
1398
|
-
componentDidCatch(_error, _info) {
|
|
1399
|
-
}
|
|
1400
|
-
render() {
|
|
1401
|
-
if (this.state.error) {
|
|
1402
|
-
return /* @__PURE__ */ jsx(RenderErrorFallback, { nodeId: this.props.nodeId, message: this.state.error.message });
|
|
1403
|
-
}
|
|
1404
|
-
return this.props.children;
|
|
1405
|
-
}
|
|
1406
|
-
};
|
|
1407
|
-
function renderNode(node, ctx) {
|
|
1408
|
-
if (node.condition && !evaluateCondition(node.condition, ctx.resolveBinding, ctx.item)) {
|
|
1409
|
-
return null;
|
|
1410
|
-
}
|
|
1411
|
-
const manifest = ctx.registry.getManifest(node.componentId);
|
|
1412
|
-
const implementation = getImplementation(ctx.implementations, node.componentId);
|
|
1413
|
-
if (!manifest || !implementation) {
|
|
1414
|
-
return decorate(
|
|
1415
|
-
ctx,
|
|
1416
|
-
node,
|
|
1417
|
-
/* @__PURE__ */ jsx(MissingComponentFallback, { nodeId: node.id, componentId: node.componentId })
|
|
1418
|
-
);
|
|
1419
|
-
}
|
|
1420
|
-
let resolved;
|
|
1421
|
-
try {
|
|
1422
|
-
resolved = resolveRender(node, manifest, ctx);
|
|
1423
|
-
} catch (error) {
|
|
1424
|
-
return decorate(ctx, node, /* @__PURE__ */ jsx(RenderErrorFallback, { nodeId: node.id, message: messageOf(error) }));
|
|
1425
|
-
}
|
|
1426
|
-
const element = createElement(
|
|
1427
|
-
implementation,
|
|
1428
|
-
{ ...resolved.props, ...resolved.slotProps, key: node.id },
|
|
1429
|
-
resolved.children
|
|
1430
|
-
);
|
|
1431
|
-
return decorate(
|
|
1432
|
-
ctx,
|
|
1433
|
-
node,
|
|
1434
|
-
/* @__PURE__ */ jsx(RenderErrorBoundary, { nodeId: node.id, children: element }, node.id)
|
|
1435
|
-
);
|
|
1436
|
-
}
|
|
1437
|
-
function decorate(ctx, node, rendered) {
|
|
1438
|
-
return ctx.decorateNode ? ctx.decorateNode(node, rendered) : rendered;
|
|
1439
|
-
}
|
|
1440
|
-
function createEmbedRenderer(ctx) {
|
|
1441
|
-
return (node) => renderComposition(node, ctx);
|
|
1442
|
-
}
|
|
1443
|
-
function manifestConsumesEmbeds(manifest) {
|
|
1444
|
-
return Object.values(manifest.props).some((field) => field.type === "richText");
|
|
1445
|
-
}
|
|
1446
|
-
function createBlocksRenderer(ctx) {
|
|
1447
|
-
return (value) => renderComposition(value, ctx);
|
|
1448
|
-
}
|
|
1449
|
-
function manifestConsumesRelations(manifest) {
|
|
1450
|
-
return Object.values(manifest.props).some((field) => field.type === "relation");
|
|
1451
|
-
}
|
|
1452
|
-
function relationRefOf(value) {
|
|
1453
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
1454
|
-
const { collection, id } = value;
|
|
1455
|
-
return typeof collection === "string" && collection !== "" && typeof id === "string" && id !== "" ? { collection, id } : null;
|
|
1456
|
-
}
|
|
1457
|
-
function relationKey(ref) {
|
|
1458
|
-
return `${ref.collection}:${ref.id}`;
|
|
1459
|
-
}
|
|
1460
|
-
function nodeRelationKeys(node, manifest) {
|
|
1461
|
-
const keys = [];
|
|
1462
|
-
for (const [name, value] of Object.entries(node.props ?? {})) {
|
|
1463
|
-
if (value.kind !== "static") continue;
|
|
1464
|
-
const spec = manifest.props[name];
|
|
1465
|
-
if (spec?.type !== "relation") continue;
|
|
1466
|
-
const refs = spec.cardinality === "many" ? Array.isArray(value.value) ? value.value : [] : [value.value];
|
|
1467
|
-
for (const raw of refs) {
|
|
1468
|
-
const ref = relationRefOf(raw);
|
|
1469
|
-
if (ref) keys.push(relationKey(ref));
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
return keys;
|
|
1473
|
-
}
|
|
1474
|
-
function resolveRelationProp(cardinality, value, ctx) {
|
|
1475
|
-
const resolveOne = (raw) => {
|
|
1476
|
-
const ref = relationRefOf(raw);
|
|
1477
|
-
if (!ref) return null;
|
|
1478
|
-
if (ctx.transcludePath?.includes(relationKey(ref))) return null;
|
|
1479
|
-
return ctx.resolveRelation?.(ref) ?? null;
|
|
1480
|
-
};
|
|
1481
|
-
if (cardinality === "many") {
|
|
1482
|
-
const refs = Array.isArray(value) ? value : [];
|
|
1483
|
-
const out = [];
|
|
1484
|
-
for (const raw of refs) {
|
|
1485
|
-
const target = resolveOne(raw);
|
|
1486
|
-
if (target) out.push(target);
|
|
1487
|
-
}
|
|
1488
|
-
return out;
|
|
1489
|
-
}
|
|
1490
|
-
return resolveOne(value) ?? void 0;
|
|
1491
|
-
}
|
|
1492
|
-
function withContent(ctx, rendered) {
|
|
1493
|
-
if (ctx.content)
|
|
1494
|
-
return /* @__PURE__ */ jsx(ContentClientProvider, { client: ctx.content, children: rendered });
|
|
1495
|
-
return rendered;
|
|
1496
|
-
}
|
|
1497
|
-
function resolveRender(node, manifest, ctx) {
|
|
1498
|
-
const props = resolveProps(node, manifest, ctx);
|
|
1499
|
-
if (manifestConsumesEmbeds(manifest)) props.renderEmbed = createEmbedRenderer(ctx);
|
|
1500
|
-
if (manifestConsumesRelations(manifest)) {
|
|
1501
|
-
props.renderBlocks = createBlocksRenderer({
|
|
1502
|
-
...ctx,
|
|
1503
|
-
transcludePath: [...ctx.transcludePath ?? [], ...nodeRelationKeys(node, manifest)]
|
|
1504
|
-
});
|
|
1505
|
-
}
|
|
1506
|
-
if (manifest.listing) {
|
|
1507
|
-
const query = listingQueryOf(node, manifest.listing);
|
|
1508
|
-
props[manifest.listing.prop] = query && ctx.resolveListing?.(query) || [];
|
|
1509
|
-
}
|
|
1510
|
-
if (manifest.iterate) {
|
|
1511
|
-
return resolveRepeater(node, manifest, manifest.iterate, props, ctx);
|
|
1512
|
-
}
|
|
1513
|
-
if (manifest.composition) {
|
|
1514
|
-
return resolveComposition(manifest.composition, props, ctx);
|
|
1515
|
-
}
|
|
1516
|
-
const { children, slotProps } = renderSlots(node, manifest, ctx);
|
|
1517
|
-
return { props, slotProps, children };
|
|
1518
|
-
}
|
|
1519
|
-
function resolveComposition(spec, props, ctx) {
|
|
1520
|
-
return { props, slotProps: {}, children: renderComposition(props[spec.valueProp], ctx) };
|
|
1521
|
-
}
|
|
1522
|
-
function resolveProps(node, manifest, ctx) {
|
|
1523
|
-
const out = {};
|
|
1524
|
-
const entries = Object.entries(node.props ?? {});
|
|
1525
|
-
const isSpread = (value) => value.kind === "binding" && value.binding.mode === "spread";
|
|
1526
|
-
for (const [name, value] of entries)
|
|
1527
|
-
if (isSpread(value)) resolveProp(name, value, manifest, ctx, out);
|
|
1528
|
-
for (const [name, value] of entries)
|
|
1529
|
-
if (!isSpread(value)) resolveProp(name, value, manifest, ctx, out);
|
|
1530
|
-
return out;
|
|
1531
|
-
}
|
|
1532
|
-
function isUrlLike(value) {
|
|
1533
|
-
return /^(https?:)?\/\//.test(value) || value.startsWith("/") || value.startsWith("data:") || value.startsWith("blob:");
|
|
1534
|
-
}
|
|
1535
|
-
function collectReferencedAssetIds(node, registry, into = /* @__PURE__ */ new Set()) {
|
|
1536
|
-
const manifest = registry.getManifest(node.componentId);
|
|
1537
|
-
if (manifest) {
|
|
1538
|
-
for (const [name, value] of Object.entries(node.props ?? {})) {
|
|
1539
|
-
if (value.kind !== "static") continue;
|
|
1540
|
-
const spec = manifest.props[name];
|
|
1541
|
-
if (spec?.type === "asset") {
|
|
1542
|
-
const raw = value.value;
|
|
1543
|
-
if (typeof raw === "string" && raw !== "" && !isUrlLike(raw)) into.add(raw);
|
|
1544
|
-
} else if (spec?.type === "object") {
|
|
1545
|
-
collectObjectPropAssetIds(spec.fields, spec.cardinality, value.value, into);
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1549
|
-
for (const children of Object.values(node.slots ?? {})) {
|
|
1550
|
-
for (const child of children) collectReferencedAssetIds(child, registry, into);
|
|
1551
|
-
}
|
|
1552
|
-
return into;
|
|
1553
|
-
}
|
|
1554
|
-
function collectReferencedRelations(nodes, registry, resolveRelation, into = {}, refKeys) {
|
|
1555
|
-
for (const node of nodes) {
|
|
1556
|
-
const manifest = registry.getManifest(node.componentId);
|
|
1557
|
-
if (manifest) {
|
|
1558
|
-
for (const [name, value] of Object.entries(node.props ?? {})) {
|
|
1559
|
-
if (value.kind !== "static") continue;
|
|
1560
|
-
const spec = manifest.props[name];
|
|
1561
|
-
if (spec?.type === "relation") {
|
|
1562
|
-
const refs = spec.cardinality === "many" ? Array.isArray(value.value) ? value.value : [] : [value.value];
|
|
1563
|
-
for (const raw of refs) {
|
|
1564
|
-
const ref = relationRefOf(raw);
|
|
1565
|
-
if (!ref) continue;
|
|
1566
|
-
const key = relationKey(ref);
|
|
1567
|
-
refKeys?.add(key);
|
|
1568
|
-
if (key in into) continue;
|
|
1569
|
-
const target = resolveRelation(ref);
|
|
1570
|
-
if (!target) continue;
|
|
1571
|
-
into[key] = target;
|
|
1572
|
-
for (const fieldValue of Object.values(target)) {
|
|
1573
|
-
const childNodes = compositionRoots(fieldValue);
|
|
1574
|
-
if (childNodes.length > 0) {
|
|
1575
|
-
collectReferencedRelations(childNodes, registry, resolveRelation, into, refKeys);
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
}
|
|
1579
|
-
} else if (spec?.type === "object" && value.value != null) {
|
|
1580
|
-
collectObjectPropRelations(
|
|
1581
|
-
spec.fields,
|
|
1582
|
-
spec.cardinality,
|
|
1583
|
-
value.value,
|
|
1584
|
-
resolveRelation,
|
|
1585
|
-
into,
|
|
1586
|
-
refKeys
|
|
1587
|
-
);
|
|
1588
|
-
}
|
|
1589
|
-
}
|
|
1590
|
-
if (manifest.composition) {
|
|
1591
|
-
const composed = node.props?.[manifest.composition.valueProp];
|
|
1592
|
-
if (composed?.kind === "static") {
|
|
1593
|
-
collectReferencedRelations(
|
|
1594
|
-
compositionRoots(composed.value),
|
|
1595
|
-
registry,
|
|
1596
|
-
resolveRelation,
|
|
1597
|
-
into,
|
|
1598
|
-
refKeys
|
|
1599
|
-
);
|
|
1600
|
-
}
|
|
1601
|
-
}
|
|
1602
|
-
}
|
|
1603
|
-
for (const children of Object.values(node.slots ?? {})) {
|
|
1604
|
-
collectReferencedRelations(children, registry, resolveRelation, into, refKeys);
|
|
1605
|
-
}
|
|
1606
|
-
}
|
|
1607
|
-
return into;
|
|
1608
|
-
}
|
|
1609
|
-
function listingQueryOf(node, spec) {
|
|
1610
|
-
const pick = node.props?.[spec.filter.fromProp];
|
|
1611
|
-
if (!pick || pick.kind !== "static") return null;
|
|
1612
|
-
const ref = relationRefOf(pick.value);
|
|
1613
|
-
const id = ref ? ref.id : typeof pick.value === "string" && pick.value !== "" ? pick.value : null;
|
|
1614
|
-
if (id === null) return null;
|
|
1615
|
-
const query = { collection: spec.collection, where: { [spec.filter.field]: id } };
|
|
1616
|
-
if (spec.sort) query.sort = spec.sort;
|
|
1617
|
-
if (spec.limit !== void 0) query.limit = spec.limit;
|
|
1618
|
-
return query;
|
|
1619
|
-
}
|
|
1620
|
-
function serializeListingQuery(query) {
|
|
1621
|
-
const where = {};
|
|
1622
|
-
for (const key of Object.keys(query.where).sort()) {
|
|
1623
|
-
const value = query.where[key];
|
|
1624
|
-
if (value !== void 0) where[key] = value;
|
|
1625
|
-
}
|
|
1626
|
-
return JSON.stringify({
|
|
1627
|
-
collection: query.collection,
|
|
1628
|
-
where,
|
|
1629
|
-
sort: query.sort ? { field: query.sort.field, direction: query.sort.direction ?? "asc" } : null,
|
|
1630
|
-
limit: query.limit ?? null
|
|
1631
|
-
});
|
|
1632
|
-
}
|
|
1633
|
-
function collectReferencedListings(nodes, registry, runListing, resolveRelation, into = {}, visitedRelations = /* @__PURE__ */ new Set()) {
|
|
1634
|
-
for (const node of nodes) {
|
|
1635
|
-
const manifest = registry.getManifest(node.componentId);
|
|
1636
|
-
if (manifest?.listing) {
|
|
1637
|
-
const query = listingQueryOf(node, manifest.listing);
|
|
1638
|
-
if (query) {
|
|
1639
|
-
const key = serializeListingQuery(query);
|
|
1640
|
-
if (!(key in into)) into[key] = runListing(query);
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1643
|
-
if (manifest) {
|
|
1644
|
-
for (const [name, value] of Object.entries(node.props ?? {})) {
|
|
1645
|
-
if (value.kind !== "static") continue;
|
|
1646
|
-
const spec = manifest.props[name];
|
|
1647
|
-
if (spec?.type !== "relation") continue;
|
|
1648
|
-
const refs = spec.cardinality === "many" ? Array.isArray(value.value) ? value.value : [] : [value.value];
|
|
1649
|
-
for (const raw of refs) {
|
|
1650
|
-
const ref = relationRefOf(raw);
|
|
1651
|
-
if (!ref) continue;
|
|
1652
|
-
const relKey = relationKey(ref);
|
|
1653
|
-
if (visitedRelations.has(relKey)) continue;
|
|
1654
|
-
visitedRelations.add(relKey);
|
|
1655
|
-
const target = resolveRelation(ref);
|
|
1656
|
-
if (!target) continue;
|
|
1657
|
-
for (const fieldValue of Object.values(target)) {
|
|
1658
|
-
const childNodes = compositionRoots(fieldValue);
|
|
1659
|
-
if (childNodes.length > 0) {
|
|
1660
|
-
collectReferencedListings(
|
|
1661
|
-
childNodes,
|
|
1662
|
-
registry,
|
|
1663
|
-
runListing,
|
|
1664
|
-
resolveRelation,
|
|
1665
|
-
into,
|
|
1666
|
-
visitedRelations
|
|
1667
|
-
);
|
|
1668
|
-
}
|
|
1669
|
-
}
|
|
1670
|
-
}
|
|
1671
|
-
}
|
|
1672
|
-
}
|
|
1673
|
-
if (manifest?.composition) {
|
|
1674
|
-
const composed = node.props?.[manifest.composition.valueProp];
|
|
1675
|
-
if (composed?.kind === "static") {
|
|
1676
|
-
collectReferencedListings(
|
|
1677
|
-
compositionRoots(composed.value),
|
|
1678
|
-
registry,
|
|
1679
|
-
runListing,
|
|
1680
|
-
resolveRelation,
|
|
1681
|
-
into,
|
|
1682
|
-
visitedRelations
|
|
1683
|
-
);
|
|
1684
|
-
}
|
|
1685
|
-
}
|
|
1686
|
-
for (const children of Object.values(node.slots ?? {})) {
|
|
1687
|
-
collectReferencedListings(children, registry, runListing, resolveRelation, into, visitedRelations);
|
|
1688
|
-
}
|
|
1689
|
-
}
|
|
1690
|
-
return into;
|
|
1691
|
-
}
|
|
1692
|
-
function collectObjectPropAssetIds(fields, cardinality, value, into) {
|
|
1693
|
-
const items = cardinality === "many" ? Array.isArray(value) ? value : [] : [value];
|
|
1694
|
-
for (const item of items) {
|
|
1695
|
-
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
1696
|
-
const record = item;
|
|
1697
|
-
for (const sub of fields) {
|
|
1698
|
-
const sv = record[sub.name];
|
|
1699
|
-
if (sub.type === "asset" && typeof sv === "string" && sv !== "" && !isUrlLike(sv)) into.add(sv);
|
|
1700
|
-
else if (sub.type === "object" && sv != null)
|
|
1701
|
-
collectObjectPropAssetIds(sub.fields, sub.cardinality, sv, into);
|
|
1702
|
-
}
|
|
1703
|
-
}
|
|
1704
|
-
}
|
|
1705
|
-
function collectObjectPropRelations(fields, cardinality, value, resolveRelation, into, refKeys) {
|
|
1706
|
-
const items = cardinality === "many" ? Array.isArray(value) ? value : [] : [value];
|
|
1707
|
-
for (const item of items) {
|
|
1708
|
-
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
1709
|
-
const record = item;
|
|
1710
|
-
for (const sub of fields) {
|
|
1711
|
-
const sv = record[sub.name];
|
|
1712
|
-
if (sub.type === "relation" && sv != null) {
|
|
1713
|
-
const refs = sub.cardinality === "many" ? Array.isArray(sv) ? sv : [] : [sv];
|
|
1714
|
-
for (const raw of refs) {
|
|
1715
|
-
const ref = relationRefOf(raw);
|
|
1716
|
-
if (!ref) continue;
|
|
1717
|
-
const key = relationKey(ref);
|
|
1718
|
-
refKeys?.add(key);
|
|
1719
|
-
if (key in into) continue;
|
|
1720
|
-
const target = resolveRelation(ref);
|
|
1721
|
-
if (target) into[key] = target;
|
|
1722
|
-
}
|
|
1723
|
-
} else if (sub.type === "object" && sv != null) {
|
|
1724
|
-
collectObjectPropRelations(sub.fields, sub.cardinality, sv, resolveRelation, into, refKeys);
|
|
1725
|
-
}
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
function resolveObjectProps(fields, cardinality, value, ctx) {
|
|
1730
|
-
const items = cardinality === "many" ? Array.isArray(value) ? value : [] : [value];
|
|
1731
|
-
const resolvedItems = items.map((item) => {
|
|
1732
|
-
if (!item || typeof item !== "object" || Array.isArray(item)) return item;
|
|
1733
|
-
const next = { ...item };
|
|
1734
|
-
for (const sub of fields) {
|
|
1735
|
-
const sv = next[sub.name];
|
|
1736
|
-
if (sub.type === "asset" && typeof sv === "string") {
|
|
1737
|
-
if (sv === "" || isUrlLike(sv)) continue;
|
|
1738
|
-
const resolved = ctx.resolveAsset?.(sv);
|
|
1739
|
-
if (resolved) next[sub.name] = resolved;
|
|
1740
|
-
else delete next[sub.name];
|
|
1741
|
-
} else if (sub.type === "relation" && sv != null) {
|
|
1742
|
-
if (!ctx.resolveRelation) continue;
|
|
1743
|
-
const resolved = resolveRelationProp(sub.cardinality, sv, ctx);
|
|
1744
|
-
if (resolved !== void 0) next[sub.name] = resolved;
|
|
1745
|
-
else delete next[sub.name];
|
|
1746
|
-
} else if (sub.type === "object" && sv != null) {
|
|
1747
|
-
next[sub.name] = resolveObjectProps(sub.fields, sub.cardinality, sv, ctx);
|
|
1748
|
-
}
|
|
1749
|
-
}
|
|
1750
|
-
return next;
|
|
1751
|
-
});
|
|
1752
|
-
return cardinality === "many" ? resolvedItems : resolvedItems[0];
|
|
1753
|
-
}
|
|
1754
|
-
function resolveProp(name, value, manifest, ctx, out) {
|
|
1755
|
-
const spec = manifest.props[name];
|
|
1756
|
-
if (value.kind === "static") {
|
|
1757
|
-
const validate = propValueValidator(manifest, name);
|
|
1758
|
-
if (validate && !validate(value.value)) {
|
|
1759
|
-
if (spec?.default !== void 0) out[name] = spec.default;
|
|
1760
|
-
return;
|
|
1761
|
-
}
|
|
1762
|
-
if (spec?.type === "asset" && typeof value.value === "string") {
|
|
1763
|
-
const raw = value.value;
|
|
1764
|
-
if (raw === "" || isUrlLike(raw)) {
|
|
1765
|
-
out[name] = raw;
|
|
1766
|
-
return;
|
|
1767
|
-
}
|
|
1768
|
-
const resolved2 = ctx.resolveAsset?.(raw);
|
|
1769
|
-
if (resolved2) out[name] = resolved2;
|
|
1770
|
-
return;
|
|
1771
|
-
}
|
|
1772
|
-
if (spec?.type === "object" && value.value != null) {
|
|
1773
|
-
out[name] = resolveObjectProps(spec.fields, spec.cardinality, value.value, ctx);
|
|
1774
|
-
return;
|
|
1775
|
-
}
|
|
1776
|
-
if (spec?.type === "relation") {
|
|
1777
|
-
if (!ctx.resolveRelation) {
|
|
1778
|
-
out[name] = value.value;
|
|
1779
|
-
return;
|
|
1780
|
-
}
|
|
1781
|
-
const resolved2 = resolveRelationProp(spec.cardinality, value.value, ctx);
|
|
1782
|
-
if (resolved2 !== void 0) out[name] = resolved2;
|
|
1783
|
-
return;
|
|
1784
|
-
}
|
|
1785
|
-
out[name] = value.value;
|
|
1786
|
-
return;
|
|
1787
|
-
}
|
|
1788
|
-
const resolved = ctx.resolveBinding(value.binding, ctx.item);
|
|
1789
|
-
if (value.binding.mode === "spread") {
|
|
1790
|
-
if (resolved && typeof resolved === "object" && !Array.isArray(resolved)) {
|
|
1791
|
-
Object.assign(out, resolved);
|
|
1792
|
-
}
|
|
1793
|
-
return;
|
|
1794
|
-
}
|
|
1795
|
-
out[name] = resolved;
|
|
1796
|
-
}
|
|
1797
|
-
function renderSlots(node, manifest, ctx) {
|
|
1798
|
-
const slots = node.slots ?? {};
|
|
1799
|
-
const slotProps = {};
|
|
1800
|
-
let children = renderSlotChildren(slots[DEFAULT_SLOT], ctx);
|
|
1801
|
-
const declaredSlots = manifestSlots(manifest);
|
|
1802
|
-
for (const slotSpec of declaredSlots) {
|
|
1803
|
-
if (slotSpec.name === DEFAULT_SLOT) continue;
|
|
1804
|
-
const nodes = slots[slotSpec.name];
|
|
1805
|
-
if ((!nodes || nodes.length === 0) && slotSpec.required) {
|
|
1806
|
-
slotProps[slotSpec.name] = /* @__PURE__ */ jsx(MissingSlotPlaceholder, { nodeId: node.id, slot: slotSpec.name });
|
|
1807
|
-
continue;
|
|
1808
|
-
}
|
|
1809
|
-
slotProps[slotSpec.name] = renderSlotChildren(nodes, ctx);
|
|
1810
|
-
}
|
|
1811
|
-
const declared = new Set(declaredSlots.map((s) => s.name));
|
|
1812
|
-
for (const [name, nodes] of Object.entries(slots)) {
|
|
1813
|
-
if (name === DEFAULT_SLOT || declared.has(name)) continue;
|
|
1814
|
-
slotProps[name] = renderSlotChildren(nodes, ctx);
|
|
1815
|
-
}
|
|
1816
|
-
const defaultSpec = declaredSlots.find((s) => s.name === DEFAULT_SLOT);
|
|
1817
|
-
const defaultNodes = slots[DEFAULT_SLOT];
|
|
1818
|
-
if (defaultSpec?.required && (!defaultNodes || defaultNodes.length === 0)) {
|
|
1819
|
-
children = /* @__PURE__ */ jsx(MissingSlotPlaceholder, { nodeId: node.id, slot: DEFAULT_SLOT });
|
|
1820
|
-
}
|
|
1821
|
-
return { children, slotProps };
|
|
1822
|
-
}
|
|
1823
|
-
function renderSlotChildren(nodes, ctx) {
|
|
1824
|
-
if (!nodes || nodes.length === 0) return null;
|
|
1825
|
-
return /* @__PURE__ */ jsx(Fragment$1, { children: nodes.map((child) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(child, ctx) }, child.id)) });
|
|
1826
|
-
}
|
|
1827
|
-
function resolveRepeater(node, manifest, iterate, props, ctx) {
|
|
1828
|
-
const itemsValue = props[iterate.itemsProp];
|
|
1829
|
-
const items = Array.isArray(itemsValue) ? itemsValue : [];
|
|
1830
|
-
const template = node.slots?.[iterate.itemSlot];
|
|
1831
|
-
const slotSpec = manifest.slots.find((s) => s.name === iterate.itemSlot);
|
|
1832
|
-
if ((!template || template.length === 0) && slotSpec?.required) {
|
|
1833
|
-
return {
|
|
1834
|
-
props,
|
|
1835
|
-
slotProps: {},
|
|
1836
|
-
children: /* @__PURE__ */ jsx(MissingSlotPlaceholder, { nodeId: node.id, slot: iterate.itemSlot })
|
|
1837
|
-
};
|
|
1838
|
-
}
|
|
1839
|
-
const rendered = items.map((item, index) => {
|
|
1840
|
-
const itemCtx = { ...ctx, item: { item, index } };
|
|
1841
|
-
return /* @__PURE__ */ jsx(Fragment$1, { children: (template ?? []).map((child) => (
|
|
1842
|
-
// Keyed per template node for the same reason as renderSlotChildren.
|
|
1843
|
-
/* @__PURE__ */ jsx(Fragment$1, { children: renderNode(child, itemCtx) }, child.id)
|
|
1844
|
-
)) }, index);
|
|
1845
|
-
});
|
|
1846
|
-
return { props, slotProps: {}, children: /* @__PURE__ */ jsx(Fragment$1, { children: rendered }) };
|
|
1847
|
-
}
|
|
1848
|
-
function renderComposition(value, ctx) {
|
|
1849
|
-
const roots = compositionRoots(value);
|
|
1850
|
-
if (roots.length === 0) return null;
|
|
1851
|
-
return withContent(
|
|
1852
|
-
ctx,
|
|
1853
|
-
/* @__PURE__ */ jsx(Fragment$1, { children: roots.map((root) => /* @__PURE__ */ jsx(Fragment$1, { children: renderNode(root, ctx) }, root.id)) })
|
|
1854
|
-
);
|
|
1855
|
-
}
|
|
1856
|
-
function compositionRoots(value) {
|
|
1857
|
-
if (Array.isArray(value)) return value.filter(isComponentNode);
|
|
1858
|
-
if (isComponentNode(value)) return [value];
|
|
1859
|
-
return [];
|
|
1860
|
-
}
|
|
1861
|
-
function isComponentNode(value) {
|
|
1862
|
-
return typeof value === "object" && value !== null && typeof value.id === "string" && typeof value.componentId === "string";
|
|
1863
|
-
}
|
|
1864
|
-
function renderCompositionInLayout(graph, layoutId, value, ctx) {
|
|
1865
|
-
const layout = layoutId ? graph.layouts.find((l) => l.id === layoutId) : void 0;
|
|
1866
|
-
if (!layout) return withContent(ctx, renderComposition(value, ctx));
|
|
1867
|
-
const root = injectNodesIntoOutlet(layout.root, compositionRoots(value));
|
|
1868
|
-
return withContent(ctx, renderNode(root, ctx));
|
|
1869
|
-
}
|
|
1870
|
-
function injectNodesIntoOutlet(root, nodes) {
|
|
1871
|
-
return {
|
|
1872
|
-
...root,
|
|
1873
|
-
slots: {
|
|
1874
|
-
...root.slots ?? {},
|
|
1875
|
-
[OUTLET_SLOT]: nodes
|
|
1876
|
-
}
|
|
1877
|
-
};
|
|
1878
|
-
}
|
|
1879
|
-
function messageOf(error) {
|
|
1880
|
-
return error instanceof Error ? error.message : String(error);
|
|
1881
|
-
}
|
|
1882
|
-
var richTextAttrsSchema = z.record(z.string(), jsonValueSchema);
|
|
1883
|
-
var richTextMarkSchema = z.object({
|
|
1884
|
-
type: z.string().min(1),
|
|
1885
|
-
attrs: richTextAttrsSchema.optional()
|
|
1886
|
-
});
|
|
1887
|
-
var richTextNodeSchema = z.lazy(
|
|
1888
|
-
() => z.object({
|
|
1889
|
-
type: z.string().min(1),
|
|
1890
|
-
attrs: richTextAttrsSchema.optional(),
|
|
1891
|
-
content: z.array(richTextNodeSchema).optional(),
|
|
1892
|
-
marks: z.array(richTextMarkSchema).optional(),
|
|
1893
|
-
text: z.string().optional()
|
|
1894
|
-
})
|
|
1895
|
-
);
|
|
1896
|
-
var richTextDocSchema = z.object({
|
|
1897
|
-
type: z.literal("doc"),
|
|
1898
|
-
content: z.array(richTextNodeSchema).default([])
|
|
1899
|
-
});
|
|
1900
|
-
var richTextValueSchema = z.object({
|
|
1901
|
-
/** Schema version this value was written under. */
|
|
1902
|
-
version: z.number().int().nonnegative(),
|
|
1903
|
-
doc: richTextDocSchema
|
|
1904
|
-
});
|
|
1905
|
-
function parseRichTextValue(input) {
|
|
1906
|
-
let json;
|
|
1907
|
-
try {
|
|
1908
|
-
json = typeof input === "string" ? JSON.parse(input) : input;
|
|
1909
|
-
} catch {
|
|
1910
|
-
json = void 0;
|
|
1911
|
-
}
|
|
1912
|
-
const result = richTextValueSchema.safeParse(json);
|
|
1913
|
-
return result.success ? { ok: true, value: result.data } : { ok: false, error: result.error };
|
|
1914
|
-
}
|
|
1915
|
-
|
|
1916
|
-
// ../rich-text/src/embed.ts
|
|
1917
|
-
var COMPONENT_EMBED_NODE_TYPE = "componentEmbed";
|
|
1918
|
-
function isComponentEmbedNode(node) {
|
|
1919
|
-
return node.type === COMPONENT_EMBED_NODE_TYPE;
|
|
1920
|
-
}
|
|
1921
|
-
function embeddedComponentNode(node) {
|
|
1922
|
-
if (!isComponentEmbedNode(node)) return void 0;
|
|
1923
|
-
const raw = node.attrs?.node;
|
|
1924
|
-
const parsed = componentNodeSchema.safeParse(raw);
|
|
1925
|
-
return parsed.success ? parsed.data : void 0;
|
|
1926
|
-
}
|
|
1927
|
-
|
|
1928
|
-
// ../rich-text/src/registry.tsx
|
|
1929
|
-
function createRichTextRegistry(definitions = []) {
|
|
1930
|
-
const byType = /* @__PURE__ */ new Map();
|
|
1931
|
-
for (const def of definitions) {
|
|
1932
|
-
byType.set(def.type, def);
|
|
1933
|
-
}
|
|
1934
|
-
return {
|
|
1935
|
-
has: (type) => byType.has(type),
|
|
1936
|
-
get: (type) => byType.get(type),
|
|
1937
|
-
isInline: (type) => byType.get(type)?.group === "inline"
|
|
1938
|
-
};
|
|
1939
|
-
}
|
|
1940
|
-
var emptyRichTextRegistry = createRichTextRegistry();
|
|
1941
|
-
function attrString(value) {
|
|
1942
|
-
return typeof value === "string" ? value : void 0;
|
|
1943
|
-
}
|
|
1944
|
-
function attrNumber(value) {
|
|
1945
|
-
return typeof value === "number" ? value : void 0;
|
|
1946
|
-
}
|
|
1947
|
-
function applyMarks(content, marks, key) {
|
|
1948
|
-
if (!marks || marks.length === 0) return content;
|
|
1949
|
-
return marks.reduce((acc, mark, i) => {
|
|
1950
|
-
const markKey = `${key}-m${i}`;
|
|
1951
|
-
switch (mark.type) {
|
|
1952
|
-
case "bold":
|
|
1953
|
-
return createElement("strong", { key: markKey }, acc);
|
|
1954
|
-
case "italic":
|
|
1955
|
-
return createElement("em", { key: markKey }, acc);
|
|
1956
|
-
case "code":
|
|
1957
|
-
return createElement("code", { key: markKey }, acc);
|
|
1958
|
-
case "strike":
|
|
1959
|
-
return createElement("s", { key: markKey }, acc);
|
|
1960
|
-
case "underline":
|
|
1961
|
-
return createElement("u", { key: markKey }, acc);
|
|
1962
|
-
case "link": {
|
|
1963
|
-
const href = attrString(mark.attrs?.href) ?? "#";
|
|
1964
|
-
const target = attrString(mark.attrs?.target);
|
|
1965
|
-
const rel = target === "_blank" ? "noopener noreferrer" : attrString(mark.attrs?.rel);
|
|
1966
|
-
return createElement("a", { key: markKey, href, ...target ? { target } : {}, ...rel ? { rel } : {} }, acc);
|
|
1967
|
-
}
|
|
1968
|
-
default:
|
|
1969
|
-
return createElement("span", { key: markKey, "data-unknown-mark": mark.type }, acc);
|
|
1970
|
-
}
|
|
1971
|
-
}, content);
|
|
1972
|
-
}
|
|
1973
|
-
function invalidEmbedFallback(key) {
|
|
1974
|
-
return createElement(
|
|
1975
|
-
"div",
|
|
1976
|
-
{ key, "data-invalid-embed": "", className: "rich-text-invalid-embed", role: "note" },
|
|
1977
|
-
"Invalid embedded component."
|
|
1978
|
-
);
|
|
1979
|
-
}
|
|
1980
|
-
function renderChildren(nodes, ctx, path) {
|
|
1981
|
-
if (!nodes) return [];
|
|
1982
|
-
return nodes.map((child, i) => renderNode2(child, ctx, [...path, i]));
|
|
1983
|
-
}
|
|
1984
|
-
function unknownFallback(node, key) {
|
|
1985
|
-
return createElement(
|
|
1986
|
-
"div",
|
|
1987
|
-
{ key, "data-unknown-node": node.type, className: "rich-text-unknown", role: "note" },
|
|
1988
|
-
`Unknown content block: ${node.type}`
|
|
1989
|
-
);
|
|
1990
|
-
}
|
|
1991
|
-
function renderNode2(node, ctx, path) {
|
|
1992
|
-
const key = `n-${path.join("-")}`;
|
|
1993
|
-
if (node.type === "text") {
|
|
1994
|
-
return applyMarks(node.text ?? "", node.marks, key);
|
|
1995
|
-
}
|
|
1996
|
-
switch (node.type) {
|
|
1997
|
-
case "paragraph":
|
|
1998
|
-
return createElement("p", { key }, ...renderChildren(node.content, ctx, path));
|
|
1999
|
-
case "heading": {
|
|
2000
|
-
const level = attrNumber(node.attrs?.level);
|
|
2001
|
-
const clamped = level && level >= 1 && level <= 6 ? level : 1;
|
|
2002
|
-
return createElement(`h${clamped}`, { key }, ...renderChildren(node.content, ctx, path));
|
|
2003
|
-
}
|
|
2004
|
-
case "bulletList":
|
|
2005
|
-
return createElement("ul", { key }, ...renderChildren(node.content, ctx, path));
|
|
2006
|
-
case "orderedList":
|
|
2007
|
-
return createElement("ol", { key }, ...renderChildren(node.content, ctx, path));
|
|
2008
|
-
case "listItem":
|
|
2009
|
-
return createElement("li", { key }, ...renderChildren(node.content, ctx, path));
|
|
2010
|
-
case "blockquote":
|
|
2011
|
-
return createElement("blockquote", { key }, ...renderChildren(node.content, ctx, path));
|
|
2012
|
-
case "codeBlock": {
|
|
2013
|
-
const language = attrString(node.attrs?.language);
|
|
2014
|
-
return createElement(
|
|
2015
|
-
"pre",
|
|
2016
|
-
{ key },
|
|
2017
|
-
createElement(
|
|
2018
|
-
"code",
|
|
2019
|
-
language ? { className: `language-${language}` } : {},
|
|
2020
|
-
...renderChildren(node.content, ctx, path)
|
|
2021
|
-
)
|
|
2022
|
-
);
|
|
2023
|
-
}
|
|
2024
|
-
case "hardBreak":
|
|
2025
|
-
return createElement("br", { key });
|
|
2026
|
-
case "horizontalRule":
|
|
2027
|
-
return createElement("hr", { key });
|
|
2028
|
-
case "image": {
|
|
2029
|
-
const src = attrString(node.attrs?.src) ?? "";
|
|
2030
|
-
const alt = attrString(node.attrs?.alt) ?? "";
|
|
2031
|
-
const title = attrString(node.attrs?.title);
|
|
2032
|
-
return createElement("img", { key, src, alt, ...title ? { title } : {} });
|
|
2033
|
-
}
|
|
2034
|
-
case "embed": {
|
|
2035
|
-
const src = attrString(node.attrs?.src) ?? "";
|
|
2036
|
-
const title = attrString(node.attrs?.title) ?? "Embedded content";
|
|
2037
|
-
return createElement("iframe", {
|
|
2038
|
-
key,
|
|
2039
|
-
src,
|
|
2040
|
-
title,
|
|
2041
|
-
loading: "lazy",
|
|
2042
|
-
"data-embed-provider": attrString(node.attrs?.provider) ?? void 0
|
|
2043
|
-
});
|
|
2044
|
-
}
|
|
2045
|
-
case COMPONENT_EMBED_NODE_TYPE: {
|
|
2046
|
-
const embedded = embeddedComponentNode(node);
|
|
2047
|
-
if (!embedded) {
|
|
2048
|
-
ctx.onIssue?.({
|
|
2049
|
-
code: "invalid-embed",
|
|
2050
|
-
message: "Component embed is missing a valid embedded component node.",
|
|
2051
|
-
nodeType: node.type,
|
|
2052
|
-
path
|
|
2053
|
-
});
|
|
2054
|
-
return invalidEmbedFallback(key);
|
|
2055
|
-
}
|
|
2056
|
-
if (!ctx.renderEmbed) return invalidEmbedFallback(key);
|
|
2057
|
-
return createElement(Fragment$1, { key }, ctx.renderEmbed(embedded));
|
|
2058
|
-
}
|
|
2059
|
-
default: {
|
|
2060
|
-
const def = ctx.registry.get(node.type);
|
|
2061
|
-
if (def) {
|
|
2062
|
-
const children = node.content && node.content.length > 0 ? renderChildren(node.content, ctx, path) : void 0;
|
|
2063
|
-
const element = def.render({ node, children });
|
|
2064
|
-
return element ? createElement(Fragment$1, { key }, element) : null;
|
|
2065
|
-
}
|
|
2066
|
-
ctx.onIssue?.({
|
|
2067
|
-
code: "unknown-node",
|
|
2068
|
-
message: `No renderer registered for rich-text node type "${node.type}".`,
|
|
2069
|
-
nodeType: node.type,
|
|
2070
|
-
path
|
|
2071
|
-
});
|
|
2072
|
-
return unknownFallback(node, key);
|
|
2073
|
-
}
|
|
2074
|
-
}
|
|
2075
|
-
}
|
|
2076
|
-
function renderRichTextDoc(doc, options = {}) {
|
|
2077
|
-
const ctx = {
|
|
2078
|
-
registry: options.registry ?? emptyRichTextRegistry,
|
|
2079
|
-
renderEmbed: options.renderEmbed,
|
|
2080
|
-
onIssue: options.onIssue
|
|
2081
|
-
};
|
|
2082
|
-
const children = renderChildren(doc.content, ctx, []);
|
|
2083
|
-
return createElement(
|
|
2084
|
-
"div",
|
|
2085
|
-
{ className: options.className ? `rich-text ${options.className}` : "rich-text" },
|
|
2086
|
-
...children
|
|
2087
|
-
);
|
|
2088
|
-
}
|
|
2089
|
-
function renderRichTextValue(value, options = {}) {
|
|
2090
|
-
return renderRichTextDoc(value.doc, options);
|
|
2091
|
-
}
|
|
2092
|
-
function str(value) {
|
|
2093
|
-
return typeof value === "string" ? value : "";
|
|
2094
|
-
}
|
|
2095
|
-
var calloutBlock = {
|
|
2096
|
-
type: "callout",
|
|
2097
|
-
group: "block",
|
|
2098
|
-
render: ({ node, children }) => createElement(
|
|
2099
|
-
"aside",
|
|
2100
|
-
{ className: "callout", "data-tone": str(node.attrs?.tone) || "info", role: "note" },
|
|
2101
|
-
children
|
|
2102
|
-
)
|
|
2103
|
-
};
|
|
2104
|
-
var mentionInline = {
|
|
2105
|
-
type: "mention",
|
|
2106
|
-
group: "inline",
|
|
2107
|
-
render: ({ node }) => createElement(
|
|
2108
|
-
"span",
|
|
2109
|
-
{ className: "mention", "data-mention-id": str(node.attrs?.id) },
|
|
2110
|
-
`@${str(node.attrs?.label)}`
|
|
2111
|
-
)
|
|
2112
|
-
};
|
|
2113
|
-
var sampleRichTextRegistry = createRichTextRegistry([
|
|
2114
|
-
calloutBlock,
|
|
2115
|
-
mentionInline
|
|
2116
|
-
]);
|
|
2117
|
-
var richTextPrimitiveRegistry = sampleRichTextRegistry;
|
|
2118
|
-
function asJson(value) {
|
|
2119
|
-
return value;
|
|
2120
|
-
}
|
|
2121
|
-
var RICH_TEXT_DEFAULT_VALUE = {
|
|
2122
|
-
version: 1,
|
|
2123
|
-
doc: {
|
|
2124
|
-
type: "doc",
|
|
2125
|
-
content: [
|
|
2126
|
-
{
|
|
2127
|
-
type: "paragraph",
|
|
2128
|
-
content: [{ type: "text", text: "Rich text. Select to format, double-click to edit." }]
|
|
2129
|
-
}
|
|
2130
|
-
]
|
|
2131
|
-
}
|
|
2132
|
-
};
|
|
2133
|
-
var RICH_TEXT_FIXTURE_VALUE = {
|
|
2134
|
-
version: 1,
|
|
2135
|
-
doc: {
|
|
2136
|
-
type: "doc",
|
|
2137
|
-
content: [
|
|
2138
|
-
{ type: "heading", attrs: { level: 3 }, content: [{ type: "text", text: "Rich text block" }] },
|
|
2139
|
-
{
|
|
2140
|
-
type: "paragraph",
|
|
2141
|
-
content: [
|
|
2142
|
-
{ type: "text", text: "Formatted " },
|
|
2143
|
-
{ type: "text", text: "bold", marks: [{ type: "bold" }] },
|
|
2144
|
-
{ type: "text", text: " and " },
|
|
2145
|
-
{ type: "text", text: "italic", marks: [{ type: "italic" }] },
|
|
2146
|
-
{ type: "text", text: " copy with a " },
|
|
2147
|
-
{ type: "text", text: "link", marks: [{ type: "link", attrs: { href: "https://example.com" } }] },
|
|
2148
|
-
{ type: "text", text: "." }
|
|
2149
|
-
]
|
|
2150
|
-
},
|
|
2151
|
-
{
|
|
2152
|
-
type: "callout",
|
|
2153
|
-
attrs: { tone: "info" },
|
|
2154
|
-
content: [{ type: "text", text: "A custom block, rendered by the project registry." }]
|
|
2155
|
-
}
|
|
2156
|
-
]
|
|
2157
|
-
}
|
|
2158
|
-
};
|
|
2159
|
-
function RichText(props) {
|
|
2160
|
-
const renderEmbed = typeof props.renderEmbed === "function" ? props.renderEmbed : void 0;
|
|
2161
|
-
const parsed = parseRichTextValue(props.content ?? RICH_TEXT_DEFAULT_VALUE);
|
|
2162
|
-
if (!parsed.ok) {
|
|
2163
|
-
return /* @__PURE__ */ jsx("div", { "data-ec-primitive": "RichText", className: "rich-text-invalid", role: "note", children: "Invalid rich-text content." });
|
|
2164
|
-
}
|
|
2165
|
-
return /* @__PURE__ */ jsx("div", { "data-ec-primitive": "RichText", children: renderRichTextValue(parsed.value, { registry: richTextPrimitiveRegistry, renderEmbed }) });
|
|
2166
|
-
}
|
|
2167
|
-
var RichTextManifest = defineComponent({
|
|
2168
|
-
id: "base.primitives.RichText",
|
|
2169
|
-
namespace: "base.primitives",
|
|
2170
|
-
title: "Rich Text",
|
|
2171
|
-
category: "content",
|
|
2172
|
-
description: "Formatted long-form content stored in the rich-text format.",
|
|
2173
|
-
props: {
|
|
2174
|
-
content: {
|
|
2175
|
-
// The structured @elytracms/rich-text storage value (EC-159); validated by
|
|
2176
|
-
// the renderer's parse guard rather than a scalar string check.
|
|
2177
|
-
type: "richText",
|
|
2178
|
-
context: "prop",
|
|
2179
|
-
default: asJson(RICH_TEXT_DEFAULT_VALUE),
|
|
2180
|
-
// Rich-text content prop, editable in place on the canvas (EC-159).
|
|
2181
|
-
form: { label: "Content", inlineEditable: true }
|
|
2182
|
-
}
|
|
2183
|
-
},
|
|
2184
|
-
slots: [],
|
|
2185
|
-
fixtures: [
|
|
2186
|
-
{ name: "Default" },
|
|
2187
|
-
{ name: "Formatted", props: { content: asJson(RICH_TEXT_FIXTURE_VALUE) } }
|
|
2188
|
-
]
|
|
2189
|
-
});
|
|
2190
|
-
function asString(value, fallback = "") {
|
|
2191
|
-
return typeof value === "string" ? value : fallback;
|
|
2192
|
-
}
|
|
2193
|
-
function gapCss(value) {
|
|
2194
|
-
if (typeof value === "string") return spacingTokenCss(value);
|
|
2195
|
-
if (typeof value === "number" && Number.isFinite(value) && value > 0) return `${value * 4}px`;
|
|
2196
|
-
return void 0;
|
|
2197
|
-
}
|
|
2198
|
-
var ALIGN_ITEMS = {
|
|
2199
|
-
start: "flex-start",
|
|
2200
|
-
center: "center",
|
|
2201
|
-
end: "flex-end",
|
|
2202
|
-
stretch: "stretch"
|
|
2203
|
-
};
|
|
2204
|
-
function alignItemsCss(value) {
|
|
2205
|
-
if (value === "start" || value === "center" || value === "end") return ALIGN_ITEMS[value];
|
|
2206
|
-
return void 0;
|
|
2207
|
-
}
|
|
2208
|
-
var SPACING_OPTIONS = SPACING_TOKEN_NAMES.map((name) => ({ label: name, value: name }));
|
|
2209
|
-
var ALIGN_OPTIONS = CONTAINER_ALIGNMENTS.map((name) => ({ label: name, value: name }));
|
|
2210
|
-
function Container(props) {
|
|
2211
|
-
return /* @__PURE__ */ jsxs("div", { "data-ec-primitive": "Container", children: [
|
|
2212
|
-
props.children,
|
|
2213
|
-
props.outlet
|
|
2214
|
-
] });
|
|
2215
|
-
}
|
|
2216
|
-
var ContainerManifest = defineComponent({
|
|
2217
|
-
id: "base.primitives.Container",
|
|
2218
|
-
namespace: "base.primitives",
|
|
2219
|
-
title: "Container",
|
|
2220
|
-
category: "layout",
|
|
2221
|
-
props: {},
|
|
2222
|
-
// The outlet is filled by page injection, so it is not authored-required.
|
|
2223
|
-
slots: [{ name: "children" }, { name: "outlet" }]
|
|
2224
|
-
});
|
|
2225
|
-
function Stack(props) {
|
|
2226
|
-
const style = {
|
|
2227
|
-
display: "flex",
|
|
2228
|
-
flexDirection: props.direction === "horizontal" ? "row" : "column"
|
|
2229
|
-
};
|
|
2230
|
-
const gap = gapCss(props.gap);
|
|
2231
|
-
if (gap !== void 0) style.gap = gap;
|
|
2232
|
-
const alignItems = alignItemsCss(props.align);
|
|
2233
|
-
if (alignItems !== void 0) style.alignItems = alignItems;
|
|
2234
|
-
return /* @__PURE__ */ jsx("div", { "data-ec-primitive": "Stack", style, children: props.children });
|
|
2235
|
-
}
|
|
2236
|
-
var StackManifest = defineComponent({
|
|
2237
|
-
id: "base.primitives.Stack",
|
|
2238
|
-
namespace: "base.primitives",
|
|
2239
|
-
title: "Stack",
|
|
2240
|
-
category: "layout",
|
|
2241
|
-
props: {
|
|
2242
|
-
direction: {
|
|
2243
|
-
type: "select",
|
|
2244
|
-
context: "prop",
|
|
2245
|
-
default: "vertical",
|
|
2246
|
-
options: [
|
|
2247
|
-
{ value: "vertical", label: "Vertical" },
|
|
2248
|
-
{ value: "horizontal", label: "Horizontal" }
|
|
2249
|
-
],
|
|
2250
|
-
form: { label: "Direction" }
|
|
2251
|
-
},
|
|
2252
|
-
gap: {
|
|
2253
|
-
// A design-token spacing step name (EC-161); legacy numeric gaps still render.
|
|
2254
|
-
type: "select",
|
|
2255
|
-
context: "prop",
|
|
2256
|
-
default: "4",
|
|
2257
|
-
options: SPACING_OPTIONS,
|
|
2258
|
-
form: { label: "Gap" }
|
|
2259
|
-
},
|
|
2260
|
-
align: {
|
|
2261
|
-
type: "select",
|
|
2262
|
-
context: "prop",
|
|
2263
|
-
default: "stretch",
|
|
2264
|
-
options: ALIGN_OPTIONS,
|
|
2265
|
-
form: { label: "Align" }
|
|
2266
|
-
}
|
|
2267
|
-
},
|
|
2268
|
-
slots: [{ name: "children" }],
|
|
2269
|
-
designTokens: ["space.gap"]
|
|
2270
|
-
});
|
|
2271
|
-
function asColumns(value) {
|
|
2272
|
-
if (typeof value !== "number" || !Number.isFinite(value)) return GRID_MIN_COLUMNS;
|
|
2273
|
-
return Math.min(Math.max(GRID_MIN_COLUMNS, Math.floor(value)), GRID_MAX_COLUMNS);
|
|
2274
|
-
}
|
|
2275
|
-
var GRID_MOBILE_BREAKPOINT_PX = 640;
|
|
2276
|
-
var GRID_STACK_CSS = `@media (max-width: ${GRID_MOBILE_BREAKPOINT_PX}px){[data-ec-grid-stack]{grid-template-columns:minmax(0,1fr) !important}}`;
|
|
2277
|
-
function Grid(props) {
|
|
2278
|
-
const style = {
|
|
2279
|
-
display: "grid",
|
|
2280
|
-
gridTemplateColumns: `repeat(${asColumns(props.columns)}, minmax(0, 1fr))`
|
|
2281
|
-
};
|
|
2282
|
-
const gap = gapCss(props.gap);
|
|
2283
|
-
if (gap !== void 0) style.gap = gap;
|
|
2284
|
-
const alignItems = alignItemsCss(props.align);
|
|
2285
|
-
if (alignItems !== void 0) style.alignItems = alignItems;
|
|
2286
|
-
const stacksOnMobile = props.stackOnMobile !== false;
|
|
2287
|
-
return /* @__PURE__ */ jsxs(
|
|
2288
|
-
"div",
|
|
2289
|
-
{
|
|
2290
|
-
"data-ec-primitive": "Grid",
|
|
2291
|
-
...stacksOnMobile ? { "data-ec-grid-stack": "" } : {},
|
|
2292
|
-
style,
|
|
2293
|
-
children: [
|
|
2294
|
-
stacksOnMobile ? /* @__PURE__ */ jsx("style", { children: GRID_STACK_CSS }) : null,
|
|
2295
|
-
props.children
|
|
2296
|
-
]
|
|
2297
|
-
}
|
|
2298
|
-
);
|
|
2299
|
-
}
|
|
2300
|
-
var GridManifest = defineComponent({
|
|
2301
|
-
id: "base.primitives.Grid",
|
|
2302
|
-
namespace: "base.primitives",
|
|
2303
|
-
title: "Grid",
|
|
2304
|
-
category: "layout",
|
|
2305
|
-
props: {
|
|
2306
|
-
columns: {
|
|
2307
|
-
type: "number",
|
|
2308
|
-
context: "prop",
|
|
2309
|
-
default: 2,
|
|
2310
|
-
validation: { min: GRID_MIN_COLUMNS, max: GRID_MAX_COLUMNS },
|
|
2311
|
-
form: { label: "Columns" }
|
|
2312
|
-
},
|
|
2313
|
-
gap: {
|
|
2314
|
-
type: "select",
|
|
2315
|
-
context: "prop",
|
|
2316
|
-
default: "4",
|
|
2317
|
-
options: SPACING_OPTIONS,
|
|
2318
|
-
form: { label: "Gap" }
|
|
2319
|
-
},
|
|
2320
|
-
align: {
|
|
2321
|
-
type: "select",
|
|
2322
|
-
context: "prop",
|
|
2323
|
-
default: "stretch",
|
|
2324
|
-
options: ALIGN_OPTIONS,
|
|
2325
|
-
form: { label: "Align" }
|
|
2326
|
-
},
|
|
2327
|
-
stackOnMobile: {
|
|
2328
|
-
// Responsive default (EC-163): stacking is on unless explicitly opted out.
|
|
2329
|
-
type: "boolean",
|
|
2330
|
-
context: "prop",
|
|
2331
|
-
default: true,
|
|
2332
|
-
form: { label: "Stack on phones" }
|
|
2333
|
-
}
|
|
2334
|
-
},
|
|
2335
|
-
slots: [{ name: "children" }],
|
|
2336
|
-
designTokens: ["space.gap"]
|
|
2337
|
-
});
|
|
2338
|
-
function Text(props) {
|
|
2339
|
-
return /* @__PURE__ */ jsx("p", { "data-ec-primitive": "Text", children: asString(props.value) });
|
|
2340
|
-
}
|
|
2341
|
-
var TextManifest = defineComponent({
|
|
2342
|
-
id: "base.primitives.Text",
|
|
2343
|
-
namespace: "base.primitives",
|
|
2344
|
-
title: "Text",
|
|
2345
|
-
category: "content",
|
|
2346
|
-
props: {
|
|
2347
|
-
value: {
|
|
2348
|
-
type: "text",
|
|
2349
|
-
context: "prop",
|
|
2350
|
-
default: "",
|
|
2351
|
-
// Plain-text content prop, editable in place on the canvas (EC-158).
|
|
2352
|
-
form: { label: "Text", control: "textarea", inlineEditable: true }
|
|
2353
|
-
}
|
|
2354
|
-
},
|
|
2355
|
-
slots: []
|
|
2356
|
-
});
|
|
2357
|
-
function Heading(props) {
|
|
2358
|
-
const level = typeof props.level === "number" && props.level >= 1 && props.level <= 6 ? props.level : 2;
|
|
2359
|
-
const tag = `h${level}`;
|
|
2360
|
-
return createElement(tag, { "data-ec-primitive": "Heading" }, asString(props.text));
|
|
2361
|
-
}
|
|
2362
|
-
var HeadingManifest = defineComponent({
|
|
2363
|
-
id: "base.primitives.Heading",
|
|
2364
|
-
namespace: "base.primitives",
|
|
2365
|
-
title: "Heading",
|
|
2366
|
-
category: "content",
|
|
2367
|
-
props: {
|
|
2368
|
-
text: {
|
|
2369
|
-
type: "text",
|
|
2370
|
-
context: "prop",
|
|
2371
|
-
default: "",
|
|
2372
|
-
// Plain-text content prop, editable in place on the canvas (EC-158).
|
|
2373
|
-
form: { label: "Text", inlineEditable: true }
|
|
2374
|
-
},
|
|
2375
|
-
level: {
|
|
2376
|
-
// A numeric heading level 1–6 (the impl clamps out-of-range to 2).
|
|
2377
|
-
type: "number",
|
|
2378
|
-
context: "prop",
|
|
2379
|
-
default: 2,
|
|
2380
|
-
validation: { min: 1, max: 6 },
|
|
2381
|
-
form: { label: "Level" }
|
|
2382
|
-
}
|
|
2383
|
-
},
|
|
2384
|
-
slots: []
|
|
2385
|
-
});
|
|
2386
|
-
function asPositiveInt(value) {
|
|
2387
|
-
return typeof value === "number" && Number.isInteger(value) && value > 0 ? value : void 0;
|
|
2388
|
-
}
|
|
2389
|
-
function asFocalPoint(value) {
|
|
2390
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
2391
|
-
const point = value;
|
|
2392
|
-
if (typeof point.x === "number" && typeof point.y === "number") {
|
|
2393
|
-
return { x: point.x, y: point.y };
|
|
2394
|
-
}
|
|
2395
|
-
}
|
|
2396
|
-
return null;
|
|
2397
|
-
}
|
|
2398
|
-
function normalizeImageSource(props) {
|
|
2399
|
-
const src = props.src;
|
|
2400
|
-
const asset = src && typeof src === "object" && !Array.isArray(src) ? src : null;
|
|
2401
|
-
const url = asset ? asString(asset.url) : asString(src);
|
|
2402
|
-
const width = asPositiveInt(props.width) ?? (asset ? asPositiveInt(asset.width) : void 0);
|
|
2403
|
-
const height = asPositiveInt(props.height) ?? (asset ? asPositiveInt(asset.height) : void 0);
|
|
2404
|
-
const alt = asString(props.alt) || (asset ? asString(asset.alt) : "");
|
|
2405
|
-
const focalPoint = asset ? asFocalPoint(asset.focalPoint) : null;
|
|
2406
|
-
return { url, width, height, alt, focalPoint };
|
|
2407
|
-
}
|
|
2408
|
-
function focalPointObjectPosition(focalPoint) {
|
|
2409
|
-
return focalPoint ? `${focalPoint.x * 100}% ${focalPoint.y * 100}%` : void 0;
|
|
2410
|
-
}
|
|
2411
|
-
function Image(props) {
|
|
2412
|
-
const { url, width, height, alt, focalPoint } = normalizeImageSource(props);
|
|
2413
|
-
const objectPosition = focalPointObjectPosition(focalPoint);
|
|
2414
|
-
if (url === "") {
|
|
2415
|
-
return /* @__PURE__ */ jsx(
|
|
2416
|
-
"span",
|
|
2417
|
-
{
|
|
2418
|
-
"data-ec-primitive": "Image",
|
|
2419
|
-
"data-ec-image": "empty",
|
|
2420
|
-
role: "img",
|
|
2421
|
-
"aria-label": alt || "No image selected",
|
|
2422
|
-
children: "No image selected"
|
|
2423
|
-
}
|
|
2424
|
-
);
|
|
2425
|
-
}
|
|
2426
|
-
return /* @__PURE__ */ jsx(
|
|
2427
|
-
"img",
|
|
2428
|
-
{
|
|
2429
|
-
"data-ec-primitive": "Image",
|
|
2430
|
-
src: url,
|
|
2431
|
-
alt,
|
|
2432
|
-
...width !== void 0 ? { width } : {},
|
|
2433
|
-
...height !== void 0 ? { height } : {},
|
|
2434
|
-
...objectPosition ? { style: { objectPosition } } : {}
|
|
2435
|
-
}
|
|
2436
|
-
);
|
|
2437
|
-
}
|
|
2438
|
-
var ImageManifest = defineComponent({
|
|
2439
|
-
id: "base.primitives.Image",
|
|
2440
|
-
namespace: "base.primitives",
|
|
2441
|
-
title: "Image",
|
|
2442
|
-
category: "media",
|
|
2443
|
-
props: {
|
|
2444
|
-
// EC-195: `asset`-typed, so the editor picks an uploaded asset (the renderer
|
|
2445
|
-
// resolves the id to a url + intrinsic dimensions at delivery) — a plain URL
|
|
2446
|
-
// string still works (the resolver passes URL-like values through).
|
|
2447
|
-
src: {
|
|
2448
|
-
type: "asset",
|
|
2449
|
-
cardinality: "one",
|
|
2450
|
-
context: "prop",
|
|
2451
|
-
default: "",
|
|
2452
|
-
form: { label: "Image" }
|
|
2453
|
-
},
|
|
2454
|
-
alt: {
|
|
2455
|
-
type: "text",
|
|
2456
|
-
context: "prop",
|
|
2457
|
-
default: "",
|
|
2458
|
-
form: { label: "Alt text" }
|
|
2459
|
-
},
|
|
2460
|
-
width: {
|
|
2461
|
-
type: "number",
|
|
2462
|
-
context: "prop",
|
|
2463
|
-
validation: { min: 1 },
|
|
2464
|
-
form: { label: "Width" }
|
|
2465
|
-
},
|
|
2466
|
-
height: {
|
|
2467
|
-
type: "number",
|
|
2468
|
-
context: "prop",
|
|
2469
|
-
validation: { min: 1 },
|
|
2470
|
-
form: { label: "Height" }
|
|
2471
|
-
},
|
|
2472
|
-
// EC-195: above-the-fold images load eagerly (`next/image priority`) so the
|
|
2473
|
-
// host can opt the hero image out of lazy-loading.
|
|
2474
|
-
priority: {
|
|
2475
|
-
type: "boolean",
|
|
2476
|
-
context: "prop",
|
|
2477
|
-
default: false,
|
|
2478
|
-
form: { label: "Load eagerly (above the fold)" }
|
|
2479
|
-
}
|
|
2480
|
-
},
|
|
2481
|
-
slots: []
|
|
2482
|
-
});
|
|
2483
|
-
function Button(props) {
|
|
2484
|
-
const label = asString(props.label, "Button");
|
|
2485
|
-
if (typeof props.href === "string" && props.href.length > 0) {
|
|
2486
|
-
return /* @__PURE__ */ jsx("a", { "data-ec-primitive": "Button", href: props.href, role: "button", children: label });
|
|
2487
|
-
}
|
|
2488
|
-
return /* @__PURE__ */ jsx("button", { "data-ec-primitive": "Button", type: "button", children: label });
|
|
2489
|
-
}
|
|
2490
|
-
var ButtonManifest = defineComponent({
|
|
2491
|
-
id: "base.primitives.Button",
|
|
2492
|
-
namespace: "base.primitives",
|
|
2493
|
-
title: "Button",
|
|
2494
|
-
category: "content",
|
|
2495
|
-
props: {
|
|
2496
|
-
label: {
|
|
2497
|
-
type: "text",
|
|
2498
|
-
context: "prop",
|
|
2499
|
-
default: "Button",
|
|
2500
|
-
form: { label: "Label" }
|
|
2501
|
-
},
|
|
2502
|
-
href: {
|
|
2503
|
-
type: "text",
|
|
2504
|
-
context: "prop",
|
|
2505
|
-
form: { label: "Link" }
|
|
2506
|
-
}
|
|
2507
|
-
},
|
|
2508
|
-
slots: []
|
|
2509
|
-
});
|
|
2510
|
-
function Repeater(props) {
|
|
2511
|
-
return /* @__PURE__ */ jsx("div", { "data-ec-primitive": "Repeater", children: props.children });
|
|
2512
|
-
}
|
|
2513
|
-
var RepeaterManifest = defineComponent({
|
|
2514
|
-
id: "base.primitives.Repeater",
|
|
2515
|
-
namespace: "base.primitives",
|
|
2516
|
-
title: "Repeater",
|
|
2517
|
-
category: "data",
|
|
2518
|
-
// EC-190 DD7: iterate-over-a-source is a binding construct designed out of v1.
|
|
2519
|
-
// The `items` value (an array) has no static field-def type; it rides node.props
|
|
2520
|
-
// undeclared (passing through the renderer) until the binding/iteration removal in
|
|
2521
|
-
// slice 5 decides Repeater's fate. No editable static prop is exposed meanwhile.
|
|
2522
|
-
props: {},
|
|
2523
|
-
slots: [{ name: "item", label: "Item template", required: true }],
|
|
2524
|
-
iterate: { itemsProp: "items", itemSlot: "item" }
|
|
2525
|
-
});
|
|
2526
|
-
function Switch(props) {
|
|
2527
|
-
const key = asString(props.case);
|
|
2528
|
-
const selected = props[`case:${key}`] ?? props.fallback;
|
|
2529
|
-
return /* @__PURE__ */ jsx("div", { "data-ec-primitive": "Switch", children: selected });
|
|
2530
|
-
}
|
|
2531
|
-
var SwitchManifest = defineComponent({
|
|
2532
|
-
id: "base.primitives.Switch",
|
|
2533
|
-
namespace: "base.primitives",
|
|
2534
|
-
title: "Switch",
|
|
2535
|
-
category: "data",
|
|
2536
|
-
props: {
|
|
2537
|
-
case: {
|
|
2538
|
-
type: "text",
|
|
2539
|
-
context: "prop",
|
|
2540
|
-
default: "",
|
|
2541
|
-
form: { label: "Case" }
|
|
2542
|
-
}
|
|
2543
|
-
},
|
|
2544
|
-
// Named case slots are dynamic; `fallback` renders when no case matches.
|
|
2545
|
-
slots: [{ name: "fallback", label: "Fallback" }]
|
|
2546
|
-
});
|
|
2547
|
-
var PRIMITIVES = [
|
|
2548
|
-
{ manifest: ContainerManifest, implementation: Container },
|
|
2549
|
-
{ manifest: StackManifest, implementation: Stack },
|
|
2550
|
-
{ manifest: GridManifest, implementation: Grid },
|
|
2551
|
-
{ manifest: TextManifest, implementation: Text },
|
|
2552
|
-
{ manifest: HeadingManifest, implementation: Heading },
|
|
2553
|
-
// Rich-text content primitive (EC-159) — content prop is the stored
|
|
2554
|
-
// `@elytracms/rich-text` value, flagged inline-editable.
|
|
2555
|
-
{ manifest: RichTextManifest, implementation: RichText },
|
|
2556
|
-
{ manifest: ImageManifest, implementation: Image },
|
|
2557
|
-
{ manifest: ButtonManifest, implementation: Button },
|
|
2558
|
-
{ manifest: RepeaterManifest, implementation: Repeater },
|
|
2559
|
-
{ manifest: SwitchManifest, implementation: Switch }
|
|
2560
|
-
];
|
|
2561
|
-
function basePrimitives() {
|
|
2562
|
-
const implementations = {};
|
|
2563
|
-
for (const { manifest, implementation } of PRIMITIVES) {
|
|
2564
|
-
implementations[manifest.id] = implementation;
|
|
2565
|
-
}
|
|
2566
|
-
return {
|
|
2567
|
-
manifests: PRIMITIVES.map((p) => p.manifest),
|
|
2568
|
-
implementations
|
|
2569
|
-
};
|
|
2570
|
-
}
|
|
1
|
+
import { ComponentRegistry } from '@elytracms/component-registry';
|
|
2
|
+
export { defineComponent } from '@elytracms/component-registry';
|
|
3
|
+
import { basePrimitives, getImplementation, renderCompositionInLayout, renderComposition, ImageManifest, serializeListingQuery, RenderErrorBoundary, collectReferencedAssetIds, collectReferencedRelations, collectReferencedListings, normalizeImageSource } from '@elytracms/runtime-renderer';
|
|
4
|
+
import { isValidElement } from 'react';
|
|
5
|
+
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
6
|
+
import { cacheTags, isSectionSourceId, parseCmsSourceQueryConfig, listQueryOfCmsConfig, isDocumentHistory, resolvedAsset, createContentClient, createContentContext } from '@elytracms/content';
|
|
7
|
+
import { documentKey, splitPath, hierarchyMount, composePath } from '@elytracms/cms-core';
|
|
8
|
+
export { documentSchema, localeConfigSchema, redirectRecordSchema, routeRecordSchema } from '@elytracms/cms-core';
|
|
9
|
+
import { revalidateTag, unstable_cache } from 'next/cache';
|
|
10
|
+
import { draftMode } from 'next/headers';
|
|
11
|
+
import { redirect, notFound, permanentRedirect } from 'next/navigation';
|
|
12
|
+
import { createHmac, timingSafeEqual } from 'crypto';
|
|
13
|
+
import { z } from 'zod';
|
|
14
|
+
export { z } from 'zod';
|
|
15
|
+
import NextImage from 'next/image';
|
|
16
|
+
export { PROJECT_GRAPH_SCHEMA_VERSION, parseProjectGraph } from '@elytracms/project-graph';
|
|
2571
17
|
|
|
2572
18
|
// src/components.ts
|
|
2573
19
|
function defineHostComponents(components = [], options = {}) {
|
|
@@ -2601,7 +47,7 @@ function pathFromToken(token) {
|
|
|
2601
47
|
if (!token.startsWith("/")) return void 0;
|
|
2602
48
|
return token.slice(1).split("/").map(unescapeRefToken);
|
|
2603
49
|
}
|
|
2604
|
-
var
|
|
50
|
+
var isPlainObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2605
51
|
function resolvePayloadToken(payload, token) {
|
|
2606
52
|
const path = pathFromToken(token);
|
|
2607
53
|
if (path === void 0) return void 0;
|
|
@@ -2614,7 +60,7 @@ function resolvePayloadToken(payload, token) {
|
|
|
2614
60
|
current = current[index];
|
|
2615
61
|
continue;
|
|
2616
62
|
}
|
|
2617
|
-
if (
|
|
63
|
+
if (isPlainObject(current)) {
|
|
2618
64
|
if (!Object.prototype.hasOwnProperty.call(current, segment)) return void 0;
|
|
2619
65
|
current = current[segment];
|
|
2620
66
|
continue;
|
|
@@ -2715,996 +161,6 @@ function compositionNodesOf(document) {
|
|
|
2715
161
|
function isComponentNodeValue(value) {
|
|
2716
162
|
return typeof value === "object" && value !== null && typeof value.id === "string" && typeof value.componentId === "string";
|
|
2717
163
|
}
|
|
2718
|
-
z.enum(["draft", "published"]);
|
|
2719
|
-
function createContentContext(init) {
|
|
2720
|
-
return {
|
|
2721
|
-
locale: init.locale,
|
|
2722
|
-
perspective: init.perspective,
|
|
2723
|
-
locales: init.locales
|
|
2724
|
-
};
|
|
2725
|
-
}
|
|
2726
|
-
|
|
2727
|
-
// ../content/src/perspective.ts
|
|
2728
|
-
function isDocumentHistory(source) {
|
|
2729
|
-
return "draft" in source && "versions" in source;
|
|
2730
|
-
}
|
|
2731
|
-
function selectPerspective(context, source) {
|
|
2732
|
-
if (isDocumentHistory(source)) {
|
|
2733
|
-
if (context.perspective === "draft") return source.draft;
|
|
2734
|
-
return source.published ?? null;
|
|
2735
|
-
}
|
|
2736
|
-
if (context.perspective === "draft") return source;
|
|
2737
|
-
return null;
|
|
2738
|
-
}
|
|
2739
|
-
|
|
2740
|
-
// ../content/src/locale.ts
|
|
2741
|
-
function resolveLocaleValues(context, collection, doc, basePath = []) {
|
|
2742
|
-
const values = {};
|
|
2743
|
-
const fallbacks = [];
|
|
2744
|
-
for (const field of collection.fields) {
|
|
2745
|
-
const resolved = resolveField(collection, doc, field.name, context.locale, context.locales);
|
|
2746
|
-
if (resolved.value === void 0) continue;
|
|
2747
|
-
values[field.name] = resolved.value;
|
|
2748
|
-
if (resolved.fellBack) {
|
|
2749
|
-
fallbacks.push({
|
|
2750
|
-
path: [...basePath, field.name],
|
|
2751
|
-
field: field.name,
|
|
2752
|
-
requestedLocale: context.locale,
|
|
2753
|
-
sourceLocale: resolved.sourceLocale
|
|
2754
|
-
});
|
|
2755
|
-
}
|
|
2756
|
-
}
|
|
2757
|
-
return { values, fallbacks };
|
|
2758
|
-
}
|
|
2759
|
-
|
|
2760
|
-
// ../content/src/populate.ts
|
|
2761
|
-
function isPopulatedRelation(field) {
|
|
2762
|
-
return field.populate !== false;
|
|
2763
|
-
}
|
|
2764
|
-
function relationStub(ref) {
|
|
2765
|
-
return { id: ref.id, collection: ref.collection };
|
|
2766
|
-
}
|
|
2767
|
-
function parseRelationRef(value) {
|
|
2768
|
-
const parsed = documentRefSchema.safeParse(value);
|
|
2769
|
-
return parsed.success ? parsed.data : void 0;
|
|
2770
|
-
}
|
|
2771
|
-
|
|
2772
|
-
// ../content/src/assets.ts
|
|
2773
|
-
function resolvedAsset(record, url) {
|
|
2774
|
-
return {
|
|
2775
|
-
id: record.id,
|
|
2776
|
-
url,
|
|
2777
|
-
width: record.width ?? null,
|
|
2778
|
-
height: record.height ?? null,
|
|
2779
|
-
alt: record.alt ?? null,
|
|
2780
|
-
mimeType: record.contentType,
|
|
2781
|
-
focalPoint: record.focalPoint ?? null
|
|
2782
|
-
};
|
|
2783
|
-
}
|
|
2784
|
-
function resolveAssetValue(lookup, assetId, location) {
|
|
2785
|
-
const record = lookup.asset(assetId);
|
|
2786
|
-
if (!record) {
|
|
2787
|
-
return {
|
|
2788
|
-
asset: null,
|
|
2789
|
-
issues: [
|
|
2790
|
-
cmsIssue({
|
|
2791
|
-
code: "unknown-asset",
|
|
2792
|
-
message: `Asset "${assetId}" could not be resolved.`,
|
|
2793
|
-
path: location.path,
|
|
2794
|
-
...location.collectionId ? { collectionId: location.collectionId } : {},
|
|
2795
|
-
...location.documentId ? { documentId: location.documentId } : {},
|
|
2796
|
-
meta: { assetId }
|
|
2797
|
-
})
|
|
2798
|
-
]
|
|
2799
|
-
};
|
|
2800
|
-
}
|
|
2801
|
-
return { asset: resolvedAsset(record, lookup.assetUrl(record)), issues: [] };
|
|
2802
|
-
}
|
|
2803
|
-
|
|
2804
|
-
// ../content/src/resolve.ts
|
|
2805
|
-
function invalidValueIssue(field, collection, documentId, path, expected) {
|
|
2806
|
-
return cmsIssue({
|
|
2807
|
-
code: "invalid-field-value",
|
|
2808
|
-
message: `Field "${field.name}" on "${collection.id}" holds a value that is not ${expected}.`,
|
|
2809
|
-
path,
|
|
2810
|
-
collectionId: collection.id,
|
|
2811
|
-
documentId,
|
|
2812
|
-
meta: { field: field.name, expected }
|
|
2813
|
-
});
|
|
2814
|
-
}
|
|
2815
|
-
function resolveRelationTarget(context, lookup, ref, populate, path, ownerCollectionId, ownerDocumentId, acc) {
|
|
2816
|
-
if (!populate) return { ...relationStub(ref) };
|
|
2817
|
-
const missing = (reason) => {
|
|
2818
|
-
acc.issues.push(
|
|
2819
|
-
cmsIssue({
|
|
2820
|
-
code: "unknown-relation-target",
|
|
2821
|
-
message: `Relation target "${ref.collection}:${ref.id}" could not be populated (${reason}).`,
|
|
2822
|
-
path,
|
|
2823
|
-
collectionId: ownerCollectionId,
|
|
2824
|
-
documentId: ownerDocumentId,
|
|
2825
|
-
meta: { targetCollection: ref.collection, targetId: ref.id, reason }
|
|
2826
|
-
})
|
|
2827
|
-
);
|
|
2828
|
-
return { ...relationStub(ref) };
|
|
2829
|
-
};
|
|
2830
|
-
const targetCollection = lookup.collection(ref.collection);
|
|
2831
|
-
if (!targetCollection) return missing("unknown collection");
|
|
2832
|
-
const source = lookup.document(ref);
|
|
2833
|
-
if (!source) return missing("document not found");
|
|
2834
|
-
const visible = selectPerspective(context, source);
|
|
2835
|
-
if (!visible) return missing(`not visible in ${context.perspective} perspective`);
|
|
2836
|
-
const fields = resolveFields(context, lookup, targetCollection, visible, 1, path, acc);
|
|
2837
|
-
return { id: ref.id, collection: ref.collection, ...fields };
|
|
2838
|
-
}
|
|
2839
|
-
function resolveRelationField(context, lookup, collection, doc, field, value, depth, path, acc) {
|
|
2840
|
-
const populate = depth === 0 && isPopulatedRelation(field);
|
|
2841
|
-
if (field.cardinality === "many") {
|
|
2842
|
-
if (!Array.isArray(value)) {
|
|
2843
|
-
acc.issues.push(invalidValueIssue(field, collection, doc.id, path, "an array of references"));
|
|
2844
|
-
return void 0;
|
|
2845
|
-
}
|
|
2846
|
-
const out = [];
|
|
2847
|
-
value.forEach((entry, index) => {
|
|
2848
|
-
const ref2 = parseRelationRef(entry);
|
|
2849
|
-
if (!ref2) {
|
|
2850
|
-
acc.issues.push(
|
|
2851
|
-
invalidValueIssue(
|
|
2852
|
-
field,
|
|
2853
|
-
collection,
|
|
2854
|
-
doc.id,
|
|
2855
|
-
[...path, index],
|
|
2856
|
-
"a { collection, id } reference"
|
|
2857
|
-
)
|
|
2858
|
-
);
|
|
2859
|
-
return;
|
|
2860
|
-
}
|
|
2861
|
-
out.push(
|
|
2862
|
-
resolveRelationTarget(
|
|
2863
|
-
context,
|
|
2864
|
-
lookup,
|
|
2865
|
-
ref2,
|
|
2866
|
-
populate,
|
|
2867
|
-
[...path, index],
|
|
2868
|
-
collection.id,
|
|
2869
|
-
doc.id,
|
|
2870
|
-
acc
|
|
2871
|
-
)
|
|
2872
|
-
);
|
|
2873
|
-
});
|
|
2874
|
-
return out;
|
|
2875
|
-
}
|
|
2876
|
-
const ref = parseRelationRef(value);
|
|
2877
|
-
if (!ref) {
|
|
2878
|
-
acc.issues.push(
|
|
2879
|
-
invalidValueIssue(field, collection, doc.id, path, "a { collection, id } reference")
|
|
2880
|
-
);
|
|
2881
|
-
return void 0;
|
|
2882
|
-
}
|
|
2883
|
-
return resolveRelationTarget(context, lookup, ref, populate, path, collection.id, doc.id, acc);
|
|
2884
|
-
}
|
|
2885
|
-
function resolveAssetField(lookup, collection, doc, field, value, path, acc) {
|
|
2886
|
-
const resolveOne = (assetId, entryPath) => {
|
|
2887
|
-
if (typeof assetId !== "string" || assetId.length === 0) {
|
|
2888
|
-
acc.issues.push(invalidValueIssue(field, collection, doc.id, entryPath, "an asset id string"));
|
|
2889
|
-
return null;
|
|
2890
|
-
}
|
|
2891
|
-
const resolution = resolveAssetValue(lookup, assetId, {
|
|
2892
|
-
path: entryPath,
|
|
2893
|
-
collectionId: collection.id,
|
|
2894
|
-
documentId: doc.id
|
|
2895
|
-
});
|
|
2896
|
-
acc.issues.push(...resolution.issues);
|
|
2897
|
-
return resolution.asset;
|
|
2898
|
-
};
|
|
2899
|
-
if (field.cardinality === "many") {
|
|
2900
|
-
if (!Array.isArray(value)) {
|
|
2901
|
-
acc.issues.push(invalidValueIssue(field, collection, doc.id, path, "an array of asset ids"));
|
|
2902
|
-
return void 0;
|
|
2903
|
-
}
|
|
2904
|
-
return value.map((entry, index) => resolveOne(entry, [...path, index])).filter((asset) => asset !== null);
|
|
2905
|
-
}
|
|
2906
|
-
return resolveOne(value, path);
|
|
2907
|
-
}
|
|
2908
|
-
function resolveObjectField(context, lookup, collection, doc, field, value, depth, path, acc) {
|
|
2909
|
-
const resolveItem = (item, itemPath) => {
|
|
2910
|
-
if (typeof item !== "object" || item === null || Array.isArray(item)) {
|
|
2911
|
-
acc.issues.push(invalidValueIssue(field, collection, doc.id, itemPath, "an object"));
|
|
2912
|
-
return null;
|
|
2913
|
-
}
|
|
2914
|
-
const record = item;
|
|
2915
|
-
const out = {};
|
|
2916
|
-
for (const sub of field.fields) {
|
|
2917
|
-
const subValue = record[sub.name];
|
|
2918
|
-
if (subValue === void 0) continue;
|
|
2919
|
-
const resolved = resolveFieldValue(
|
|
2920
|
-
context,
|
|
2921
|
-
lookup,
|
|
2922
|
-
collection,
|
|
2923
|
-
doc,
|
|
2924
|
-
sub,
|
|
2925
|
-
subValue,
|
|
2926
|
-
depth,
|
|
2927
|
-
[...itemPath, sub.name],
|
|
2928
|
-
acc
|
|
2929
|
-
);
|
|
2930
|
-
if (resolved !== void 0) out[sub.name] = resolved;
|
|
2931
|
-
}
|
|
2932
|
-
return out;
|
|
2933
|
-
};
|
|
2934
|
-
if (field.cardinality === "many") {
|
|
2935
|
-
if (!Array.isArray(value)) {
|
|
2936
|
-
acc.issues.push(invalidValueIssue(field, collection, doc.id, path, "an array of objects"));
|
|
2937
|
-
return void 0;
|
|
2938
|
-
}
|
|
2939
|
-
return value.map((item, index) => resolveItem(item, [...path, index])).filter((item) => item !== null);
|
|
2940
|
-
}
|
|
2941
|
-
return resolveItem(value, path) ?? void 0;
|
|
2942
|
-
}
|
|
2943
|
-
function resolveFieldValue(context, lookup, collection, doc, field, value, depth, path, acc) {
|
|
2944
|
-
switch (field.type) {
|
|
2945
|
-
case "relation":
|
|
2946
|
-
return resolveRelationField(context, lookup, collection, doc, field, value, depth, path, acc);
|
|
2947
|
-
case "asset":
|
|
2948
|
-
return resolveAssetField(lookup, collection, doc, field, value, path, acc);
|
|
2949
|
-
case "object":
|
|
2950
|
-
return resolveObjectField(context, lookup, collection, doc, field, value, depth, path, acc);
|
|
2951
|
-
default:
|
|
2952
|
-
return value;
|
|
2953
|
-
}
|
|
2954
|
-
}
|
|
2955
|
-
function resolveFields(context, lookup, collection, doc, depth, basePath, acc) {
|
|
2956
|
-
const localeResolution = resolveLocaleValues(context, collection, doc, basePath);
|
|
2957
|
-
acc.fallbacks.push(...localeResolution.fallbacks);
|
|
2958
|
-
const out = {};
|
|
2959
|
-
for (const field of collection.fields) {
|
|
2960
|
-
const value = localeResolution.values[field.name];
|
|
2961
|
-
if (value === void 0) continue;
|
|
2962
|
-
const resolved = resolveFieldValue(
|
|
2963
|
-
context,
|
|
2964
|
-
lookup,
|
|
2965
|
-
collection,
|
|
2966
|
-
doc,
|
|
2967
|
-
field,
|
|
2968
|
-
value,
|
|
2969
|
-
depth,
|
|
2970
|
-
[...basePath, field.name],
|
|
2971
|
-
acc
|
|
2972
|
-
);
|
|
2973
|
-
if (resolved !== void 0) out[field.name] = resolved;
|
|
2974
|
-
}
|
|
2975
|
-
return out;
|
|
2976
|
-
}
|
|
2977
|
-
function resolveDocument(context, lookup, source) {
|
|
2978
|
-
const acc = {
|
|
2979
|
-
fallbacks: [],
|
|
2980
|
-
issues: [...validateLocale(context.locales, context.locale)]
|
|
2981
|
-
};
|
|
2982
|
-
const info = () => ({
|
|
2983
|
-
locale: context.locale,
|
|
2984
|
-
perspective: context.perspective,
|
|
2985
|
-
localeFallbacks: acc.fallbacks
|
|
2986
|
-
});
|
|
2987
|
-
const doc = selectPerspective(context, source);
|
|
2988
|
-
if (!doc) return { document: null, info: info(), issues: acc.issues };
|
|
2989
|
-
const collection = lookup.collection(doc.collection);
|
|
2990
|
-
if (!collection) {
|
|
2991
|
-
acc.issues.push(
|
|
2992
|
-
cmsIssue({
|
|
2993
|
-
code: "unknown-collection",
|
|
2994
|
-
message: `Document "${doc.id}" belongs to unknown collection "${doc.collection}".`,
|
|
2995
|
-
path: ["collection"],
|
|
2996
|
-
collectionId: doc.collection,
|
|
2997
|
-
documentId: doc.id
|
|
2998
|
-
})
|
|
2999
|
-
);
|
|
3000
|
-
return { document: null, info: info(), issues: acc.issues };
|
|
3001
|
-
}
|
|
3002
|
-
const fields = resolveFields(context, lookup, collection, doc, 0, [], acc);
|
|
3003
|
-
return {
|
|
3004
|
-
document: { id: doc.id, collection: doc.collection, ...fields },
|
|
3005
|
-
info: info(),
|
|
3006
|
-
issues: acc.issues
|
|
3007
|
-
};
|
|
3008
|
-
}
|
|
3009
|
-
function populateRelationRef(context, lookup, ref) {
|
|
3010
|
-
const acc = { fallbacks: [], issues: [] };
|
|
3011
|
-
const miss = (reason) => {
|
|
3012
|
-
acc.issues.push(
|
|
3013
|
-
cmsIssue({
|
|
3014
|
-
code: "unknown-relation-target",
|
|
3015
|
-
message: `Relation target "${ref.collection}:${ref.id}" could not be populated (${reason}).`,
|
|
3016
|
-
path: [ref.collection, ref.id],
|
|
3017
|
-
collectionId: ref.collection,
|
|
3018
|
-
documentId: ref.id,
|
|
3019
|
-
meta: { targetCollection: ref.collection, targetId: ref.id, reason }
|
|
3020
|
-
})
|
|
3021
|
-
);
|
|
3022
|
-
return { document: null, issues: acc.issues };
|
|
3023
|
-
};
|
|
3024
|
-
const targetCollection = lookup.collection(ref.collection);
|
|
3025
|
-
if (!targetCollection) return miss("unknown collection");
|
|
3026
|
-
const source = lookup.document(ref);
|
|
3027
|
-
if (!source) return miss("document not found");
|
|
3028
|
-
const visible = selectPerspective(context, source);
|
|
3029
|
-
if (!visible) return miss(`not visible in ${context.perspective} perspective`);
|
|
3030
|
-
const fields = resolveFields(context, lookup, targetCollection, visible, 1, [], acc);
|
|
3031
|
-
return {
|
|
3032
|
-
document: { id: ref.id, collection: ref.collection, ...fields },
|
|
3033
|
-
issues: acc.issues
|
|
3034
|
-
};
|
|
3035
|
-
}
|
|
3036
|
-
|
|
3037
|
-
// ../content/src/query.ts
|
|
3038
|
-
var FILTER_OPERATORS = ["eq", "gt", "gte", "in", "lt", "lte"];
|
|
3039
|
-
function queryError(code, message, path, meta) {
|
|
3040
|
-
return { code, message, path, ...meta ? { meta } : {} };
|
|
3041
|
-
}
|
|
3042
|
-
function isFilterScalar(value) {
|
|
3043
|
-
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
3044
|
-
}
|
|
3045
|
-
function isOperator(key) {
|
|
3046
|
-
return FILTER_OPERATORS.includes(key);
|
|
3047
|
-
}
|
|
3048
|
-
function normalizeWhere(where) {
|
|
3049
|
-
const entries = [];
|
|
3050
|
-
for (const field of Object.keys(where).sort()) {
|
|
3051
|
-
const raw = where[field];
|
|
3052
|
-
if (raw === void 0) continue;
|
|
3053
|
-
if (isFilterScalar(raw)) {
|
|
3054
|
-
entries.push({ field, condition: { eq: raw } });
|
|
3055
|
-
continue;
|
|
3056
|
-
}
|
|
3057
|
-
const condition = {};
|
|
3058
|
-
for (const op of FILTER_OPERATORS) {
|
|
3059
|
-
const value = raw[op];
|
|
3060
|
-
if (value === void 0) continue;
|
|
3061
|
-
condition[op] = value;
|
|
3062
|
-
}
|
|
3063
|
-
entries.push({ field, condition });
|
|
3064
|
-
}
|
|
3065
|
-
return entries;
|
|
3066
|
-
}
|
|
3067
|
-
function fieldByName(collection, name) {
|
|
3068
|
-
return collection.fields.find((f) => f.name === name);
|
|
3069
|
-
}
|
|
3070
|
-
function validateWhere(collection, where) {
|
|
3071
|
-
const errors = [];
|
|
3072
|
-
for (const field of Object.keys(where).sort()) {
|
|
3073
|
-
const raw = where[field];
|
|
3074
|
-
if (raw === void 0) continue;
|
|
3075
|
-
const path = ["where", field];
|
|
3076
|
-
const def = fieldByName(collection, field);
|
|
3077
|
-
if (!def || def.filterable !== true) {
|
|
3078
|
-
errors.push(
|
|
3079
|
-
queryError(
|
|
3080
|
-
"non-filterable-field",
|
|
3081
|
-
def ? `Field "${field}" on "${collection.id}" is not declared filterable.` : `Field "${field}" does not exist on "${collection.id}".`,
|
|
3082
|
-
path,
|
|
3083
|
-
{ collection: collection.id, field }
|
|
3084
|
-
)
|
|
3085
|
-
);
|
|
3086
|
-
continue;
|
|
3087
|
-
}
|
|
3088
|
-
if (isFilterScalar(raw)) continue;
|
|
3089
|
-
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
3090
|
-
errors.push(
|
|
3091
|
-
queryError(
|
|
3092
|
-
"invalid-filter",
|
|
3093
|
-
`Filter for "${field}" must be a scalar or an operator object.`,
|
|
3094
|
-
path
|
|
3095
|
-
)
|
|
3096
|
-
);
|
|
3097
|
-
continue;
|
|
3098
|
-
}
|
|
3099
|
-
const keys = Object.keys(raw).sort();
|
|
3100
|
-
if (keys.length === 0) {
|
|
3101
|
-
errors.push(queryError("invalid-filter", `Filter for "${field}" has no operators.`, path));
|
|
3102
|
-
continue;
|
|
3103
|
-
}
|
|
3104
|
-
for (const key of keys) {
|
|
3105
|
-
const value = raw[key];
|
|
3106
|
-
if (value === void 0) continue;
|
|
3107
|
-
const opPath = ["where", field, key];
|
|
3108
|
-
if (!isOperator(key)) {
|
|
3109
|
-
errors.push(
|
|
3110
|
-
queryError(
|
|
3111
|
-
"invalid-filter",
|
|
3112
|
-
`Unknown filter operator "${key}" on "${field}" \u2014 the vocabulary is exactly ${FILTER_OPERATORS.join(", ")}.`,
|
|
3113
|
-
opPath,
|
|
3114
|
-
{ operator: key }
|
|
3115
|
-
)
|
|
3116
|
-
);
|
|
3117
|
-
continue;
|
|
3118
|
-
}
|
|
3119
|
-
if (key === "in") {
|
|
3120
|
-
if (!Array.isArray(value) || !value.every(isFilterScalar)) {
|
|
3121
|
-
errors.push(
|
|
3122
|
-
queryError(
|
|
3123
|
-
"invalid-filter",
|
|
3124
|
-
`Operator "in" on "${field}" requires an array of scalars.`,
|
|
3125
|
-
opPath
|
|
3126
|
-
)
|
|
3127
|
-
);
|
|
3128
|
-
}
|
|
3129
|
-
continue;
|
|
3130
|
-
}
|
|
3131
|
-
if (!isFilterScalar(value)) {
|
|
3132
|
-
errors.push(
|
|
3133
|
-
queryError(
|
|
3134
|
-
"invalid-filter",
|
|
3135
|
-
`Operator "${key}" on "${field}" requires a scalar value.`,
|
|
3136
|
-
opPath
|
|
3137
|
-
)
|
|
3138
|
-
);
|
|
3139
|
-
}
|
|
3140
|
-
}
|
|
3141
|
-
}
|
|
3142
|
-
return errors;
|
|
3143
|
-
}
|
|
3144
|
-
function validateSort(collection, sort) {
|
|
3145
|
-
const def = fieldByName(collection, sort.field);
|
|
3146
|
-
if (!def || def.filterable !== true) {
|
|
3147
|
-
return [
|
|
3148
|
-
queryError(
|
|
3149
|
-
"invalid-sort",
|
|
3150
|
-
def ? `Sort field "${sort.field}" on "${collection.id}" is not declared filterable.` : `Sort field "${sort.field}" does not exist on "${collection.id}".`,
|
|
3151
|
-
["sort", "field"],
|
|
3152
|
-
{ collection: collection.id, field: sort.field }
|
|
3153
|
-
)
|
|
3154
|
-
];
|
|
3155
|
-
}
|
|
3156
|
-
if (sort.direction !== void 0 && sort.direction !== "asc" && sort.direction !== "desc") {
|
|
3157
|
-
return [
|
|
3158
|
-
queryError("invalid-sort", `Sort direction must be "asc" or "desc".`, ["sort", "direction"])
|
|
3159
|
-
];
|
|
3160
|
-
}
|
|
3161
|
-
return [];
|
|
3162
|
-
}
|
|
3163
|
-
function validateLimit(limit) {
|
|
3164
|
-
if (!Number.isInteger(limit) || limit < 1) {
|
|
3165
|
-
return [queryError("invalid-limit", "Limit must be a positive integer.", ["limit"])];
|
|
3166
|
-
}
|
|
3167
|
-
return [];
|
|
3168
|
-
}
|
|
3169
|
-
function filterScalarOf(value) {
|
|
3170
|
-
if (isFilterScalar(value)) return value;
|
|
3171
|
-
if (value !== null && typeof value === "object" && !Array.isArray(value) && typeof value.id === "string") {
|
|
3172
|
-
return value.id;
|
|
3173
|
-
}
|
|
3174
|
-
return null;
|
|
3175
|
-
}
|
|
3176
|
-
function compares(value, bound) {
|
|
3177
|
-
if (value === null) return null;
|
|
3178
|
-
if (typeof value === "number" && typeof bound === "number") {
|
|
3179
|
-
return value < bound ? -1 : value > bound ? 1 : 0;
|
|
3180
|
-
}
|
|
3181
|
-
if (typeof value === "string" && typeof bound === "string") {
|
|
3182
|
-
return value < bound ? -1 : value > bound ? 1 : 0;
|
|
3183
|
-
}
|
|
3184
|
-
return null;
|
|
3185
|
-
}
|
|
3186
|
-
function matchesFieldValue(value, condition) {
|
|
3187
|
-
if (Array.isArray(value)) {
|
|
3188
|
-
return value.some((element) => matchesCondition(filterScalarOf(element), condition));
|
|
3189
|
-
}
|
|
3190
|
-
return matchesCondition(filterScalarOf(value), condition);
|
|
3191
|
-
}
|
|
3192
|
-
function matchesCondition(value, condition) {
|
|
3193
|
-
for (const op of FILTER_OPERATORS) {
|
|
3194
|
-
const bound = condition[op];
|
|
3195
|
-
if (bound === void 0) continue;
|
|
3196
|
-
switch (op) {
|
|
3197
|
-
case "eq":
|
|
3198
|
-
if (value !== bound) return false;
|
|
3199
|
-
break;
|
|
3200
|
-
case "in":
|
|
3201
|
-
if (value === null || !bound.includes(value)) return false;
|
|
3202
|
-
break;
|
|
3203
|
-
case "lt":
|
|
3204
|
-
case "lte":
|
|
3205
|
-
case "gt":
|
|
3206
|
-
case "gte": {
|
|
3207
|
-
const cmp = compares(value, bound);
|
|
3208
|
-
if (cmp === null) return false;
|
|
3209
|
-
if (op === "lt" && !(cmp < 0)) return false;
|
|
3210
|
-
if (op === "lte" && !(cmp <= 0)) return false;
|
|
3211
|
-
if (op === "gt" && !(cmp > 0)) return false;
|
|
3212
|
-
if (op === "gte" && !(cmp >= 0)) return false;
|
|
3213
|
-
break;
|
|
3214
|
-
}
|
|
3215
|
-
}
|
|
3216
|
-
}
|
|
3217
|
-
return true;
|
|
3218
|
-
}
|
|
3219
|
-
function compareSortValues(a, b) {
|
|
3220
|
-
const rank = (v) => typeof v === "number" ? 0 : typeof v === "string" ? 1 : typeof v === "boolean" ? 2 : 3;
|
|
3221
|
-
const ra = rank(a);
|
|
3222
|
-
const rb = rank(b);
|
|
3223
|
-
if (ra !== rb) return ra - rb;
|
|
3224
|
-
if (typeof a === "number" && typeof b === "number") return a < b ? -1 : a > b ? 1 : 0;
|
|
3225
|
-
if (typeof a === "string" && typeof b === "string") return a < b ? -1 : a > b ? 1 : 0;
|
|
3226
|
-
if (typeof a === "boolean" && typeof b === "boolean") return a === b ? 0 : a ? 1 : -1;
|
|
3227
|
-
return 0;
|
|
3228
|
-
}
|
|
3229
|
-
function compareListEntries(a, b, direction) {
|
|
3230
|
-
const byValue = compareSortValues(a.sortValue, b.sortValue);
|
|
3231
|
-
const directed = direction === "desc" ? -byValue : byValue;
|
|
3232
|
-
if (directed !== 0) return directed;
|
|
3233
|
-
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
3234
|
-
}
|
|
3235
|
-
var LIST_CURSOR_PREFIX = "ec1.";
|
|
3236
|
-
var cursorPayloadSchema = z.object({
|
|
3237
|
-
v: z.literal(1),
|
|
3238
|
-
f: z.string().nullable(),
|
|
3239
|
-
d: z.enum(["asc", "desc"]),
|
|
3240
|
-
s: z.union([z.string(), z.number(), z.boolean(), z.null()]),
|
|
3241
|
-
i: z.string().min(1)
|
|
3242
|
-
});
|
|
3243
|
-
var BASE64URL = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
|
|
3244
|
-
function toBase64Url(bytes) {
|
|
3245
|
-
let out = "";
|
|
3246
|
-
for (let i = 0; i < bytes.length; i += 3) {
|
|
3247
|
-
const a = bytes[i] ?? 0;
|
|
3248
|
-
const b = bytes[i + 1];
|
|
3249
|
-
const c = bytes[i + 2];
|
|
3250
|
-
out += BASE64URL[a >> 2];
|
|
3251
|
-
out += BASE64URL[(a & 3) << 4 | (b ?? 0) >> 4];
|
|
3252
|
-
if (b !== void 0) out += BASE64URL[(b & 15) << 2 | (c ?? 0) >> 6];
|
|
3253
|
-
if (c !== void 0) out += BASE64URL[c & 63];
|
|
3254
|
-
}
|
|
3255
|
-
return out;
|
|
3256
|
-
}
|
|
3257
|
-
function fromBase64Url(text) {
|
|
3258
|
-
const values = [];
|
|
3259
|
-
for (const char of text) {
|
|
3260
|
-
const value = BASE64URL.indexOf(char);
|
|
3261
|
-
if (value === -1) return null;
|
|
3262
|
-
values.push(value);
|
|
3263
|
-
}
|
|
3264
|
-
if (values.length % 4 === 1) return null;
|
|
3265
|
-
const bytes = [];
|
|
3266
|
-
for (let i = 0; i < values.length; i += 4) {
|
|
3267
|
-
const [a, b, c, d] = [values[i], values[i + 1], values[i + 2], values[i + 3]];
|
|
3268
|
-
if (a === void 0 || b === void 0) break;
|
|
3269
|
-
bytes.push(a << 2 | b >> 4);
|
|
3270
|
-
if (c !== void 0) bytes.push((b & 15) << 4 | c >> 2);
|
|
3271
|
-
if (d !== void 0) bytes.push(((c ?? 0) & 3) << 6 | d);
|
|
3272
|
-
}
|
|
3273
|
-
return new Uint8Array(bytes);
|
|
3274
|
-
}
|
|
3275
|
-
function encodeListCursor(cursor) {
|
|
3276
|
-
const payload = JSON.stringify({
|
|
3277
|
-
v: 1,
|
|
3278
|
-
f: cursor.sortField,
|
|
3279
|
-
d: cursor.direction,
|
|
3280
|
-
s: cursor.sortValue,
|
|
3281
|
-
i: cursor.id
|
|
3282
|
-
});
|
|
3283
|
-
return LIST_CURSOR_PREFIX + toBase64Url(new TextEncoder().encode(payload));
|
|
3284
|
-
}
|
|
3285
|
-
function decodeListCursor(text) {
|
|
3286
|
-
if (!text.startsWith(LIST_CURSOR_PREFIX)) return null;
|
|
3287
|
-
const bytes = fromBase64Url(text.slice(LIST_CURSOR_PREFIX.length));
|
|
3288
|
-
if (!bytes) return null;
|
|
3289
|
-
let parsed;
|
|
3290
|
-
try {
|
|
3291
|
-
parsed = JSON.parse(new TextDecoder().decode(bytes));
|
|
3292
|
-
} catch {
|
|
3293
|
-
return null;
|
|
3294
|
-
}
|
|
3295
|
-
const payload = cursorPayloadSchema.safeParse(parsed);
|
|
3296
|
-
if (!payload.success) return null;
|
|
3297
|
-
return {
|
|
3298
|
-
sortField: payload.data.f,
|
|
3299
|
-
direction: payload.data.d,
|
|
3300
|
-
sortValue: payload.data.s,
|
|
3301
|
-
id: payload.data.i
|
|
3302
|
-
};
|
|
3303
|
-
}
|
|
3304
|
-
|
|
3305
|
-
// ../content/src/tags.ts
|
|
3306
|
-
function sortedUnique(values) {
|
|
3307
|
-
return [...new Set(values)].sort();
|
|
3308
|
-
}
|
|
3309
|
-
function cacheTags(init) {
|
|
3310
|
-
return {
|
|
3311
|
-
docs: sortedUnique(init.docs ?? []),
|
|
3312
|
-
collections: sortedUnique(init.collections ?? []),
|
|
3313
|
-
routes: sortedUnique(init.routes ?? []),
|
|
3314
|
-
assets: sortedUnique(init.assets ?? []),
|
|
3315
|
-
locale: init.locale
|
|
3316
|
-
};
|
|
3317
|
-
}
|
|
3318
|
-
function documentCacheKeys(collection, document) {
|
|
3319
|
-
const keys = [documentKey({ collection: document.collection, id: document.id })];
|
|
3320
|
-
for (const field of collection.fields) {
|
|
3321
|
-
if (field.type !== "relation") continue;
|
|
3322
|
-
const value = document[field.name];
|
|
3323
|
-
const entries = Array.isArray(value) ? value : [value];
|
|
3324
|
-
for (const entry of entries) {
|
|
3325
|
-
if (entry !== null && typeof entry === "object" && typeof entry.id === "string" && typeof entry.collection === "string") {
|
|
3326
|
-
const ref = entry;
|
|
3327
|
-
keys.push(documentKey({ collection: ref.collection, id: ref.id }));
|
|
3328
|
-
}
|
|
3329
|
-
}
|
|
3330
|
-
}
|
|
3331
|
-
return keys;
|
|
3332
|
-
}
|
|
3333
|
-
|
|
3334
|
-
// ../content/src/client.ts
|
|
3335
|
-
function withLocale(context, locale) {
|
|
3336
|
-
return locale === void 0 || locale === context.locale ? context : { ...context, locale };
|
|
3337
|
-
}
|
|
3338
|
-
function normalizePath2(url) {
|
|
3339
|
-
return "/" + splitPath(url).join("/");
|
|
3340
|
-
}
|
|
3341
|
-
function materializeRouteDocumentRef(ref, params) {
|
|
3342
|
-
if (!ref.id.startsWith(":")) return { ok: true, ref };
|
|
3343
|
-
const param = ref.id.slice(1);
|
|
3344
|
-
const value = params[param];
|
|
3345
|
-
if (value === void 0 || value.length === 0) return { ok: false, missingParam: param };
|
|
3346
|
-
return { ok: true, ref: { collection: ref.collection, id: value } };
|
|
3347
|
-
}
|
|
3348
|
-
function mountParamName(route) {
|
|
3349
|
-
const last = splitPath(route.pattern).at(-1) ?? "";
|
|
3350
|
-
return last.replace(/^:/, "").replace(/\*$/, "");
|
|
3351
|
-
}
|
|
3352
|
-
function createContentClient(init) {
|
|
3353
|
-
const { lookup, context } = init;
|
|
3354
|
-
const allRoutes = init.routes ?? [];
|
|
3355
|
-
const mounts = allRoutes.map((route) => {
|
|
3356
|
-
const mount = hierarchyMount(route);
|
|
3357
|
-
return mount ? { mount, route } : void 0;
|
|
3358
|
-
}).filter((m) => m !== void 0);
|
|
3359
|
-
const router = createRouter(
|
|
3360
|
-
allRoutes.filter((route) => !isHierarchyTemplate(route)),
|
|
3361
|
-
init.redirects ?? [],
|
|
3362
|
-
{ defaultLocale: context.locales.default }
|
|
3363
|
-
);
|
|
3364
|
-
const resolveBoundDocument = (ctx, ref, issues, docKeys, collections) => {
|
|
3365
|
-
collections.push(ref.collection);
|
|
3366
|
-
const collection = lookup.collection(ref.collection);
|
|
3367
|
-
const source = lookup.document(ref) ?? (collection ? findBySlug(ctx, collection, ref.id) : void 0);
|
|
3368
|
-
if (!source) {
|
|
3369
|
-
issues.push(
|
|
3370
|
-
cmsIssue({
|
|
3371
|
-
code: "unknown-route-target",
|
|
3372
|
-
message: `Route-bound document "${ref.collection}:${ref.id}" was not found.`,
|
|
3373
|
-
path: ["document"],
|
|
3374
|
-
collectionId: ref.collection,
|
|
3375
|
-
documentId: ref.id
|
|
3376
|
-
})
|
|
3377
|
-
);
|
|
3378
|
-
return null;
|
|
3379
|
-
}
|
|
3380
|
-
const result = resolveDocument(ctx, lookup, source);
|
|
3381
|
-
issues.push(...result.issues);
|
|
3382
|
-
if (result.document && collection) {
|
|
3383
|
-
docKeys.push(...documentCacheKeys(collection, result.document));
|
|
3384
|
-
}
|
|
3385
|
-
return result.document;
|
|
3386
|
-
};
|
|
3387
|
-
const resolvePage = (url, locale) => {
|
|
3388
|
-
const ctx = withLocale(context, locale);
|
|
3389
|
-
const path = normalizePath2(url);
|
|
3390
|
-
const resolution = router.resolveUrl(path, ctx.locale);
|
|
3391
|
-
const issues = [];
|
|
3392
|
-
const docKeys = [];
|
|
3393
|
-
const collections = [];
|
|
3394
|
-
const routePaths = [path];
|
|
3395
|
-
if (resolution.status === "redirect") {
|
|
3396
|
-
const chain = router.followRedirects(path);
|
|
3397
|
-
routePaths.push(...chain.chain);
|
|
3398
|
-
return {
|
|
3399
|
-
status: "redirect",
|
|
3400
|
-
route: null,
|
|
3401
|
-
dynamicParams: {},
|
|
3402
|
-
localeFallback: false,
|
|
3403
|
-
redirect: {
|
|
3404
|
-
target: resolution.redirectTarget ?? chain.target,
|
|
3405
|
-
permanent: resolution.permanent ?? chain.permanent
|
|
3406
|
-
},
|
|
3407
|
-
document: null,
|
|
3408
|
-
issues,
|
|
3409
|
-
tags: cacheTags({ routes: routePaths, locale: ctx.locale })
|
|
3410
|
-
};
|
|
3411
|
-
}
|
|
3412
|
-
if (resolution.status === "notFound") {
|
|
3413
|
-
const urlSegments = splitPath(path);
|
|
3414
|
-
for (const { mount, route: route2 } of mounts) {
|
|
3415
|
-
if (mount.locale !== void 0 && mount.locale !== ctx.locale) continue;
|
|
3416
|
-
if (urlSegments.length <= mount.prefix.length) continue;
|
|
3417
|
-
if (!mount.prefix.every((seg, i) => urlSegments[i] === seg)) continue;
|
|
3418
|
-
const collection = lookup.collection(mount.collection);
|
|
3419
|
-
if (!collection || !isHierarchical(collection)) continue;
|
|
3420
|
-
const rest = urlSegments.slice(mount.prefix.length);
|
|
3421
|
-
const source = findByComposedPath(ctx, mount.collection, rest);
|
|
3422
|
-
if (!source) continue;
|
|
3423
|
-
collections.push(mount.collection);
|
|
3424
|
-
const result = resolveDocument(ctx, lookup, source);
|
|
3425
|
-
issues.push(...result.issues);
|
|
3426
|
-
if (!result.document) continue;
|
|
3427
|
-
docKeys.push(...documentCacheKeys(collection, result.document));
|
|
3428
|
-
return {
|
|
3429
|
-
status: "ok",
|
|
3430
|
-
route: route2,
|
|
3431
|
-
dynamicParams: { [mountParamName(route2)]: rest.join("/") },
|
|
3432
|
-
localeFallback: false,
|
|
3433
|
-
redirect: null,
|
|
3434
|
-
document: result.document,
|
|
3435
|
-
issues,
|
|
3436
|
-
tags: cacheTags({ docs: docKeys, collections, routes: routePaths, locale: ctx.locale })
|
|
3437
|
-
};
|
|
3438
|
-
}
|
|
3439
|
-
return {
|
|
3440
|
-
status: "notFound",
|
|
3441
|
-
route: null,
|
|
3442
|
-
dynamicParams: {},
|
|
3443
|
-
localeFallback: resolution.fallback,
|
|
3444
|
-
redirect: null,
|
|
3445
|
-
document: null,
|
|
3446
|
-
issues,
|
|
3447
|
-
tags: cacheTags({ routes: routePaths, locale: ctx.locale })
|
|
3448
|
-
};
|
|
3449
|
-
}
|
|
3450
|
-
const route = resolution.route ?? null;
|
|
3451
|
-
let document = null;
|
|
3452
|
-
let documentMissing = false;
|
|
3453
|
-
if (resolution.documentRef) {
|
|
3454
|
-
const materialized = materializeRouteDocumentRef(
|
|
3455
|
-
resolution.documentRef,
|
|
3456
|
-
resolution.dynamicParams
|
|
3457
|
-
);
|
|
3458
|
-
if (materialized.ok) {
|
|
3459
|
-
document = resolveBoundDocument(ctx, materialized.ref, issues, docKeys, collections);
|
|
3460
|
-
documentMissing = document === null;
|
|
3461
|
-
} else {
|
|
3462
|
-
documentMissing = true;
|
|
3463
|
-
collections.push(resolution.documentRef.collection);
|
|
3464
|
-
issues.push(
|
|
3465
|
-
cmsIssue({
|
|
3466
|
-
code: "unknown-route-target",
|
|
3467
|
-
message: `Route "${route?.id ?? path}" binds its document to URL parameter ":${materialized.missingParam}", which pattern "${route?.pattern ?? path}" does not capture.`,
|
|
3468
|
-
path: ["routes", route?.id ?? path, "document"],
|
|
3469
|
-
collectionId: resolution.documentRef.collection,
|
|
3470
|
-
meta: { param: materialized.missingParam, pattern: route?.pattern }
|
|
3471
|
-
})
|
|
3472
|
-
);
|
|
3473
|
-
}
|
|
3474
|
-
}
|
|
3475
|
-
if (documentMissing) {
|
|
3476
|
-
return {
|
|
3477
|
-
status: "notFound",
|
|
3478
|
-
route: null,
|
|
3479
|
-
dynamicParams: resolution.dynamicParams,
|
|
3480
|
-
localeFallback: resolution.fallback,
|
|
3481
|
-
redirect: null,
|
|
3482
|
-
document: null,
|
|
3483
|
-
issues,
|
|
3484
|
-
tags: cacheTags({ docs: docKeys, collections, routes: routePaths, locale: ctx.locale })
|
|
3485
|
-
};
|
|
3486
|
-
}
|
|
3487
|
-
return {
|
|
3488
|
-
status: "ok",
|
|
3489
|
-
route,
|
|
3490
|
-
dynamicParams: resolution.dynamicParams,
|
|
3491
|
-
localeFallback: resolution.fallback,
|
|
3492
|
-
redirect: null,
|
|
3493
|
-
document,
|
|
3494
|
-
issues,
|
|
3495
|
-
tags: cacheTags({ docs: docKeys, collections, routes: routePaths, locale: ctx.locale })
|
|
3496
|
-
};
|
|
3497
|
-
};
|
|
3498
|
-
const findBySlug = (ctx, collection, slug) => {
|
|
3499
|
-
if (!lookup.documents) return void 0;
|
|
3500
|
-
if (!collection.fields.some((f) => f.name === "slug")) return void 0;
|
|
3501
|
-
const matches = [];
|
|
3502
|
-
for (const source of lookup.documents(collection.id)) {
|
|
3503
|
-
const row = selectPerspective(ctx, source);
|
|
3504
|
-
if (!row) continue;
|
|
3505
|
-
const value = resolveField(collection, row, "slug", ctx.locale, ctx.locales).value;
|
|
3506
|
-
if (value === slug) matches.push({ id: row.id, source });
|
|
3507
|
-
}
|
|
3508
|
-
matches.sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
3509
|
-
return matches[0]?.source;
|
|
3510
|
-
};
|
|
3511
|
-
const findByComposedPath = (ctx, collectionId, segments) => {
|
|
3512
|
-
if (!lookup.documents) return void 0;
|
|
3513
|
-
const rows = [];
|
|
3514
|
-
const sourceById = /* @__PURE__ */ new Map();
|
|
3515
|
-
for (const source of lookup.documents(collectionId)) {
|
|
3516
|
-
const row = selectPerspective(ctx, source);
|
|
3517
|
-
if (!row) continue;
|
|
3518
|
-
rows.push(row);
|
|
3519
|
-
sourceById.set(row.id, source);
|
|
3520
|
-
}
|
|
3521
|
-
const matched = resolveByPath(segments, rows);
|
|
3522
|
-
return matched ? sourceById.get(matched.id) : void 0;
|
|
3523
|
-
};
|
|
3524
|
-
const getDocument = (collectionId, idOrSlug, locale) => {
|
|
3525
|
-
const ctx = withLocale(context, locale);
|
|
3526
|
-
const baseTags = { collections: [collectionId], locale: ctx.locale };
|
|
3527
|
-
const collection = lookup.collection(collectionId);
|
|
3528
|
-
if (!collection) {
|
|
3529
|
-
return {
|
|
3530
|
-
document: null,
|
|
3531
|
-
info: null,
|
|
3532
|
-
issues: [
|
|
3533
|
-
cmsIssue({
|
|
3534
|
-
code: "unknown-collection",
|
|
3535
|
-
message: `Collection "${collectionId}" is not defined.`,
|
|
3536
|
-
path: ["collection"],
|
|
3537
|
-
collectionId
|
|
3538
|
-
})
|
|
3539
|
-
],
|
|
3540
|
-
tags: cacheTags(baseTags)
|
|
3541
|
-
};
|
|
3542
|
-
}
|
|
3543
|
-
const source = lookup.document({ collection: collectionId, id: idOrSlug }) ?? findBySlug(ctx, collection, idOrSlug);
|
|
3544
|
-
if (!source) {
|
|
3545
|
-
return { document: null, info: null, issues: [], tags: cacheTags(baseTags) };
|
|
3546
|
-
}
|
|
3547
|
-
const result = resolveDocument(ctx, lookup, source);
|
|
3548
|
-
const docKeys = result.document ? documentCacheKeys(collection, result.document) : [];
|
|
3549
|
-
return {
|
|
3550
|
-
// The generated per-collection delivery type (EC-142 codegen) is the
|
|
3551
|
-
// static face of what resolveDocument produces for this collection.
|
|
3552
|
-
document: result.document,
|
|
3553
|
-
info: result.info,
|
|
3554
|
-
issues: result.issues,
|
|
3555
|
-
tags: cacheTags({ ...baseTags, docs: docKeys })
|
|
3556
|
-
};
|
|
3557
|
-
};
|
|
3558
|
-
const listDocuments = (collectionId, query = {}) => {
|
|
3559
|
-
const ctx = context;
|
|
3560
|
-
const errorTags = cacheTags({ collections: [collectionId], locale: ctx.locale });
|
|
3561
|
-
const errors = [];
|
|
3562
|
-
const collection = lookup.collection(collectionId);
|
|
3563
|
-
if (!collection) {
|
|
3564
|
-
errors.push(
|
|
3565
|
-
queryError("unknown-collection", `Collection "${collectionId}" is not defined.`, [
|
|
3566
|
-
"collection"
|
|
3567
|
-
])
|
|
3568
|
-
);
|
|
3569
|
-
return { ok: false, errors, tags: errorTags };
|
|
3570
|
-
}
|
|
3571
|
-
if (!lookup.documents) {
|
|
3572
|
-
errors.push(
|
|
3573
|
-
queryError(
|
|
3574
|
-
"list-not-supported",
|
|
3575
|
-
"This content lookup does not provide a `documents` capability.",
|
|
3576
|
-
[]
|
|
3577
|
-
)
|
|
3578
|
-
);
|
|
3579
|
-
return { ok: false, errors, tags: errorTags };
|
|
3580
|
-
}
|
|
3581
|
-
if (query.where) errors.push(...validateWhere(collection, query.where));
|
|
3582
|
-
if (query.sort) errors.push(...validateSort(collection, query.sort));
|
|
3583
|
-
if (query.limit !== void 0) errors.push(...validateLimit(query.limit));
|
|
3584
|
-
const sortField = query.sort?.field ?? null;
|
|
3585
|
-
const direction = query.sort?.direction ?? "asc";
|
|
3586
|
-
let after = null;
|
|
3587
|
-
if (query.cursor !== void 0) {
|
|
3588
|
-
const decoded = decodeListCursor(query.cursor);
|
|
3589
|
-
if (!decoded || decoded.sortField !== sortField || decoded.direction !== direction) {
|
|
3590
|
-
errors.push(
|
|
3591
|
-
queryError(
|
|
3592
|
-
"invalid-cursor",
|
|
3593
|
-
decoded ? "Cursor was minted under a different sort and cannot be reused." : "Cursor is malformed.",
|
|
3594
|
-
["cursor"]
|
|
3595
|
-
)
|
|
3596
|
-
);
|
|
3597
|
-
} else {
|
|
3598
|
-
after = { sortValue: decoded.sortValue, id: decoded.id };
|
|
3599
|
-
}
|
|
3600
|
-
}
|
|
3601
|
-
if (errors.length > 0) return { ok: false, errors, tags: errorTags };
|
|
3602
|
-
const where = normalizeWhere(query.where ?? {});
|
|
3603
|
-
const entries = [];
|
|
3604
|
-
for (const source of lookup.documents(collectionId)) {
|
|
3605
|
-
const row = selectPerspective(ctx, source);
|
|
3606
|
-
if (!row || row.collection !== collectionId) continue;
|
|
3607
|
-
const values = resolveLocaleValues(ctx, collection, row).values;
|
|
3608
|
-
const matches = where.every(
|
|
3609
|
-
(entry) => matchesFieldValue(values[entry.field], entry.condition)
|
|
3610
|
-
);
|
|
3611
|
-
if (!matches) continue;
|
|
3612
|
-
entries.push({
|
|
3613
|
-
source,
|
|
3614
|
-
id: row.id,
|
|
3615
|
-
sortValue: sortField === null ? row.id : filterScalarOf(values[sortField])
|
|
3616
|
-
});
|
|
3617
|
-
}
|
|
3618
|
-
entries.sort(
|
|
3619
|
-
(a, b) => compareListEntries(
|
|
3620
|
-
{ id: a.id, sortValue: a.sortValue },
|
|
3621
|
-
{ id: b.id, sortValue: b.sortValue },
|
|
3622
|
-
direction
|
|
3623
|
-
)
|
|
3624
|
-
);
|
|
3625
|
-
const afterPosition = after;
|
|
3626
|
-
const fromIndex = afterPosition ? entries.findIndex(
|
|
3627
|
-
(entry) => compareListEntries(
|
|
3628
|
-
{ id: entry.id, sortValue: entry.sortValue },
|
|
3629
|
-
afterPosition,
|
|
3630
|
-
direction
|
|
3631
|
-
) > 0
|
|
3632
|
-
) : 0;
|
|
3633
|
-
const start = fromIndex === -1 ? entries.length : fromIndex;
|
|
3634
|
-
const end = query.limit !== void 0 ? start + query.limit : entries.length;
|
|
3635
|
-
const pageEntries = entries.slice(start, end);
|
|
3636
|
-
const hasMore = end < entries.length;
|
|
3637
|
-
const documents = [];
|
|
3638
|
-
const issues = [];
|
|
3639
|
-
const docKeys = [];
|
|
3640
|
-
for (const entry of pageEntries) {
|
|
3641
|
-
const result = resolveDocument(ctx, lookup, entry.source);
|
|
3642
|
-
issues.push(...result.issues);
|
|
3643
|
-
if (result.document) {
|
|
3644
|
-
documents.push(result.document);
|
|
3645
|
-
docKeys.push(...documentCacheKeys(collection, result.document));
|
|
3646
|
-
}
|
|
3647
|
-
}
|
|
3648
|
-
const last = pageEntries[pageEntries.length - 1];
|
|
3649
|
-
const nextCursor = hasMore && last ? encodeListCursor({ sortField, direction, sortValue: last.sortValue, id: last.id }) : null;
|
|
3650
|
-
return {
|
|
3651
|
-
ok: true,
|
|
3652
|
-
// The generated per-collection delivery type (EC-142 codegen) is the
|
|
3653
|
-
// static face of what resolveDocument produces for this collection.
|
|
3654
|
-
documents,
|
|
3655
|
-
nextCursor,
|
|
3656
|
-
issues,
|
|
3657
|
-
tags: cacheTags({
|
|
3658
|
-
docs: docKeys,
|
|
3659
|
-
collections: [collectionId],
|
|
3660
|
-
locale: ctx.locale
|
|
3661
|
-
})
|
|
3662
|
-
};
|
|
3663
|
-
};
|
|
3664
|
-
const resolveRelation = (ref) => populateRelationRef(context, lookup, ref).document;
|
|
3665
|
-
return {
|
|
3666
|
-
context,
|
|
3667
|
-
routerIssues: router.issues,
|
|
3668
|
-
resolvePage,
|
|
3669
|
-
getDocument,
|
|
3670
|
-
listDocuments,
|
|
3671
|
-
resolveRelation
|
|
3672
|
-
};
|
|
3673
|
-
}
|
|
3674
|
-
|
|
3675
|
-
// ../content/src/binding-sources.ts
|
|
3676
|
-
var SECTION_SOURCE_PREFIX = "cms.section.";
|
|
3677
|
-
function isSectionSourceId(sourceId) {
|
|
3678
|
-
return sourceId.startsWith(SECTION_SOURCE_PREFIX);
|
|
3679
|
-
}
|
|
3680
|
-
var isPlainObject3 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3681
|
-
function parseCmsSourceQueryConfig(value) {
|
|
3682
|
-
if (!isPlainObject3(value)) return void 0;
|
|
3683
|
-
const collectionId = value.collectionId;
|
|
3684
|
-
if (typeof collectionId !== "string" || collectionId.length === 0) return void 0;
|
|
3685
|
-
const config = { collectionId };
|
|
3686
|
-
if (typeof value.documentId === "string") config.documentId = value.documentId;
|
|
3687
|
-
if (isPlainObject3(value.filter)) config.filter = value.filter;
|
|
3688
|
-
if (typeof value.limit === "number") config.limit = value.limit;
|
|
3689
|
-
const sort = value.sort;
|
|
3690
|
-
if (isPlainObject3(sort) && typeof sort.field === "string") {
|
|
3691
|
-
const direction = sort.direction;
|
|
3692
|
-
config.sort = {
|
|
3693
|
-
field: sort.field,
|
|
3694
|
-
...direction === "asc" || direction === "desc" ? { direction } : {}
|
|
3695
|
-
};
|
|
3696
|
-
}
|
|
3697
|
-
return config;
|
|
3698
|
-
}
|
|
3699
|
-
function listQueryOfCmsConfig(config) {
|
|
3700
|
-
return {
|
|
3701
|
-
...config.filter !== void 0 && Object.keys(config.filter).length > 0 ? { where: config.filter } : {},
|
|
3702
|
-
...config.sort !== void 0 ? { sort: config.sort } : {},
|
|
3703
|
-
...config.limit !== void 0 ? { limit: config.limit } : {}
|
|
3704
|
-
};
|
|
3705
|
-
}
|
|
3706
|
-
|
|
3707
|
-
// src/source.ts
|
|
3708
164
|
function mergeCacheTags(first, ...rest) {
|
|
3709
165
|
const all = [first, ...rest];
|
|
3710
166
|
return cacheTags({
|
|
@@ -3715,8 +171,6 @@ function mergeCacheTags(first, ...rest) {
|
|
|
3715
171
|
locale: first.locale
|
|
3716
172
|
});
|
|
3717
173
|
}
|
|
3718
|
-
|
|
3719
|
-
// src/source-payloads.ts
|
|
3720
174
|
function collectBindingSourceIds(node, into = /* @__PURE__ */ new Set()) {
|
|
3721
175
|
for (const value of Object.values(node.props ?? {})) {
|
|
3722
176
|
if (value.kind === "binding") into.add(value.binding.sourceId);
|
|
@@ -3758,8 +212,6 @@ function materializeSourcePayloads(input) {
|
|
|
3758
212
|
}
|
|
3759
213
|
return { payloads, tags };
|
|
3760
214
|
}
|
|
3761
|
-
|
|
3762
|
-
// src/snapshot.ts
|
|
3763
215
|
var DEFAULT_LOCALES = { default: "en", locales: ["en"] };
|
|
3764
216
|
function patternKey(pattern) {
|
|
3765
217
|
return "/" + splitPath(pattern).join("/");
|
|
@@ -3866,8 +318,6 @@ async function createContentSnapshot(adapter, projectId, options = {}) {
|
|
|
3866
318
|
assetUrl: (asset) => adapter.blobs.url(asset.storageKey)
|
|
3867
319
|
});
|
|
3868
320
|
}
|
|
3869
|
-
|
|
3870
|
-
// src/route-core.ts
|
|
3871
321
|
function pathFromSlug(slug) {
|
|
3872
322
|
return "/" + (slug ?? []).join("/");
|
|
3873
323
|
}
|
|
@@ -4069,7 +519,7 @@ async function loadCachedEntry(cache, keyParts, baselineTags, load) {
|
|
|
4069
519
|
}
|
|
4070
520
|
|
|
4071
521
|
// src/metadata.ts
|
|
4072
|
-
function
|
|
522
|
+
function str(value) {
|
|
4073
523
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
4074
524
|
}
|
|
4075
525
|
function looksLikeUrl(value) {
|
|
@@ -4082,13 +532,13 @@ function resolveOgImage(value, assets) {
|
|
|
4082
532
|
}
|
|
4083
533
|
function canvasPageMetadata(result, assets = []) {
|
|
4084
534
|
const doc = result.document;
|
|
4085
|
-
const seoTitle =
|
|
4086
|
-
const seoDescription =
|
|
4087
|
-
const seoOgTitle =
|
|
4088
|
-
const seoOgImage = resolveOgImage(
|
|
535
|
+
const seoTitle = str(doc?.["seoTitle"]);
|
|
536
|
+
const seoDescription = str(doc?.["seoDescription"]);
|
|
537
|
+
const seoOgTitle = str(doc?.["seoOgTitle"]);
|
|
538
|
+
const seoOgImage = resolveOgImage(str(doc?.["seoOgImage"]), assets);
|
|
4089
539
|
const seoNoindex = doc?.["seoNoindex"] === true;
|
|
4090
|
-
const seoCanonical =
|
|
4091
|
-
const docTitle =
|
|
540
|
+
const seoCanonical = str(doc?.["seoCanonical"]);
|
|
541
|
+
const docTitle = str(doc?.["name"]) ?? str(doc?.["title"]);
|
|
4092
542
|
const title = seoTitle ?? docTitle;
|
|
4093
543
|
const metadata = {};
|
|
4094
544
|
if (title !== null) metadata.title = title;
|
|
@@ -4179,8 +629,6 @@ function createCanvasRoute(options) {
|
|
|
4179
629
|
};
|
|
4180
630
|
return { Page, generateMetadata };
|
|
4181
631
|
}
|
|
4182
|
-
|
|
4183
|
-
// src/sitemap.ts
|
|
4184
632
|
function paramNamesOf(pattern) {
|
|
4185
633
|
return splitPath(pattern).filter((segment) => segment.startsWith(":")).map((segment) => segment.slice(1));
|
|
4186
634
|
}
|
|
@@ -4479,6 +927,6 @@ function createPreviewDisableRoute() {
|
|
|
4479
927
|
}
|
|
4480
928
|
var PACKAGE = "@elytracms/next";
|
|
4481
929
|
|
|
4482
|
-
export { CanvasRenderer, ElytraImage, PACKAGE,
|
|
930
|
+
export { CanvasRenderer, ElytraImage, PACKAGE, REVALIDATE_SIGNATURE_HEADER, canvasPageMetadata, canvasSitemapEntries, collectBindingSourceIds, collectionCacheTag, createAssetImageLoader, createBindingResolver, createCanvasRoute, createCanvasSitemap, createContentSnapshot, createPreviewDisableRoute, createPreviewRoute, createRevalidateRoute, createStaticContentSource, defaultBindingPayloads, defineHostComponents, docCacheTag, evaluatePreviewRequest, evaluateRevalidateRequest, isSourcePayloadError, loadCachedEntry, materializeSourcePayloads, mergeCacheTags, mergeRouteRecords, nextCacheTags, nextImagePrimitive, pathFromSlug, projectSweepTag, resolveCanvasPage, resolvePayloadToken, revalidatePayloadSchema, routeCacheTag, safeRedirectTarget, scopeCacheTag, signRevalidateBody, sourcePayloadError, splitLocalePath };
|
|
4483
931
|
//# sourceMappingURL=index.js.map
|
|
4484
932
|
//# sourceMappingURL=index.js.map
|