@avocadostudio-ai/shared 0.5.0 → 0.5.1
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/block-manifest.d.ts +18 -0
- package/dist/block-manifest.js +8 -1
- package/dist/blocks/_registry.d.ts +52 -1
- package/dist/blocks/_registry.js +152 -8
- package/dist/blocks/banner.js +2 -2
- package/dist/blocks/card-grid.js +2 -2
- package/dist/blocks/card.js +2 -2
- package/dist/blocks/carousel.js +2 -2
- package/dist/blocks/cta.js +2 -2
- package/dist/blocks/embed.js +2 -2
- package/dist/blocks/faq-accordion.js +2 -2
- package/dist/blocks/feature-grid.js +2 -2
- package/dist/blocks/footer.js +2 -2
- package/dist/blocks/gallery.js +2 -2
- package/dist/blocks/hero.js +2 -2
- package/dist/blocks/quote.js +2 -2
- package/dist/blocks/rich-text.js +2 -2
- package/dist/blocks/site-header.js +2 -2
- package/dist/blocks/stats.js +2 -2
- package/dist/blocks/table.js +2 -2
- package/dist/blocks/tabs.js +2 -2
- package/dist/blocks/testimonials.js +2 -2
- package/dist/blocks/two-column.js +2 -2
- package/dist/blocks/video.js +2 -2
- package/dist/editable-coverage.js +9 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/ops/builders.d.ts +34 -0
- package/dist/ops/builders.js +63 -0
- package/dist/panel-coverage.d.ts +19 -4
- package/dist/panel-coverage.js +0 -0
- package/package.json +2 -2
package/dist/block-manifest.d.ts
CHANGED
|
@@ -15,6 +15,7 @@ export declare const fieldMetaSchema: z.ZodObject<{
|
|
|
15
15
|
imageAlt: "imageAlt";
|
|
16
16
|
color: "color";
|
|
17
17
|
headingLevel: "headingLevel";
|
|
18
|
+
reference: "reference";
|
|
18
19
|
}>;
|
|
19
20
|
label: z.ZodOptional<z.ZodString>;
|
|
20
21
|
inlineEditable: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -34,6 +35,7 @@ export declare const fieldMetaSchema: z.ZodObject<{
|
|
|
34
35
|
}>>;
|
|
35
36
|
}, z.core.$strip>>;
|
|
36
37
|
multiline: z.ZodOptional<z.ZodBoolean>;
|
|
38
|
+
referenceLabelKey: z.ZodOptional<z.ZodString>;
|
|
37
39
|
inline: z.ZodOptional<z.ZodBoolean>;
|
|
38
40
|
decorators: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
39
41
|
name: z.ZodString;
|
|
@@ -57,6 +59,7 @@ export declare const listFieldMetaSchema: z.ZodObject<{
|
|
|
57
59
|
imageAlt: "imageAlt";
|
|
58
60
|
color: "color";
|
|
59
61
|
headingLevel: "headingLevel";
|
|
62
|
+
reference: "reference";
|
|
60
63
|
}>;
|
|
61
64
|
label: z.ZodOptional<z.ZodString>;
|
|
62
65
|
inlineEditable: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -76,6 +79,7 @@ export declare const listFieldMetaSchema: z.ZodObject<{
|
|
|
76
79
|
}>>;
|
|
77
80
|
}, z.core.$strip>>;
|
|
78
81
|
multiline: z.ZodOptional<z.ZodBoolean>;
|
|
82
|
+
referenceLabelKey: z.ZodOptional<z.ZodString>;
|
|
79
83
|
inline: z.ZodOptional<z.ZodBoolean>;
|
|
80
84
|
decorators: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
81
85
|
name: z.ZodString;
|
|
@@ -98,6 +102,7 @@ export declare const listFieldMetaSchema: z.ZodObject<{
|
|
|
98
102
|
imageAlt: "imageAlt";
|
|
99
103
|
color: "color";
|
|
100
104
|
headingLevel: "headingLevel";
|
|
105
|
+
reference: "reference";
|
|
101
106
|
}>;
|
|
102
107
|
label: z.ZodOptional<z.ZodString>;
|
|
103
108
|
inlineEditable: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -117,6 +122,7 @@ export declare const listFieldMetaSchema: z.ZodObject<{
|
|
|
117
122
|
}>>;
|
|
118
123
|
}, z.core.$strip>>;
|
|
119
124
|
multiline: z.ZodOptional<z.ZodBoolean>;
|
|
125
|
+
referenceLabelKey: z.ZodOptional<z.ZodString>;
|
|
120
126
|
inline: z.ZodOptional<z.ZodBoolean>;
|
|
121
127
|
decorators: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
122
128
|
name: z.ZodString;
|
|
@@ -145,6 +151,7 @@ export declare const blockDefinitionSchema: z.ZodObject<{
|
|
|
145
151
|
imageAlt: "imageAlt";
|
|
146
152
|
color: "color";
|
|
147
153
|
headingLevel: "headingLevel";
|
|
154
|
+
reference: "reference";
|
|
148
155
|
}>;
|
|
149
156
|
label: z.ZodOptional<z.ZodString>;
|
|
150
157
|
inlineEditable: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -164,6 +171,7 @@ export declare const blockDefinitionSchema: z.ZodObject<{
|
|
|
164
171
|
}>>;
|
|
165
172
|
}, z.core.$strip>>;
|
|
166
173
|
multiline: z.ZodOptional<z.ZodBoolean>;
|
|
174
|
+
referenceLabelKey: z.ZodOptional<z.ZodString>;
|
|
167
175
|
inline: z.ZodOptional<z.ZodBoolean>;
|
|
168
176
|
decorators: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
169
177
|
name: z.ZodString;
|
|
@@ -187,6 +195,7 @@ export declare const blockDefinitionSchema: z.ZodObject<{
|
|
|
187
195
|
imageAlt: "imageAlt";
|
|
188
196
|
color: "color";
|
|
189
197
|
headingLevel: "headingLevel";
|
|
198
|
+
reference: "reference";
|
|
190
199
|
}>;
|
|
191
200
|
label: z.ZodOptional<z.ZodString>;
|
|
192
201
|
inlineEditable: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -206,6 +215,7 @@ export declare const blockDefinitionSchema: z.ZodObject<{
|
|
|
206
215
|
}>>;
|
|
207
216
|
}, z.core.$strip>>;
|
|
208
217
|
multiline: z.ZodOptional<z.ZodBoolean>;
|
|
218
|
+
referenceLabelKey: z.ZodOptional<z.ZodString>;
|
|
209
219
|
inline: z.ZodOptional<z.ZodBoolean>;
|
|
210
220
|
decorators: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
211
221
|
name: z.ZodString;
|
|
@@ -228,6 +238,7 @@ export declare const blockDefinitionSchema: z.ZodObject<{
|
|
|
228
238
|
imageAlt: "imageAlt";
|
|
229
239
|
color: "color";
|
|
230
240
|
headingLevel: "headingLevel";
|
|
241
|
+
reference: "reference";
|
|
231
242
|
}>;
|
|
232
243
|
label: z.ZodOptional<z.ZodString>;
|
|
233
244
|
inlineEditable: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -247,6 +258,7 @@ export declare const blockDefinitionSchema: z.ZodObject<{
|
|
|
247
258
|
}>>;
|
|
248
259
|
}, z.core.$strip>>;
|
|
249
260
|
multiline: z.ZodOptional<z.ZodBoolean>;
|
|
261
|
+
referenceLabelKey: z.ZodOptional<z.ZodString>;
|
|
250
262
|
inline: z.ZodOptional<z.ZodBoolean>;
|
|
251
263
|
decorators: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
252
264
|
name: z.ZodString;
|
|
@@ -281,6 +293,7 @@ export declare const blockManifestSchema: z.ZodObject<{
|
|
|
281
293
|
imageAlt: "imageAlt";
|
|
282
294
|
color: "color";
|
|
283
295
|
headingLevel: "headingLevel";
|
|
296
|
+
reference: "reference";
|
|
284
297
|
}>;
|
|
285
298
|
label: z.ZodOptional<z.ZodString>;
|
|
286
299
|
inlineEditable: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -300,6 +313,7 @@ export declare const blockManifestSchema: z.ZodObject<{
|
|
|
300
313
|
}>>;
|
|
301
314
|
}, z.core.$strip>>;
|
|
302
315
|
multiline: z.ZodOptional<z.ZodBoolean>;
|
|
316
|
+
referenceLabelKey: z.ZodOptional<z.ZodString>;
|
|
303
317
|
inline: z.ZodOptional<z.ZodBoolean>;
|
|
304
318
|
decorators: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
305
319
|
name: z.ZodString;
|
|
@@ -323,6 +337,7 @@ export declare const blockManifestSchema: z.ZodObject<{
|
|
|
323
337
|
imageAlt: "imageAlt";
|
|
324
338
|
color: "color";
|
|
325
339
|
headingLevel: "headingLevel";
|
|
340
|
+
reference: "reference";
|
|
326
341
|
}>;
|
|
327
342
|
label: z.ZodOptional<z.ZodString>;
|
|
328
343
|
inlineEditable: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -342,6 +357,7 @@ export declare const blockManifestSchema: z.ZodObject<{
|
|
|
342
357
|
}>>;
|
|
343
358
|
}, z.core.$strip>>;
|
|
344
359
|
multiline: z.ZodOptional<z.ZodBoolean>;
|
|
360
|
+
referenceLabelKey: z.ZodOptional<z.ZodString>;
|
|
345
361
|
inline: z.ZodOptional<z.ZodBoolean>;
|
|
346
362
|
decorators: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
347
363
|
name: z.ZodString;
|
|
@@ -364,6 +380,7 @@ export declare const blockManifestSchema: z.ZodObject<{
|
|
|
364
380
|
imageAlt: "imageAlt";
|
|
365
381
|
color: "color";
|
|
366
382
|
headingLevel: "headingLevel";
|
|
383
|
+
reference: "reference";
|
|
367
384
|
}>;
|
|
368
385
|
label: z.ZodOptional<z.ZodString>;
|
|
369
386
|
inlineEditable: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -383,6 +400,7 @@ export declare const blockManifestSchema: z.ZodObject<{
|
|
|
383
400
|
}>>;
|
|
384
401
|
}, z.core.$strip>>;
|
|
385
402
|
multiline: z.ZodOptional<z.ZodBoolean>;
|
|
403
|
+
referenceLabelKey: z.ZodOptional<z.ZodString>;
|
|
386
404
|
inline: z.ZodOptional<z.ZodBoolean>;
|
|
387
405
|
decorators: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
388
406
|
name: z.ZodString;
|
package/dist/block-manifest.js
CHANGED
|
@@ -36,7 +36,7 @@ export const jsonSchemaLikeSchema = z.lazy(() => z.object({
|
|
|
36
36
|
*/
|
|
37
37
|
const FIELD_KINDS = [
|
|
38
38
|
"text", "richtext", "url", "link", "file", "image", "imageAlt",
|
|
39
|
-
"enum", "color", "number", "boolean", "headingLevel"
|
|
39
|
+
"enum", "color", "number", "boolean", "headingLevel", "reference"
|
|
40
40
|
];
|
|
41
41
|
const _everyKindListed = true;
|
|
42
42
|
void _everyKindListed;
|
|
@@ -54,6 +54,13 @@ export const fieldMetaSchema = z.object({
|
|
|
54
54
|
})
|
|
55
55
|
.optional(),
|
|
56
56
|
multiline: z.boolean().optional(),
|
|
57
|
+
/*
|
|
58
|
+
* Travels with the manifest because the panel is what reads it, and the panel
|
|
59
|
+
* is in a different process. A key this schema omits is stripped here, not
|
|
60
|
+
* rejected — so a reference would have arrived in the editor with nothing to
|
|
61
|
+
* label it and no sign that anything had been declared.
|
|
62
|
+
*/
|
|
63
|
+
referenceLabelKey: z.string().optional(),
|
|
57
64
|
inline: z.boolean().optional(),
|
|
58
65
|
decorators: z
|
|
59
66
|
.array(z.object({ name: z.string().min(1), title: z.string().optional() }))
|
|
@@ -18,8 +18,27 @@ export declare function isImagePlaceholder(url: string | undefined | null): bool
|
|
|
18
18
|
* against nothing at all; a `file` can be checked against what the site's asset
|
|
19
19
|
* store actually holds, which is the only way anyone finds out that a menu link
|
|
20
20
|
* has been pointing at a filename with a typo in it since August.
|
|
21
|
+
*
|
|
22
|
+
* `reference` is a *pointer the CMS owns*, and it is not a flavour of `link`.
|
|
23
|
+
* A Storyblok story link is `{ linktype: "story", id: <uuid>, cached_url: "faq" }`
|
|
24
|
+
* and the Delivery API renders it per language — `/faq` on the German page,
|
|
25
|
+
* `/fr/faq` on the French one. Neither string is what the document holds.
|
|
26
|
+
* Contentful entry links and Sanity references have the same shape and the same
|
|
27
|
+
* problem. Declare one as `link` and the projection stops being invertible in
|
|
28
|
+
* both directions at once: the diff reports every reference on every page as
|
|
29
|
+
* changed because the rendered href never equals the stored object, and writing
|
|
30
|
+
* the href back replaces the reference with a hard-coded URL, which silently
|
|
31
|
+
* stops following renames — the one thing the reference was for.
|
|
32
|
+
*
|
|
33
|
+
* So the panel shows it and does not edit it. The value is carried through
|
|
34
|
+
* untouched, the way `avocadoUnknownBlock` carries a rich-text node the pivot
|
|
35
|
+
* cannot model, and `referenceLabelKey` says where to find something a person
|
|
36
|
+
* can read. Re-pointing a reference needs a picker that knows the CMS's own
|
|
37
|
+
* document ids, which only the integration has; until it exists, showing the
|
|
38
|
+
* target and refusing the edit is the honest answer and the corrupting one is
|
|
39
|
+
* a text input.
|
|
21
40
|
*/
|
|
22
|
-
export type FieldKind = "text" | "richtext" | "url" | "link" | "file" | "image" | "imageAlt" | "enum" | "color" | "number" | "boolean" | "headingLevel";
|
|
41
|
+
export type FieldKind = "text" | "richtext" | "url" | "link" | "file" | "image" | "imageAlt" | "enum" | "color" | "number" | "boolean" | "headingLevel" | "reference";
|
|
23
42
|
/** Recommended image dimensions for an image field. */
|
|
24
43
|
export type ImageSpec = {
|
|
25
44
|
aspectRatio: "landscape" | "square" | "portrait";
|
|
@@ -97,6 +116,17 @@ export type FieldMeta = {
|
|
|
97
116
|
* Use this one when there is no element at all.
|
|
98
117
|
*/
|
|
99
118
|
panelOnly?: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* For `kind: "reference"`: the key inside the value holding something a
|
|
121
|
+
* person can read.
|
|
122
|
+
*
|
|
123
|
+
* Optional, because the common spellings are tried anyway — Storyblok's
|
|
124
|
+
* `cached_url`, a `slug`, a `title`, a `name`. Set it when the reference
|
|
125
|
+
* carries a readable key under a name nothing would guess, or when it carries
|
|
126
|
+
* several and the wrong one wins. Purely a display concern: it never selects
|
|
127
|
+
* what is written, because a reference is not written here at all.
|
|
128
|
+
*/
|
|
129
|
+
referenceLabelKey?: string;
|
|
100
130
|
};
|
|
101
131
|
/** Metadata for list-type props (features, items, cards). */
|
|
102
132
|
export type ListFieldMeta = {
|
|
@@ -137,6 +167,14 @@ export type BlockRegistration = {
|
|
|
137
167
|
};
|
|
138
168
|
/** Test seam: forget what has already been warned about. */
|
|
139
169
|
export declare function resetListFieldWarnings(): void;
|
|
170
|
+
/** Whether `type` is currently backed by Avocado's own definition. */
|
|
171
|
+
export declare function isBuiltinBlock(type: string): boolean;
|
|
172
|
+
/**
|
|
173
|
+
* Register one of Avocado's own blocks. Internal — a site uses `registerBlock`,
|
|
174
|
+
* and registering over a built-in name deliberately drops the built-in flag
|
|
175
|
+
* with the built-in schema.
|
|
176
|
+
*/
|
|
177
|
+
export declare function registerBuiltinBlock(type: string, config: BlockRegistration): void;
|
|
140
178
|
export declare function registerBlock(type: string, config: BlockRegistration): void;
|
|
141
179
|
/** Get metadata for a registered block type, or undefined. */
|
|
142
180
|
export declare function getBlockMeta(type: string): BlockMeta | undefined;
|
|
@@ -226,6 +264,19 @@ export declare function isInBlockCatalogue(type: string): boolean;
|
|
|
226
264
|
export declare function catalogueBlockTypes(): string[];
|
|
227
265
|
/** Declared types with no registration behind them — a typo, or a missing import. */
|
|
228
266
|
export declare function undeclaredBlockTypes(): string[];
|
|
267
|
+
/**
|
|
268
|
+
* Something a person can read out of a reference value.
|
|
269
|
+
*
|
|
270
|
+
* A reference is opaque by design — the panel shows it and never rewrites it —
|
|
271
|
+
* but "opaque" must not mean `[object Object]` in the one place a person looks
|
|
272
|
+
* to check which document a CTA points at. Storyblok spells the readable half
|
|
273
|
+
* `cached_url`, Contentful puts a `slug` or `title` on the resolved entry,
|
|
274
|
+
* Sanity hands over a bare `_ref`. Falls back to the id, and then to nothing,
|
|
275
|
+
* which the panel renders as "not set" rather than as a broken label.
|
|
276
|
+
*
|
|
277
|
+
* `referenceLabelKey` wins when the meta declares it.
|
|
278
|
+
*/
|
|
279
|
+
export declare function referenceLabel(value: unknown, labelKey?: string): string;
|
|
229
280
|
export declare function getPropDisplayName(blockType: string | undefined, propKey: string): string;
|
|
230
281
|
export declare function defaultListItemForBlock(type: BlockType, listKey: string): Record<string, unknown> | null;
|
|
231
282
|
/** Base schema — accepts any block type. Used for ingesting external site content with custom blocks. */
|
package/dist/blocks/_registry.js
CHANGED
|
@@ -68,6 +68,72 @@ function unwrapZod(schema) {
|
|
|
68
68
|
}
|
|
69
69
|
return current;
|
|
70
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* The object shapes a list's rows can take.
|
|
73
|
+
*
|
|
74
|
+
* A monomorphic list has one. A polymorphic list — `z.discriminatedUnion`, the
|
|
75
|
+
* shape our own docs tell an integrator to use when rows differ — has one per
|
|
76
|
+
* branch and no `.shape` of its own. Reading `.element.shape` and stopping there
|
|
77
|
+
* declared every correct polymorphic list unbacked, warned about it at startup,
|
|
78
|
+
* and prescribed `z.array(z.object({ … }))`: the one edit that really would
|
|
79
|
+
* break it, because collapsing the union is what stops each row narrowing to its
|
|
80
|
+
* own field set. The panel never used this path — it reads the JSON Schema,
|
|
81
|
+
* where the branches survive as `anyOf` — so the lists worked exactly as
|
|
82
|
+
* declared while their author was being told they could not.
|
|
83
|
+
*/
|
|
84
|
+
function listElementBranches(element) {
|
|
85
|
+
const unwrapped = unwrapZod(element);
|
|
86
|
+
if (!unwrapped)
|
|
87
|
+
return undefined;
|
|
88
|
+
const ownShape = unwrapped.shape;
|
|
89
|
+
if (ownShape)
|
|
90
|
+
return [{ discriminantValues: [], shape: ownShape }];
|
|
91
|
+
const def = unwrapped.def ?? unwrapped._def;
|
|
92
|
+
const options = def?.options;
|
|
93
|
+
if (!Array.isArray(options) || options.length === 0)
|
|
94
|
+
return undefined;
|
|
95
|
+
const discriminator = def?.discriminator;
|
|
96
|
+
const branches = [];
|
|
97
|
+
for (const option of options) {
|
|
98
|
+
const optionShape = unwrapZod(option)?.shape;
|
|
99
|
+
if (!optionShape)
|
|
100
|
+
continue;
|
|
101
|
+
branches.push({
|
|
102
|
+
discriminantValues: discriminator ? literalValuesOf(optionShape[discriminator]) : [],
|
|
103
|
+
shape: optionShape
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
return branches.length > 0 ? branches : undefined;
|
|
107
|
+
}
|
|
108
|
+
/** The values a discriminant field admits, spelled as the strings a row carries. */
|
|
109
|
+
function literalValuesOf(schema) {
|
|
110
|
+
const def = unwrapZod(schema)?.def ?? unwrapZod(schema)?._def;
|
|
111
|
+
if (!def)
|
|
112
|
+
return [];
|
|
113
|
+
if (Array.isArray(def.values))
|
|
114
|
+
return def.values.map(String); // z.literal in Zod 4
|
|
115
|
+
if (def.value !== undefined)
|
|
116
|
+
return [String(def.value)]; // z.literal in Zod 3
|
|
117
|
+
if (def.entries)
|
|
118
|
+
return Object.values(def.entries).map(String); // z.enum
|
|
119
|
+
return [];
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Fill in `required` from the Zod shapes a field set can be backed by.
|
|
123
|
+
*
|
|
124
|
+
* More than one shape means the set spans branches, and a key only one branch
|
|
125
|
+
* declares cannot be required of a row that might be another branch.
|
|
126
|
+
*/
|
|
127
|
+
function deriveRequired(fields, shapes) {
|
|
128
|
+
for (const [key, field] of Object.entries(fields)) {
|
|
129
|
+
if (field.required !== undefined)
|
|
130
|
+
continue;
|
|
131
|
+
const backing = shapes.filter((s) => s[key]);
|
|
132
|
+
if (backing.length === 0)
|
|
133
|
+
continue;
|
|
134
|
+
field.required = backing.length === shapes.length && backing.every((s) => !s[key].isOptional());
|
|
135
|
+
}
|
|
136
|
+
}
|
|
71
137
|
const warnedListFields = new Set();
|
|
72
138
|
function warnUnbackedListField(type, listKey, present) {
|
|
73
139
|
const key = `${type}.${listKey}`;
|
|
@@ -91,8 +157,35 @@ function warnUnbackedListField(type, listKey, present) {
|
|
|
91
157
|
export function resetListFieldWarnings() {
|
|
92
158
|
warnedListFields.clear();
|
|
93
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* The block types Avocado itself defines, as opposed to a site's own.
|
|
162
|
+
*
|
|
163
|
+
* A site that renders its own `Hero` registers it under that name, and from
|
|
164
|
+
* then on the name is all the two shapes have in common. Anything that reasons
|
|
165
|
+
* about a block by hard-coded key — the demo-content migrations in
|
|
166
|
+
* `session-state`, which stamp `imageUrl` and a `left`/`right` pair onto
|
|
167
|
+
* whatever is called `Hero` or `TwoColumn` — is reasoning about *our* schema,
|
|
168
|
+
* and must not run against a schema it has never seen. Registration order
|
|
169
|
+
* settles it: whoever registered last owns the name, so the flag is written on
|
|
170
|
+
* every call rather than only on the first.
|
|
171
|
+
*/
|
|
172
|
+
const _builtinBlockTypes = G.__ase_builtinBlockTypes ?? (G.__ase_builtinBlockTypes = new Set());
|
|
173
|
+
/** Whether `type` is currently backed by Avocado's own definition. */
|
|
174
|
+
export function isBuiltinBlock(type) {
|
|
175
|
+
return _builtinBlockTypes.has(type);
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Register one of Avocado's own blocks. Internal — a site uses `registerBlock`,
|
|
179
|
+
* and registering over a built-in name deliberately drops the built-in flag
|
|
180
|
+
* with the built-in schema.
|
|
181
|
+
*/
|
|
182
|
+
export function registerBuiltinBlock(type, config) {
|
|
183
|
+
registerBlock(type, config);
|
|
184
|
+
_builtinBlockTypes.add(type);
|
|
185
|
+
}
|
|
94
186
|
export function registerBlock(type, config) {
|
|
95
187
|
_blockSchemas[type] = config.schema;
|
|
188
|
+
_builtinBlockTypes.delete(type);
|
|
96
189
|
// Auto-derive `required` on each FieldMeta from the Zod schema shape
|
|
97
190
|
const shape = config.schema.shape;
|
|
98
191
|
if (shape) {
|
|
@@ -112,18 +205,20 @@ export function registerBlock(type, config) {
|
|
|
112
205
|
// Without the outer unwrap, `z.array(...).optional()` — which is how a
|
|
113
206
|
// list should be declared — looked like no array at all, so every
|
|
114
207
|
// optional list silently skipped its own `required` derivation.
|
|
115
|
-
const
|
|
116
|
-
if (!
|
|
208
|
+
const branches = listElementBranches(unwrapZod(listZod)?.element);
|
|
209
|
+
if (!branches) {
|
|
117
210
|
warnUnbackedListField(type, listKey, listZod !== undefined);
|
|
118
211
|
continue;
|
|
119
212
|
}
|
|
120
|
-
|
|
121
|
-
|
|
213
|
+
// `itemFields` is the union of every branch — the set a row is edited
|
|
214
|
+
// against when its discriminant matches nothing — so a key is required
|
|
215
|
+
// there only if every branch has it and no branch makes it optional.
|
|
216
|
+
deriveRequired(listMeta.itemFields, branches.map((b) => b.shape));
|
|
217
|
+
for (const [value, branchFields] of Object.entries(listMeta.itemFieldsByType ?? {})) {
|
|
218
|
+
const matching = branches.filter((b) => b.discriminantValues.includes(value));
|
|
219
|
+
if (matching.length === 0)
|
|
122
220
|
continue;
|
|
123
|
-
|
|
124
|
-
if (zodItem) {
|
|
125
|
-
itemField.required = !zodItem.isOptional();
|
|
126
|
-
}
|
|
221
|
+
deriveRequired(branchFields, matching.map((b) => b.shape));
|
|
127
222
|
}
|
|
128
223
|
}
|
|
129
224
|
}
|
|
@@ -391,6 +486,47 @@ export function undeclaredBlockTypes() {
|
|
|
391
486
|
return [];
|
|
392
487
|
return [...declared].filter((t) => !(t in _blockSchemas));
|
|
393
488
|
}
|
|
489
|
+
/**
|
|
490
|
+
* Something a person can read out of a reference value.
|
|
491
|
+
*
|
|
492
|
+
* A reference is opaque by design — the panel shows it and never rewrites it —
|
|
493
|
+
* but "opaque" must not mean `[object Object]` in the one place a person looks
|
|
494
|
+
* to check which document a CTA points at. Storyblok spells the readable half
|
|
495
|
+
* `cached_url`, Contentful puts a `slug` or `title` on the resolved entry,
|
|
496
|
+
* Sanity hands over a bare `_ref`. Falls back to the id, and then to nothing,
|
|
497
|
+
* which the panel renders as "not set" rather than as a broken label.
|
|
498
|
+
*
|
|
499
|
+
* `referenceLabelKey` wins when the meta declares it.
|
|
500
|
+
*/
|
|
501
|
+
export function referenceLabel(value, labelKey) {
|
|
502
|
+
if (value === null || value === undefined)
|
|
503
|
+
return "";
|
|
504
|
+
if (typeof value === "string" || typeof value === "number")
|
|
505
|
+
return String(value).trim();
|
|
506
|
+
if (typeof value !== "object" || Array.isArray(value))
|
|
507
|
+
return "";
|
|
508
|
+
const rec = value;
|
|
509
|
+
const readable = (key) => {
|
|
510
|
+
const candidate = rec[key];
|
|
511
|
+
return typeof candidate === "string" || typeof candidate === "number" ? String(candidate).trim() : "";
|
|
512
|
+
};
|
|
513
|
+
if (labelKey) {
|
|
514
|
+
const declared = readable(labelKey);
|
|
515
|
+
if (declared !== "")
|
|
516
|
+
return declared;
|
|
517
|
+
}
|
|
518
|
+
for (const key of ["cached_url", "slug", "url", "href", "title", "name", "label"]) {
|
|
519
|
+
const found = readable(key);
|
|
520
|
+
if (found !== "")
|
|
521
|
+
return found;
|
|
522
|
+
}
|
|
523
|
+
for (const key of ["_ref", "id", "_id", "uuid", "entryId"]) {
|
|
524
|
+
const found = readable(key);
|
|
525
|
+
if (found !== "")
|
|
526
|
+
return found;
|
|
527
|
+
}
|
|
528
|
+
return "";
|
|
529
|
+
}
|
|
394
530
|
export function getPropDisplayName(blockType, propKey) {
|
|
395
531
|
if (!blockType)
|
|
396
532
|
return propKey;
|
|
@@ -429,6 +565,14 @@ function defaultScalarForField(field, fieldKey) {
|
|
|
429
565
|
return false;
|
|
430
566
|
if (field.kind === "enum")
|
|
431
567
|
return Array.isArray(field.options) && field.options.length > 0 ? field.options[0] : "";
|
|
568
|
+
/*
|
|
569
|
+
* A reference points at a document in the CMS. There is no default one, and
|
|
570
|
+
* `"New Story link"` in a field the site resolves to a URL is a broken link
|
|
571
|
+
* dressed as content. An added row simply has no reference until somebody
|
|
572
|
+
* sets one upstream.
|
|
573
|
+
*/
|
|
574
|
+
if (field.kind === "reference")
|
|
575
|
+
return null;
|
|
432
576
|
return `New ${label}`;
|
|
433
577
|
}
|
|
434
578
|
export function defaultListItemForBlock(type, listKey) {
|
package/dist/blocks/banner.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { registerBuiltinBlock } from "./_registry.js";
|
|
3
3
|
import { f } from "./_helpers.js";
|
|
4
|
-
|
|
4
|
+
registerBuiltinBlock("Banner", {
|
|
5
5
|
schema: z.object({
|
|
6
6
|
text: z.string().min(1),
|
|
7
7
|
variant: z.enum(["info", "success", "warning"]).default("info").catch("info"),
|
package/dist/blocks/card-grid.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { registerBuiltinBlock } from "./_registry.js";
|
|
3
3
|
import { f } from "./_helpers.js";
|
|
4
|
-
|
|
4
|
+
registerBuiltinBlock("CardGrid", {
|
|
5
5
|
schema: z.object({
|
|
6
6
|
title: z.string().min(1),
|
|
7
7
|
subtitle: z.string().optional(),
|
package/dist/blocks/card.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { registerBuiltinBlock } from "./_registry.js";
|
|
3
3
|
import { f } from "./_helpers.js";
|
|
4
|
-
|
|
4
|
+
registerBuiltinBlock("Card", {
|
|
5
5
|
schema: z.object({
|
|
6
6
|
title: z.string().min(1),
|
|
7
7
|
description: z.string().min(1),
|
package/dist/blocks/carousel.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { registerBuiltinBlock, IMAGE_PLACEHOLDER } from "./_registry.js";
|
|
3
3
|
import { f } from "./_helpers.js";
|
|
4
|
-
|
|
4
|
+
registerBuiltinBlock("Carousel", {
|
|
5
5
|
schema: z.object({
|
|
6
6
|
items: z.array(z.object({
|
|
7
7
|
id: z.string().optional(),
|
package/dist/blocks/cta.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { registerBuiltinBlock } from "./_registry.js";
|
|
3
3
|
import { f } from "./_helpers.js";
|
|
4
|
-
|
|
4
|
+
registerBuiltinBlock("CTA", {
|
|
5
5
|
schema: z.object({
|
|
6
6
|
title: z.string().min(1),
|
|
7
7
|
description: z.string().min(1),
|
package/dist/blocks/embed.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { registerBuiltinBlock } from "./_registry.js";
|
|
3
3
|
import { f } from "./_helpers.js";
|
|
4
|
-
|
|
4
|
+
registerBuiltinBlock("Embed", {
|
|
5
5
|
schema: z.object({
|
|
6
6
|
embedType: z.enum(["map", "social", "custom"]).default("map").catch("map"),
|
|
7
7
|
url: z.string().min(1),
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { registerBuiltinBlock } from "./_registry.js";
|
|
3
3
|
import { f } from "./_helpers.js";
|
|
4
|
-
|
|
4
|
+
registerBuiltinBlock("FAQAccordion", {
|
|
5
5
|
schema: z.object({
|
|
6
6
|
title: z.string().min(1),
|
|
7
7
|
items: z.array(z.object({ id: z.string().optional(), q: z.string().min(1), a: z.string().min(1) })).min(1)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { registerBuiltinBlock } from "./_registry.js";
|
|
3
3
|
import { f } from "./_helpers.js";
|
|
4
|
-
|
|
4
|
+
registerBuiltinBlock("FeatureGrid", {
|
|
5
5
|
schema: z.object({
|
|
6
6
|
title: z.string().min(1),
|
|
7
7
|
columns: z.enum(["2", "3", "4"]).default("3").catch("3"),
|
package/dist/blocks/footer.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { registerBuiltinBlock } from "./_registry.js";
|
|
3
3
|
import { f } from "./_helpers.js";
|
|
4
|
-
|
|
4
|
+
registerBuiltinBlock("Footer", {
|
|
5
5
|
schema: z.object({
|
|
6
6
|
copyright: z.string().min(1),
|
|
7
7
|
columns: z.array(z.object({ id: z.string().optional(), title: z.string().min(1), links: z.string().min(1) })).min(1)
|
package/dist/blocks/gallery.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { registerBuiltinBlock, IMAGE_PLACEHOLDER } from "./_registry.js";
|
|
3
3
|
import { f } from "./_helpers.js";
|
|
4
|
-
|
|
4
|
+
registerBuiltinBlock("Gallery", {
|
|
5
5
|
schema: z.object({
|
|
6
6
|
title: z.string().optional(),
|
|
7
7
|
headingLevel: z.string().optional(),
|
package/dist/blocks/hero.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { registerBuiltinBlock, IMAGE_PLACEHOLDER } from "./_registry.js";
|
|
3
3
|
import { f } from "./_helpers.js";
|
|
4
|
-
|
|
4
|
+
registerBuiltinBlock("Hero", {
|
|
5
5
|
schema: z.object({
|
|
6
6
|
heading: z.string().min(1),
|
|
7
7
|
subheading: z.string().min(1),
|
package/dist/blocks/quote.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { registerBuiltinBlock } from "./_registry.js";
|
|
3
3
|
import { f } from "./_helpers.js";
|
|
4
|
-
|
|
4
|
+
registerBuiltinBlock("Quote", {
|
|
5
5
|
schema: z.object({
|
|
6
6
|
quote: z.string().min(1),
|
|
7
7
|
author: z.string().optional(),
|
package/dist/blocks/rich-text.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { registerBuiltinBlock } from "./_registry.js";
|
|
3
3
|
import { f } from "./_helpers.js";
|
|
4
|
-
|
|
4
|
+
registerBuiltinBlock("RichText", {
|
|
5
5
|
schema: z.object({
|
|
6
6
|
title: z.string(),
|
|
7
7
|
body: z.string().min(1)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { registerBuiltinBlock } from "./_registry.js";
|
|
3
3
|
import { f } from "./_helpers.js";
|
|
4
4
|
const navLinkLeaf = z.object({
|
|
5
5
|
id: z.string().optional(),
|
|
@@ -7,7 +7,7 @@ const navLinkLeaf = z.object({
|
|
|
7
7
|
href: z.string().min(1),
|
|
8
8
|
newTab: z.boolean().optional(),
|
|
9
9
|
});
|
|
10
|
-
|
|
10
|
+
registerBuiltinBlock("SiteHeader", {
|
|
11
11
|
schema: z.object({
|
|
12
12
|
siteName: z.string().min(1),
|
|
13
13
|
logoUrl: z.string().min(1),
|
package/dist/blocks/stats.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { registerBuiltinBlock } from "./_registry.js";
|
|
3
3
|
import { f } from "./_helpers.js";
|
|
4
|
-
|
|
4
|
+
registerBuiltinBlock("Stats", {
|
|
5
5
|
schema: z.object({
|
|
6
6
|
title: z.string().optional(),
|
|
7
7
|
stats: z.array(z.object({
|
package/dist/blocks/table.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { registerBuiltinBlock } from "./_registry.js";
|
|
3
3
|
import { f } from "./_helpers.js";
|
|
4
|
-
|
|
4
|
+
registerBuiltinBlock("Table", {
|
|
5
5
|
schema: z.object({
|
|
6
6
|
title: z.string().optional(),
|
|
7
7
|
headingLevel: z.string().optional(),
|
package/dist/blocks/tabs.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { registerBuiltinBlock } from "./_registry.js";
|
|
3
3
|
import { f } from "./_helpers.js";
|
|
4
|
-
|
|
4
|
+
registerBuiltinBlock("Tabs", {
|
|
5
5
|
schema: z.object({
|
|
6
6
|
title: z.string().optional(),
|
|
7
7
|
tabs: z.array(z.object({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { registerBuiltinBlock } from "./_registry.js";
|
|
3
3
|
import { f } from "./_helpers.js";
|
|
4
|
-
|
|
4
|
+
registerBuiltinBlock("Testimonials", {
|
|
5
5
|
schema: z.object({
|
|
6
6
|
title: z.string().min(1),
|
|
7
7
|
items: z.array(z.object({
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { registerBuiltinBlock, IMAGE_PLACEHOLDER } from "./_registry.js";
|
|
3
3
|
import { f } from "./_helpers.js";
|
|
4
4
|
const twoColumnChild = z.object({
|
|
5
5
|
id: z.string().optional(),
|
|
@@ -62,7 +62,7 @@ const twoColumnItemFieldsByType = {
|
|
|
62
62
|
poster: f.image("Video poster image", { aspectRatio: "landscape", width: 768, height: 512 }),
|
|
63
63
|
},
|
|
64
64
|
};
|
|
65
|
-
|
|
65
|
+
registerBuiltinBlock("TwoColumn", {
|
|
66
66
|
schema: z.object({
|
|
67
67
|
variant: z.enum(["default", "accent"]).default("default").catch("default"),
|
|
68
68
|
left: z.array(twoColumnChild).min(1),
|
package/dist/blocks/video.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import { registerBuiltinBlock } from "./_registry.js";
|
|
3
3
|
import { f } from "./_helpers.js";
|
|
4
|
-
|
|
4
|
+
registerBuiltinBlock("Video", {
|
|
5
5
|
schema: z.object({
|
|
6
6
|
src: z.string().min(1),
|
|
7
7
|
title: z.string().optional(),
|
|
@@ -314,7 +314,15 @@ export function editableCoverage(manifest, blocks) {
|
|
|
314
314
|
for (const [itemKey, itemMeta] of drawnItemFields) {
|
|
315
315
|
const path = `${listKey}[].${itemKey}`;
|
|
316
316
|
expected += 1;
|
|
317
|
-
|
|
317
|
+
/*
|
|
318
|
+
* `isCovered`, not `paths.has`, for the same reason the top-level scan
|
|
319
|
+
* uses it: a row field can itself be a list. `buttons` is marked as
|
|
320
|
+
* `left[].buttons[].label` — the label is the only part of a button a
|
|
321
|
+
* person edits in place — and an exact match called that unmarked,
|
|
322
|
+
* reporting a gap whose only remedy would be a second marker on an
|
|
323
|
+
* element that draws nothing.
|
|
324
|
+
*/
|
|
325
|
+
if (!isCovered(path, paths)) {
|
|
318
326
|
missingItemFields.push(path);
|
|
319
327
|
}
|
|
320
328
|
else if (itemMeta.kind === "image" && voids.has(path)) {
|
package/dist/index.d.ts
CHANGED
|
@@ -12,10 +12,10 @@ export { parseLink, resolveLink, normalizeLinkPath, isKnownRoute, internalPathFo
|
|
|
12
12
|
export { parseInline, parseRichText, parseRichTextBlocks, normalizeRichTextBody, resolveRichTextHeadingLevel, clampMarkdownHeadings, unescapeMarkdownText, isRichTextDoc, fromMarkdown, toMarkdown, mergeRichTextDoc, NODE, MARK, type InlineToken, type RichTextBlock, type RichTextList, type RichTextListItem, type RichTextDoc, type RichTextNode, type RichTextMark } from "@avocadostudio-ai/richtext";
|
|
13
13
|
export { blockDefinitionSchema, blockManifestSchema, buildBlockManifest, jsonSchemaLikeSchema, validateByJsonSchemaLike, findManifestSchemaIssue, type ManifestSchemaIssue, validateManifestDefaultProps, deriveFieldMetaFromSchema, resolveManifestFieldMeta, isProseMirrorDocSchema, type BlockDefinition, type BlockManifest } from "./block-manifest.ts";
|
|
14
14
|
export { z } from "zod";
|
|
15
|
-
export { type FieldKind, type ImageSpec, type FieldMeta, type ListFieldMeta, type BlockMeta, type BlockType, type BlockInstance, type BlockRegistration, IMAGE_PLACEHOLDER, isImagePlaceholder, registerBlock, resetListFieldWarnings, getBlockMeta, getAllBlockMeta, blockAcceptsProp, blockListItemAcceptsKey, getImageFields, getListImageFields, getMediaFields, isFieldInlineEditable, getImageSpec, isChrome, getChromeTypes, blockSchemas, allowedBlockTypes, declareBlockCatalogue, getBlockCatalogue, isInBlockCatalogue, catalogueBlockTypes, undeclaredBlockTypes, getPropDisplayName, defaultListItemForBlock, blockInstanceSchema, blockInstanceSchemaLenient, validateBlockProps, getBlockJsonSchema, } from "./blocks/_registry.ts";
|
|
15
|
+
export { type FieldKind, type ImageSpec, type FieldMeta, type ListFieldMeta, type BlockMeta, type BlockType, type BlockInstance, type BlockRegistration, IMAGE_PLACEHOLDER, isImagePlaceholder, registerBlock, isBuiltinBlock, resetListFieldWarnings, getBlockMeta, getAllBlockMeta, blockAcceptsProp, blockListItemAcceptsKey, getImageFields, getListImageFields, getMediaFields, isFieldInlineEditable, referenceLabel, getImageSpec, isChrome, getChromeTypes, blockSchemas, allowedBlockTypes, declareBlockCatalogue, getBlockCatalogue, isInBlockCatalogue, catalogueBlockTypes, undeclaredBlockTypes, getPropDisplayName, defaultListItemForBlock, blockInstanceSchema, blockInstanceSchemaLenient, validateBlockProps, getBlockJsonSchema, } from "./blocks/_registry.ts";
|
|
16
16
|
export { defaultPropsForType, declaredDefaultPropsForType, resolveHeadingTag, resolveItemHeadingTag, DEFAULT_HEADING_LEVELS, } from "./blocks/index.ts";
|
|
17
17
|
export { blockTypeToCamel, camelToBlockType, blockTypeToLower, lowerToBlockType, } from "./block-names.ts";
|
|
18
|
-
export { makeAddBlock, generateBlockId, makeAddItem, generateItemId, ensureItemIds, type AddBlockOp, type MakeAddBlockOptions, type AddItemOp, type MakeAddItemOptions, } from "./ops/builders.ts";
|
|
18
|
+
export { makeAddBlock, generateBlockId, makeAddItem, generateItemId, ensureItemIds, withoutGeneratedItemIds, isGeneratedItemId, type AddBlockOp, type MakeAddBlockOptions, type AddItemOp, type MakeAddItemOptions, } from "./ops/builders.ts";
|
|
19
19
|
export { THEME_TOKEN_TO_CSS_VARS, themeTokenKeys, semanticThemeTokensSchema, mapSemanticThemeTokens, type ThemeTokenKey, type SemanticThemeTokens, } from "./ops/theme-tokens.ts";
|
|
20
20
|
export { chatStreamEventSchema, parseChatStreamFrame, type ChatStreamEvent, type ChatStreamEventType, type ChatStreamFrame, } from "./chat-events.ts";
|
|
21
21
|
export { type PageMeta, type PageDoc, type SiteConfig, type Operation, type EditPlan, type PatchRejectReason, type ApplyPatchMessage, type PatchAckMessage, type ResetToServerMessage, pageMetaSchema, pageDocSchema, pageDocSchemaLenient, siteConfigSchema, operationSchema, editPlanSchema, demoPublishedPages, demoSiteConfig, } from "./schemas.ts";
|
package/dist/index.js
CHANGED
|
@@ -37,7 +37,7 @@ export {
|
|
|
37
37
|
// Constants & helpers
|
|
38
38
|
IMAGE_PLACEHOLDER, isImagePlaceholder,
|
|
39
39
|
// Registry functions
|
|
40
|
-
registerBlock, resetListFieldWarnings, getBlockMeta, getAllBlockMeta, blockAcceptsProp, blockListItemAcceptsKey, getImageFields, getListImageFields, getMediaFields, isFieldInlineEditable, getImageSpec, isChrome, getChromeTypes,
|
|
40
|
+
registerBlock, isBuiltinBlock, resetListFieldWarnings, getBlockMeta, getAllBlockMeta, blockAcceptsProp, blockListItemAcceptsKey, getImageFields, getListImageFields, getMediaFields, isFieldInlineEditable, referenceLabel, getImageSpec, isChrome, getChromeTypes,
|
|
41
41
|
// Backwards-compatible exports
|
|
42
42
|
blockSchemas, allowedBlockTypes,
|
|
43
43
|
// The catalogue a site actually renders — see `declareBlockCatalogue`
|
|
@@ -48,7 +48,7 @@ getPropDisplayName, defaultListItemForBlock,
|
|
|
48
48
|
blockInstanceSchema, blockInstanceSchemaLenient, validateBlockProps, getBlockJsonSchema, } from "./blocks/_registry.js";
|
|
49
49
|
export { defaultPropsForType, declaredDefaultPropsForType, resolveHeadingTag, resolveItemHeadingTag, DEFAULT_HEADING_LEVELS, } from "./blocks/index.js";
|
|
50
50
|
export { blockTypeToCamel, camelToBlockType, blockTypeToLower, lowerToBlockType, } from "./block-names.js";
|
|
51
|
-
export { makeAddBlock, generateBlockId, makeAddItem, generateItemId, ensureItemIds, } from "./ops/builders.js";
|
|
51
|
+
export { makeAddBlock, generateBlockId, makeAddItem, generateItemId, ensureItemIds, withoutGeneratedItemIds, isGeneratedItemId, } from "./ops/builders.js";
|
|
52
52
|
export { THEME_TOKEN_TO_CSS_VARS, themeTokenKeys, semanticThemeTokensSchema, mapSemanticThemeTokens, } from "./ops/theme-tokens.js";
|
|
53
53
|
export { chatStreamEventSchema, parseChatStreamFrame, } from "./chat-events.js";
|
|
54
54
|
export {
|
package/dist/ops/builders.d.ts
CHANGED
|
@@ -65,3 +65,37 @@ export declare function ensureItemIds(blocks: Array<{
|
|
|
65
65
|
type: string;
|
|
66
66
|
props: unknown;
|
|
67
67
|
}>): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Whether `value` is an id this module generated, rather than one the content
|
|
70
|
+
* arrived with.
|
|
71
|
+
*
|
|
72
|
+
* The shape is the whole point: eight lowercase hex characters behind `i_`. A
|
|
73
|
+
* CMS's own row key — a Sanity `_key`, a Contentful `sys.id`, a Storyblok
|
|
74
|
+
* `_uid` flattened to `id` — does not match, and must not be stripped by
|
|
75
|
+
* anything below.
|
|
76
|
+
*/
|
|
77
|
+
export declare function isGeneratedItemId(value: unknown): boolean;
|
|
78
|
+
/**
|
|
79
|
+
* The same blocks with Avocado's own list-row stamps removed.
|
|
80
|
+
*
|
|
81
|
+
* `ensureItemIds` writes an `id` onto every row of every declared list so the
|
|
82
|
+
* panel can keep rows stable under reordering and a planner can address one by
|
|
83
|
+
* name. It goes into `props`, because `props` is the only thing persisted — and
|
|
84
|
+
* it comes back out of `/draft/pages` indistinguishable from content.
|
|
85
|
+
*
|
|
86
|
+
* An adapter that compares its draft against freshly-read CMS content therefore
|
|
87
|
+
* sees **every block with a list** as changed, permanently and from the first
|
|
88
|
+
* load. On the integration that found this, a one-field edit to one page
|
|
89
|
+
* produced a publish that wanted to rewrite 117 stories; the same publish
|
|
90
|
+
* touches one after this. Diagnosing it took a field-level diff of two JSON
|
|
91
|
+
* blobs, which is why the keys are named here in code rather than described in
|
|
92
|
+
* a document.
|
|
93
|
+
*
|
|
94
|
+
* Returns a deep copy — the draft keeps its ids, which every op still needs.
|
|
95
|
+
* Only ids this module generated are removed, so a row that carries the CMS's
|
|
96
|
+
* own `id` keeps it.
|
|
97
|
+
*/
|
|
98
|
+
export declare function withoutGeneratedItemIds<T extends {
|
|
99
|
+
type: string;
|
|
100
|
+
props: unknown;
|
|
101
|
+
}>(blocks: T[]): T[];
|
package/dist/ops/builders.js
CHANGED
|
@@ -108,6 +108,69 @@ export function ensureItemIds(blocks) {
|
|
|
108
108
|
}
|
|
109
109
|
return changed;
|
|
110
110
|
}
|
|
111
|
+
/**
|
|
112
|
+
* Whether `value` is an id this module generated, rather than one the content
|
|
113
|
+
* arrived with.
|
|
114
|
+
*
|
|
115
|
+
* The shape is the whole point: eight lowercase hex characters behind `i_`. A
|
|
116
|
+
* CMS's own row key — a Sanity `_key`, a Contentful `sys.id`, a Storyblok
|
|
117
|
+
* `_uid` flattened to `id` — does not match, and must not be stripped by
|
|
118
|
+
* anything below.
|
|
119
|
+
*/
|
|
120
|
+
export function isGeneratedItemId(value) {
|
|
121
|
+
return typeof value === "string" && /^i_[0-9a-f]{8}$/.test(value);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* The same blocks with Avocado's own list-row stamps removed.
|
|
125
|
+
*
|
|
126
|
+
* `ensureItemIds` writes an `id` onto every row of every declared list so the
|
|
127
|
+
* panel can keep rows stable under reordering and a planner can address one by
|
|
128
|
+
* name. It goes into `props`, because `props` is the only thing persisted — and
|
|
129
|
+
* it comes back out of `/draft/pages` indistinguishable from content.
|
|
130
|
+
*
|
|
131
|
+
* An adapter that compares its draft against freshly-read CMS content therefore
|
|
132
|
+
* sees **every block with a list** as changed, permanently and from the first
|
|
133
|
+
* load. On the integration that found this, a one-field edit to one page
|
|
134
|
+
* produced a publish that wanted to rewrite 117 stories; the same publish
|
|
135
|
+
* touches one after this. Diagnosing it took a field-level diff of two JSON
|
|
136
|
+
* blobs, which is why the keys are named here in code rather than described in
|
|
137
|
+
* a document.
|
|
138
|
+
*
|
|
139
|
+
* Returns a deep copy — the draft keeps its ids, which every op still needs.
|
|
140
|
+
* Only ids this module generated are removed, so a row that carries the CMS's
|
|
141
|
+
* own `id` keeps it.
|
|
142
|
+
*/
|
|
143
|
+
export function withoutGeneratedItemIds(blocks) {
|
|
144
|
+
return blocks.map((block) => {
|
|
145
|
+
const listFields = getBlockMeta(block.type)?.listFields;
|
|
146
|
+
const props = block.props;
|
|
147
|
+
if (!listFields || !props || typeof props !== "object" || Array.isArray(props))
|
|
148
|
+
return block;
|
|
149
|
+
const nextProps = { ...props };
|
|
150
|
+
let changed = false;
|
|
151
|
+
for (const listKey of Object.keys(listFields)) {
|
|
152
|
+
const list = nextProps[listKey];
|
|
153
|
+
if (!Array.isArray(list))
|
|
154
|
+
continue;
|
|
155
|
+
let listChanged = false;
|
|
156
|
+
const rows = list.map((item) => {
|
|
157
|
+
if (!item || typeof item !== "object" || Array.isArray(item))
|
|
158
|
+
return item;
|
|
159
|
+
const rec = item;
|
|
160
|
+
if (!isGeneratedItemId(rec.id))
|
|
161
|
+
return item;
|
|
162
|
+
const { id: _dropped, ...rest } = rec;
|
|
163
|
+
listChanged = true;
|
|
164
|
+
return rest;
|
|
165
|
+
});
|
|
166
|
+
if (listChanged) {
|
|
167
|
+
nextProps[listKey] = rows;
|
|
168
|
+
changed = true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return changed ? { ...block, props: nextProps } : block;
|
|
172
|
+
});
|
|
173
|
+
}
|
|
111
174
|
function randomSuffix() {
|
|
112
175
|
const c = globalThis.crypto;
|
|
113
176
|
if (c?.randomUUID)
|
package/dist/panel-coverage.d.ts
CHANGED
|
@@ -37,7 +37,7 @@ export type PanelFindingCode =
|
|
|
37
37
|
| "phantom_field"
|
|
38
38
|
/** The type name exists in the editor's own registry with a different shape. */
|
|
39
39
|
| "colliding_type"
|
|
40
|
-
/** An image row labelled by its filename
|
|
40
|
+
/** An image row labelled by its filename because the alt field beside it is empty. */
|
|
41
41
|
| "filename_row_label";
|
|
42
42
|
export type PanelFinding = {
|
|
43
43
|
code: PanelFindingCode;
|
|
@@ -73,14 +73,29 @@ type ManifestLike = {
|
|
|
73
73
|
* The label the panel puts on a collapsed list row.
|
|
74
74
|
*
|
|
75
75
|
* Mirrors `PropertyPanel`'s own derivation exactly, including its fallbacks: the
|
|
76
|
-
* first text-ish field with a value, else
|
|
77
|
-
* `Item N`. Kept here so the two cannot drift
|
|
76
|
+
* first text-ish field with a value, else that image's alt text, else the
|
|
77
|
+
* filename of the first image, else `Item N`. Kept here so the two cannot drift
|
|
78
|
+
* — the panel imports this.
|
|
79
|
+
*
|
|
80
|
+
* "With a value" is load-bearing and was, for a while, only true of the comment.
|
|
81
|
+
* The code took the first *declared* candidate and read whatever it held, so a
|
|
82
|
+
* field set listing an empty `title` ahead of a populated `text` labelled the row
|
|
83
|
+
* `Item 4` with the answer sitting one key further along. Each step below scans
|
|
84
|
+
* for content instead of stopping at the first key of the right kind.
|
|
85
|
+
*
|
|
86
|
+
* Alt text outranks the filename because it is the only one of the two a person
|
|
87
|
+
* wrote on purpose. A column of `20250904_075546.webp`, `20250904_081233.webp`
|
|
88
|
+
* tells a reader which row is which no better than `Item 4` did, while the alt
|
|
89
|
+
* beside it already says "Pool bei Sonnenuntergang". The first version of this
|
|
90
|
+
* ranked the filename higher and the check then reported the mismatch as a
|
|
91
|
+
* finding — which told a site its alt text was the better label while the panel
|
|
92
|
+
* had no way to use it. A finding with no remedy gets switched off.
|
|
78
93
|
*/
|
|
79
94
|
export declare function deriveRowLabel(fields: Record<string, FieldMeta>, item: Record<string, unknown>, index: number, options?: {
|
|
80
95
|
discriminator?: string;
|
|
81
96
|
}): {
|
|
82
97
|
label: string;
|
|
83
|
-
source: "text" | "filename" | "fallback";
|
|
98
|
+
source: "text" | "alt" | "filename" | "fallback";
|
|
84
99
|
};
|
|
85
100
|
export declare function panelCoverage(manifest: ManifestLike, pages: PageLike[], options?: {
|
|
86
101
|
builtinTypes?: Record<string, BlockMeta>;
|
package/dist/panel-coverage.js
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avocadostudio-ai/shared",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"zod": "^4.3.6",
|
|
22
|
-
"@avocadostudio-ai/richtext": "^0.5.
|
|
22
|
+
"@avocadostudio-ai/richtext": "^0.5.1"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"tsx": "^4.21.0",
|