@shapio/schema 0.3.1 → 0.4.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/index.d.ts +36 -9
- package/dist/index.js +181 -73
- package/dist/index.js.map +1 -1
- package/dist/resolve-7-z9VQqw.d.ts +91 -0
- package/dist/resolve-viddCEYd.js +91 -0
- package/dist/resolve-viddCEYd.js.map +1 -0
- package/dist/seoResolve.d.ts +2 -0
- package/dist/seoResolve.js +2 -0
- package/package.json +6 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { _ as STABLE_ID_PATTERN, a as MAX_RICHTEXT_DEPTH, c as BIGINTEGER_PATTERN, d as DECIMAL_PATTERN, f as RICHTEXT_FORMAT, g as UID_PATTERN, h as TIME_PATTERN, i as MARK_RULES, l as DATETIME_PATTERN, m as SLUG_PATTERN, n as richTextMediaIds, o as NODE_RULES, p as RICHTEXT_FORMAT_VERSION, r as validateRichText, s as SAFE_LINK_PATTERN, t as isEmptyRichText, u as DATE_PATTERN, v as createStableId, y as isStableId } from "./validate-BQGutaBo.js";
|
|
2
|
+
import { a as isValidTitleTemplate, c as SEO_COMPONENT_ID, d as SEO_FIELD_IDS, f as SEO_TITLE_PLACEHOLDER, h as SEO_TWITTER_HANDLE_PATTERN, i as applyTitleTemplate, l as SEO_DESCRIPTION_SOFT_MAX, m as SEO_TITLE_TEMPLATE_PATTERN, n as resolveSeo, o as seoDefaultsForLocale, p as SEO_TITLE_SOFT_MAX, r as EMPTY_SEO_DEFAULTS, s as SEO_COMPONENT_API_KEY, t as DEFAULT_SEO_KEYS, u as SEO_EDITOR_ID } from "./resolve-viddCEYd.js";
|
|
2
3
|
import { Type } from "typebox";
|
|
3
4
|
import pluralize from "pluralize";
|
|
4
5
|
import { Value } from "typebox/value";
|
|
@@ -103,7 +104,7 @@ const STORAGE_FAMILY = {
|
|
|
103
104
|
* defaulted properties may be omitted. `normalizeDefinition` turns input into the normalized types below,
|
|
104
105
|
* which are what Shapio stores, hashes and diffs.
|
|
105
106
|
*/
|
|
106
|
-
const closed$
|
|
107
|
+
const closed$3 = { additionalProperties: false };
|
|
107
108
|
const MODEL_KINDS = ["collection", "singleton"];
|
|
108
109
|
const DEFINITION_KINDS = [...MODEL_KINDS, "component"];
|
|
109
110
|
const SORT_DIRECTIONS = ["asc", "desc"];
|
|
@@ -121,7 +122,7 @@ const EditorChoiceInputSchema = Type.Object({
|
|
|
121
122
|
maxLength: 128
|
|
122
123
|
}),
|
|
123
124
|
options: Type.Optional(Type.Record(Type.String(), Type.Unknown()))
|
|
124
|
-
}, closed$
|
|
125
|
+
}, closed$3);
|
|
125
126
|
const FieldInputSchema = Type.Object({
|
|
126
127
|
id: Type.Optional(Type.String()),
|
|
127
128
|
apiKey: Type.String(),
|
|
@@ -144,7 +145,7 @@ const FieldInputSchema = Type.Object({
|
|
|
144
145
|
/** Type-specific settings, validated against SETTINGS_SCHEMAS[type]. */
|
|
145
146
|
settings: Type.Optional(Type.Record(Type.String(), Type.Unknown())),
|
|
146
147
|
editor: Type.Optional(EditorChoiceInputSchema)
|
|
147
|
-
}, closed$
|
|
148
|
+
}, closed$3);
|
|
148
149
|
const FieldGroupSchema = Type.Object({
|
|
149
150
|
id: Type.String({
|
|
150
151
|
minLength: 1,
|
|
@@ -152,7 +153,7 @@ const FieldGroupSchema = Type.Object({
|
|
|
152
153
|
}),
|
|
153
154
|
label: Label,
|
|
154
155
|
fieldIds: Type.Array(Type.String())
|
|
155
|
-
}, closed$
|
|
156
|
+
}, closed$3);
|
|
156
157
|
const ModelDisplayInputSchema = Type.Object({
|
|
157
158
|
/** Field whose value labels an entry in lists, pickers and relation summaries. */
|
|
158
159
|
titleFieldId: Type.Optional(Type.String()),
|
|
@@ -161,7 +162,7 @@ const ModelDisplayInputSchema = Type.Object({
|
|
|
161
162
|
defaultSort: Type.Optional(Type.Object({
|
|
162
163
|
fieldId: Type.String(),
|
|
163
164
|
direction: Type.Enum(SORT_DIRECTIONS)
|
|
164
|
-
}, closed$
|
|
165
|
+
}, closed$3)),
|
|
165
166
|
/** Editor layout: named sections of the entry form. */
|
|
166
167
|
groups: Type.Optional(Type.Array(FieldGroupSchema)),
|
|
167
168
|
/** Fields rendered as blocks in the entry document's canvas, in order. Default: see `effectiveLayout`. */
|
|
@@ -170,11 +171,11 @@ const ModelDisplayInputSchema = Type.Object({
|
|
|
170
171
|
coverFieldId: Type.Optional(Type.String()),
|
|
171
172
|
/** Properties shown as chips in the document's strip. Default: the first non-empty ones. */
|
|
172
173
|
stripFieldIds: Type.Optional(Type.Array(Type.String()))
|
|
173
|
-
}, closed$
|
|
174
|
+
}, closed$3);
|
|
174
175
|
const ComponentDisplayInputSchema = Type.Object({
|
|
175
176
|
titleFieldId: Type.Optional(Type.String()),
|
|
176
177
|
groups: Type.Optional(Type.Array(FieldGroupSchema))
|
|
177
|
-
}, closed$
|
|
178
|
+
}, closed$3);
|
|
178
179
|
const definitionBase = {
|
|
179
180
|
id: Type.Optional(Type.String()),
|
|
180
181
|
apiKey: Type.String(),
|
|
@@ -194,7 +195,7 @@ const ModelDefinitionInputSchema = Type.Object({
|
|
|
194
195
|
/** Entries have separate draft and published states. Default true. */
|
|
195
196
|
draftAndPublish: Type.Optional(Type.Boolean()),
|
|
196
197
|
display: Type.Optional(ModelDisplayInputSchema)
|
|
197
|
-
}, closed$
|
|
198
|
+
}, closed$3);
|
|
198
199
|
const ComponentDefinitionInputSchema = Type.Object({
|
|
199
200
|
...definitionBase,
|
|
200
201
|
kind: Type.Literal("component"),
|
|
@@ -207,7 +208,7 @@ const ComponentDefinitionInputSchema = Type.Object({
|
|
|
207
208
|
maxLength: 64
|
|
208
209
|
})),
|
|
209
210
|
display: Type.Optional(ComponentDisplayInputSchema)
|
|
210
|
-
}, closed$
|
|
211
|
+
}, closed$3);
|
|
211
212
|
const DefinitionInputSchema = Type.Union([ModelDefinitionInputSchema, ComponentDefinitionInputSchema]);
|
|
212
213
|
const isComponentDefinition = (definition) => definition.kind === "component";
|
|
213
214
|
const isModelDefinition = (definition) => definition.kind !== "component";
|
|
@@ -235,7 +236,7 @@ const LocaleInputSchema = Type.Object({
|
|
|
235
236
|
* enum values...). Each schema is closed (`additionalProperties: false`) so a typo in a hand-edited schema
|
|
236
237
|
* file is reported instead of ignored. Defaults are applied by `normalizeDefinition`.
|
|
237
238
|
*/
|
|
238
|
-
const closed$
|
|
239
|
+
const closed$2 = { additionalProperties: false };
|
|
239
240
|
const NonNegativeInt = Type.Integer({ minimum: 0 });
|
|
240
241
|
const pattern = (regex) => Type.String({ pattern: regex.source });
|
|
241
242
|
const lengthRules = {
|
|
@@ -263,7 +264,7 @@ const EnumValueSchema = Type.Object({
|
|
|
263
264
|
minLength: 1,
|
|
264
265
|
maxLength: 200
|
|
265
266
|
})
|
|
266
|
-
}, closed$
|
|
267
|
+
}, closed$2);
|
|
267
268
|
const SETTINGS_SCHEMAS = {
|
|
268
269
|
string: Type.Object({
|
|
269
270
|
...lengthRules,
|
|
@@ -271,20 +272,20 @@ const SETTINGS_SCHEMAS = {
|
|
|
271
272
|
minLength: 1,
|
|
272
273
|
maxLength: 500
|
|
273
274
|
}))
|
|
274
|
-
}, closed$
|
|
275
|
-
text: Type.Object(lengthRules, closed$
|
|
275
|
+
}, closed$2),
|
|
276
|
+
text: Type.Object(lengthRules, closed$2),
|
|
276
277
|
richtext: Type.Object({
|
|
277
278
|
formatVersion: Type.Optional(Type.Integer({ minimum: 1 })),
|
|
278
279
|
maxLength: Type.Optional(Type.Integer({ minimum: 1 }))
|
|
279
|
-
}, closed$
|
|
280
|
+
}, closed$2),
|
|
280
281
|
number: Type.Object({
|
|
281
282
|
min: Type.Optional(Type.Number()),
|
|
282
283
|
max: Type.Optional(Type.Number())
|
|
283
|
-
}, closed$
|
|
284
|
+
}, closed$2),
|
|
284
285
|
integer: Type.Object({
|
|
285
286
|
min: Type.Optional(Type.Integer()),
|
|
286
287
|
max: Type.Optional(Type.Integer())
|
|
287
|
-
}, closed$
|
|
288
|
+
}, closed$2),
|
|
288
289
|
decimal: Type.Object({
|
|
289
290
|
precision: Type.Optional(Type.Integer({
|
|
290
291
|
minimum: 1,
|
|
@@ -296,70 +297,74 @@ const SETTINGS_SCHEMAS = {
|
|
|
296
297
|
})),
|
|
297
298
|
min: Type.Optional(pattern(DECIMAL_PATTERN)),
|
|
298
299
|
max: Type.Optional(pattern(DECIMAL_PATTERN))
|
|
299
|
-
}, closed$
|
|
300
|
+
}, closed$2),
|
|
300
301
|
biginteger: Type.Object({
|
|
301
302
|
min: Type.Optional(pattern(BIGINTEGER_PATTERN)),
|
|
302
303
|
max: Type.Optional(pattern(BIGINTEGER_PATTERN))
|
|
303
|
-
}, closed$
|
|
304
|
-
boolean: Type.Object({}, closed$
|
|
304
|
+
}, closed$2),
|
|
305
|
+
boolean: Type.Object({}, closed$2),
|
|
305
306
|
date: Type.Object({
|
|
306
307
|
min: Type.Optional(pattern(DATE_PATTERN)),
|
|
307
308
|
max: Type.Optional(pattern(DATE_PATTERN))
|
|
308
|
-
}, closed$
|
|
309
|
+
}, closed$2),
|
|
309
310
|
datetime: Type.Object({
|
|
310
311
|
min: Type.Optional(pattern(DATETIME_PATTERN)),
|
|
311
312
|
max: Type.Optional(pattern(DATETIME_PATTERN))
|
|
312
|
-
}, closed$
|
|
313
|
+
}, closed$2),
|
|
313
314
|
time: Type.Object({
|
|
314
315
|
min: Type.Optional(pattern(TIME_PATTERN)),
|
|
315
316
|
max: Type.Optional(pattern(TIME_PATTERN))
|
|
316
|
-
}, closed$
|
|
317
|
+
}, closed$2),
|
|
317
318
|
enum: Type.Object({
|
|
318
319
|
values: Type.Array(EnumValueSchema, {
|
|
319
320
|
minItems: 1,
|
|
320
321
|
maxItems: 500
|
|
321
322
|
}),
|
|
322
323
|
multiple: Type.Optional(Type.Boolean())
|
|
323
|
-
}, closed$
|
|
324
|
-
json: Type.Object({}, closed$
|
|
324
|
+
}, closed$2),
|
|
325
|
+
json: Type.Object({}, closed$2),
|
|
325
326
|
slug: Type.Object({
|
|
326
327
|
...lengthRules,
|
|
327
328
|
sourceFieldId: Type.Optional(Type.String())
|
|
328
|
-
}, closed$
|
|
329
|
-
email: Type.Object({}, closed$
|
|
329
|
+
}, closed$2),
|
|
330
|
+
email: Type.Object({}, closed$2),
|
|
330
331
|
url: Type.Object({ protocols: Type.Optional(Type.Array(Type.Enum([
|
|
331
332
|
"http",
|
|
332
333
|
"https",
|
|
333
334
|
"mailto",
|
|
334
335
|
"tel"
|
|
335
|
-
]), { minItems: 1 })) }, closed$
|
|
336
|
-
uid: Type.Object(lengthRules, closed$
|
|
336
|
+
]), { minItems: 1 })) }, closed$2),
|
|
337
|
+
uid: Type.Object(lengthRules, closed$2),
|
|
337
338
|
media: Type.Object({
|
|
338
339
|
multiple: Type.Optional(Type.Boolean()),
|
|
339
340
|
allowedKinds: Type.Optional(Type.Array(Type.Enum(MEDIA_KINDS), { minItems: 1 })),
|
|
340
341
|
...countRules
|
|
341
|
-
}, closed$
|
|
342
|
+
}, closed$2),
|
|
342
343
|
relation: Type.Object({
|
|
343
344
|
target: Type.String(),
|
|
344
345
|
cardinality: Type.Enum(RELATION_CARDINALITIES),
|
|
345
346
|
...countRules
|
|
346
|
-
}, closed$
|
|
347
|
+
}, closed$2),
|
|
347
348
|
component: Type.Object({
|
|
348
349
|
component: Type.String(),
|
|
349
350
|
repeatable: Type.Optional(Type.Boolean()),
|
|
350
351
|
...countRules
|
|
351
|
-
}, closed$
|
|
352
|
+
}, closed$2),
|
|
352
353
|
dynamiczone: Type.Object({
|
|
353
354
|
components: Type.Array(Type.String(), {
|
|
354
355
|
minItems: 1,
|
|
355
356
|
maxItems: 100
|
|
356
357
|
}),
|
|
357
358
|
...countRules
|
|
358
|
-
}, closed$
|
|
359
|
+
}, closed$2)
|
|
359
360
|
};
|
|
360
361
|
//#endregion
|
|
362
|
+
//#region src/seo/field.ts
|
|
363
|
+
/** Whether a field holds the built-in SEO component (by stable ID, whatever its API ID or editor). */
|
|
364
|
+
const isSeoField = (field) => field.type === "component" && field.settings.component === "f3905a00-d2a3-4c62-899a-c894bfd6cb9b" && !field.settings.repeatable;
|
|
365
|
+
//#endregion
|
|
361
366
|
//#region src/editors/catalogue.ts
|
|
362
|
-
const closed = { additionalProperties: false };
|
|
367
|
+
const closed$1 = { additionalProperties: false };
|
|
363
368
|
const Placeholder = Type.Optional(Type.String({ maxLength: 200 }));
|
|
364
369
|
const isMultipleEnum = (field) => field.type === "enum" && field.settings.multiple;
|
|
365
370
|
const entries = [
|
|
@@ -371,7 +376,7 @@ const entries = [
|
|
|
371
376
|
"url",
|
|
372
377
|
"uid"
|
|
373
378
|
],
|
|
374
|
-
optionsSchema: Type.Object({ placeholder: Placeholder }, closed)
|
|
379
|
+
optionsSchema: Type.Object({ placeholder: Placeholder }, closed$1)
|
|
375
380
|
},
|
|
376
381
|
{
|
|
377
382
|
id: "textarea",
|
|
@@ -382,7 +387,7 @@ const entries = [
|
|
|
382
387
|
minimum: 2,
|
|
383
388
|
maximum: 40
|
|
384
389
|
}))
|
|
385
|
-
}, closed)
|
|
390
|
+
}, closed$1)
|
|
386
391
|
},
|
|
387
392
|
{
|
|
388
393
|
id: "color",
|
|
@@ -390,12 +395,12 @@ const entries = [
|
|
|
390
395
|
optionsSchema: Type.Object({
|
|
391
396
|
format: Type.Optional(Type.Enum(["hex", "rgb"])),
|
|
392
397
|
presets: Type.Optional(Type.Array(Type.String({ maxLength: 32 }), { maxItems: 50 }))
|
|
393
|
-
}, closed)
|
|
398
|
+
}, closed$1)
|
|
394
399
|
},
|
|
395
400
|
{
|
|
396
401
|
id: "richText",
|
|
397
402
|
dataTypes: ["richtext"],
|
|
398
|
-
optionsSchema: Type.Object({ toolbar: Type.Optional(Type.Enum(["full", "minimal"])) }, closed)
|
|
403
|
+
optionsSchema: Type.Object({ toolbar: Type.Optional(Type.Enum(["full", "minimal"])) }, closed$1)
|
|
399
404
|
},
|
|
400
405
|
{
|
|
401
406
|
id: "numberInput",
|
|
@@ -408,17 +413,17 @@ const entries = [
|
|
|
408
413
|
optionsSchema: Type.Object({
|
|
409
414
|
placeholder: Placeholder,
|
|
410
415
|
step: Type.Optional(Type.Number({ exclusiveMinimum: 0 }))
|
|
411
|
-
}, closed)
|
|
416
|
+
}, closed$1)
|
|
412
417
|
},
|
|
413
418
|
{
|
|
414
419
|
id: "toggle",
|
|
415
420
|
dataTypes: ["boolean"],
|
|
416
|
-
optionsSchema: Type.Object({}, closed)
|
|
421
|
+
optionsSchema: Type.Object({}, closed$1)
|
|
417
422
|
},
|
|
418
423
|
{
|
|
419
424
|
id: "checkbox",
|
|
420
425
|
dataTypes: ["boolean"],
|
|
421
|
-
optionsSchema: Type.Object({}, closed)
|
|
426
|
+
optionsSchema: Type.Object({}, closed$1)
|
|
422
427
|
},
|
|
423
428
|
{
|
|
424
429
|
id: "segmented",
|
|
@@ -426,35 +431,35 @@ const entries = [
|
|
|
426
431
|
optionsSchema: Type.Object({
|
|
427
432
|
trueLabel: Type.Optional(Type.String({ maxLength: 50 })),
|
|
428
433
|
falseLabel: Type.Optional(Type.String({ maxLength: 50 }))
|
|
429
|
-
}, closed),
|
|
434
|
+
}, closed$1),
|
|
430
435
|
supports: (field) => !isMultipleEnum(field)
|
|
431
436
|
},
|
|
432
437
|
{
|
|
433
438
|
id: "select",
|
|
434
439
|
dataTypes: ["enum"],
|
|
435
|
-
optionsSchema: Type.Object({ placeholder: Placeholder }, closed)
|
|
440
|
+
optionsSchema: Type.Object({ placeholder: Placeholder }, closed$1)
|
|
436
441
|
},
|
|
437
442
|
{
|
|
438
443
|
id: "radio",
|
|
439
444
|
dataTypes: ["enum"],
|
|
440
|
-
optionsSchema: Type.Object({ layout: Type.Optional(Type.Enum(["vertical", "horizontal"])) }, closed),
|
|
445
|
+
optionsSchema: Type.Object({ layout: Type.Optional(Type.Enum(["vertical", "horizontal"])) }, closed$1),
|
|
441
446
|
supports: (field) => !isMultipleEnum(field)
|
|
442
447
|
},
|
|
443
448
|
{
|
|
444
449
|
id: "checkboxGroup",
|
|
445
450
|
dataTypes: ["enum"],
|
|
446
|
-
optionsSchema: Type.Object({ layout: Type.Optional(Type.Enum(["vertical", "horizontal"])) }, closed),
|
|
451
|
+
optionsSchema: Type.Object({ layout: Type.Optional(Type.Enum(["vertical", "horizontal"])) }, closed$1),
|
|
447
452
|
supports: isMultipleEnum
|
|
448
453
|
},
|
|
449
454
|
{
|
|
450
455
|
id: "datePicker",
|
|
451
456
|
dataTypes: ["date"],
|
|
452
|
-
optionsSchema: Type.Object({}, closed)
|
|
457
|
+
optionsSchema: Type.Object({}, closed$1)
|
|
453
458
|
},
|
|
454
459
|
{
|
|
455
460
|
id: "dateTimePicker",
|
|
456
461
|
dataTypes: ["datetime"],
|
|
457
|
-
optionsSchema: Type.Object({ display: Type.Optional(Type.Enum(["local", "utc"])) }, closed)
|
|
462
|
+
optionsSchema: Type.Object({ display: Type.Optional(Type.Enum(["local", "utc"])) }, closed$1)
|
|
458
463
|
},
|
|
459
464
|
{
|
|
460
465
|
id: "timePicker",
|
|
@@ -462,12 +467,12 @@ const entries = [
|
|
|
462
467
|
optionsSchema: Type.Object({ stepMinutes: Type.Optional(Type.Integer({
|
|
463
468
|
minimum: 1,
|
|
464
469
|
maximum: 60
|
|
465
|
-
})) }, closed)
|
|
470
|
+
})) }, closed$1)
|
|
466
471
|
},
|
|
467
472
|
{
|
|
468
473
|
id: "slugInput",
|
|
469
474
|
dataTypes: ["slug"],
|
|
470
|
-
optionsSchema: Type.Object({ placeholder: Placeholder }, closed)
|
|
475
|
+
optionsSchema: Type.Object({ placeholder: Placeholder }, closed$1)
|
|
471
476
|
},
|
|
472
477
|
{
|
|
473
478
|
id: "jsonEditor",
|
|
@@ -475,27 +480,33 @@ const entries = [
|
|
|
475
480
|
optionsSchema: Type.Object({ rows: Type.Optional(Type.Integer({
|
|
476
481
|
minimum: 3,
|
|
477
482
|
maximum: 80
|
|
478
|
-
})) }, closed)
|
|
483
|
+
})) }, closed$1)
|
|
479
484
|
},
|
|
480
485
|
{
|
|
481
486
|
id: "mediaPicker",
|
|
482
487
|
dataTypes: ["media"],
|
|
483
|
-
optionsSchema: Type.Object({}, closed)
|
|
488
|
+
optionsSchema: Type.Object({}, closed$1)
|
|
484
489
|
},
|
|
485
490
|
{
|
|
486
491
|
id: "relationPicker",
|
|
487
492
|
dataTypes: ["relation"],
|
|
488
|
-
optionsSchema: Type.Object({ allowInlineCreate: Type.Optional(Type.Boolean()) }, closed)
|
|
493
|
+
optionsSchema: Type.Object({ allowInlineCreate: Type.Optional(Type.Boolean()) }, closed$1)
|
|
489
494
|
},
|
|
490
495
|
{
|
|
491
496
|
id: "componentEditor",
|
|
492
497
|
dataTypes: ["component"],
|
|
493
|
-
optionsSchema: Type.Object({ collapsed: Type.Optional(Type.Boolean()) }, closed)
|
|
498
|
+
optionsSchema: Type.Object({ collapsed: Type.Optional(Type.Boolean()) }, closed$1)
|
|
499
|
+
},
|
|
500
|
+
{
|
|
501
|
+
id: SEO_EDITOR_ID,
|
|
502
|
+
dataTypes: ["component"],
|
|
503
|
+
optionsSchema: Type.Object({}, closed$1),
|
|
504
|
+
supports: isSeoField
|
|
494
505
|
},
|
|
495
506
|
{
|
|
496
507
|
id: "dynamicZoneEditor",
|
|
497
508
|
dataTypes: ["dynamiczone"],
|
|
498
|
-
optionsSchema: Type.Object({ collapsed: Type.Optional(Type.Boolean()) }, closed)
|
|
509
|
+
optionsSchema: Type.Object({ collapsed: Type.Optional(Type.Boolean()) }, closed$1)
|
|
499
510
|
}
|
|
500
511
|
];
|
|
501
512
|
const EDITOR_CATALOGUE = new Map(entries.map((entry) => [entry.id, entry]));
|
|
@@ -565,8 +576,9 @@ const effectiveTitleField = (definition) => {
|
|
|
565
576
|
//#endregion
|
|
566
577
|
//#region src/display/layout.ts
|
|
567
578
|
/**
|
|
568
|
-
* Data types that
|
|
569
|
-
* media must hold several files (`isCanvasEligible`); a single component or file is a property
|
|
579
|
+
* Data types that are blocks of the entry document's canvas by default. Components must also be repeatable
|
|
580
|
+
* and media must hold several files (`isCanvasEligible`); a single component or file is a property unless
|
|
581
|
+
* `display.canvasFieldIds` places it in the document.
|
|
570
582
|
*/
|
|
571
583
|
const CANVAS_ELIGIBLE = /* @__PURE__ */ new Set([
|
|
572
584
|
"richtext",
|
|
@@ -578,6 +590,11 @@ const CANVAS_ELIGIBLE = /* @__PURE__ */ new Set([
|
|
|
578
590
|
const INLINE_TITLE_TYPES = /* @__PURE__ */ new Set(["string", "text"]);
|
|
579
591
|
/** How many properties the strip shows when `display.stripFieldIds` is not set. */
|
|
580
592
|
const STRIP_DEFAULT_COUNT = 5;
|
|
593
|
+
/**
|
|
594
|
+
* The automatic rule: the fields written in the document while `display.canvasFieldIds` is unset (rich
|
|
595
|
+
* text, dynamic zones, repeatable components, media with several files). Also what a canvas field can take
|
|
596
|
+
* between blocks: only these hold blocks.
|
|
597
|
+
*/
|
|
581
598
|
const isCanvasEligible = (field) => {
|
|
582
599
|
switch (field.type) {
|
|
583
600
|
case "richtext":
|
|
@@ -587,6 +604,12 @@ const isCanvasEligible = (field) => {
|
|
|
587
604
|
default: return false;
|
|
588
605
|
}
|
|
589
606
|
};
|
|
607
|
+
/**
|
|
608
|
+
* Whether `display.canvasFieldIds` may list the field: any field but the entry's title (`effectiveTitleField`),
|
|
609
|
+
* which the document shows as its heading. A configured cover is refused by the validator; an automatically
|
|
610
|
+
* chosen one placed in the document simply stops being the cover.
|
|
611
|
+
*/
|
|
612
|
+
const isDocumentPlaceable = (field, title) => field.id !== title?.id;
|
|
590
613
|
/** A single `media` field that may hold an image. */
|
|
591
614
|
const isCoverEligible = (field) => field.type === "media" && !field.settings.multiple && (field.settings.allowedKinds === void 0 || field.settings.allowedKinds.includes("image"));
|
|
592
615
|
const byIds = (live, ids) => (ids ?? []).map((id) => live.find((field) => field.id === id)).filter((field) => field !== void 0);
|
|
@@ -611,16 +634,16 @@ const coverOf = (model, live, canvas) => {
|
|
|
611
634
|
return live.find((field) => isCoverEligible(field) && !canvas.has(field.id));
|
|
612
635
|
};
|
|
613
636
|
/**
|
|
614
|
-
* The entry document's layout: title, cover,
|
|
615
|
-
* when they point at live, eligible fields; anything else falls back to the defaults (canvas: every
|
|
616
|
-
*
|
|
617
|
-
* skipped, so a stale definition never breaks the editor. Pure: no values, no I/O.
|
|
637
|
+
* The entry document's layout: title, cover, document fields and properties. Configured `display` ids win
|
|
638
|
+
* when they point at live, eligible fields; anything else falls back to the defaults (canvas: every block
|
|
639
|
+
* field in field order; cover: the first single image field not in the canvas). Deprecated, unknown ids and
|
|
640
|
+
* the title are skipped, so a stale definition never breaks the editor. Pure: no values, no I/O.
|
|
618
641
|
*/
|
|
619
642
|
const effectiveLayout = (model) => {
|
|
620
643
|
const live = model.fields.filter((field) => !field.deprecated);
|
|
621
644
|
const title = effectiveTitleField(model);
|
|
622
645
|
const titleInline = title !== void 0 && INLINE_TITLE_TYPES.has(title.type);
|
|
623
|
-
const configuredCanvas = unique(byIds(live, model.display.canvasFieldIds).filter(
|
|
646
|
+
const configuredCanvas = unique(byIds(live, model.display.canvasFieldIds).filter((field) => isDocumentPlaceable(field, title)));
|
|
624
647
|
const canvasCandidates = model.display.canvasFieldIds === void 0 ? live.filter(isCanvasEligible) : configuredCanvas;
|
|
625
648
|
const cover = coverOf(model, live, new Set(configuredCanvas.map((field) => field.id)));
|
|
626
649
|
const canvas = canvasCandidates.filter((field) => field.id !== cover?.id);
|
|
@@ -939,7 +962,10 @@ const RESERVED_TYPE_NAMES = [
|
|
|
939
962
|
"SnapshotChangeKind",
|
|
940
963
|
"SnapshotChangeLocale",
|
|
941
964
|
"SnapshotChangePage",
|
|
942
|
-
"SnapshotInfo"
|
|
965
|
+
"SnapshotInfo",
|
|
966
|
+
"SiteInfo",
|
|
967
|
+
"SiteSeoDefaults",
|
|
968
|
+
"SiteSeoLocale"
|
|
943
969
|
];
|
|
944
970
|
/**
|
|
945
971
|
* Model and component API keys the admin uses as static URL segments (`/content/new`,
|
|
@@ -973,7 +999,8 @@ const RESERVED_FIELD_API_KEYS = [
|
|
|
973
999
|
const RESERVED_QUERY_NAMES = [
|
|
974
1000
|
"_schemaVersion",
|
|
975
1001
|
"_changes",
|
|
976
|
-
"_snapshot"
|
|
1002
|
+
"_snapshot",
|
|
1003
|
+
"_site"
|
|
977
1004
|
];
|
|
978
1005
|
/** Every name a definition contributes to the GraphQL schema. */
|
|
979
1006
|
const generatedNames = (definition) => {
|
|
@@ -1232,17 +1259,18 @@ const checkFieldList = (ids, check) => {
|
|
|
1232
1259
|
});
|
|
1233
1260
|
};
|
|
1234
1261
|
/**
|
|
1235
|
-
* The entry document's layout (`effectiveLayout`):
|
|
1236
|
-
* are disjoint
|
|
1237
|
-
* entries are properties, so never the cover or a
|
|
1262
|
+
* The entry document's layout (`effectiveLayout`): document fields, cover and strip. Title, cover and
|
|
1263
|
+
* canvas are disjoint: any field but the title (configured or automatic) may be in the canvas, the
|
|
1264
|
+
* configured cover may not; strip entries are properties, so never the cover or a document field.
|
|
1238
1265
|
*/
|
|
1239
1266
|
const checkDocumentLayout = (model, fields) => {
|
|
1240
1267
|
const { canvasFieldIds, coverFieldId, stripFieldIds } = model.display;
|
|
1241
1268
|
const canvas = new Set(canvasFieldIds ?? []);
|
|
1269
|
+
const title = effectiveTitleField(model);
|
|
1242
1270
|
const issues = checkFieldList(canvasFieldIds, {
|
|
1243
1271
|
path: "/display/canvasFieldIds",
|
|
1244
1272
|
fields,
|
|
1245
|
-
reject: (field) =>
|
|
1273
|
+
reject: (field) => isDocumentPlaceable(field, title) ? void 0 : "the title is the heading of the document, not a field in it"
|
|
1246
1274
|
});
|
|
1247
1275
|
if (coverFieldId !== void 0) issues.push(...checkFieldList([coverFieldId], {
|
|
1248
1276
|
path: "/display/coverFieldId",
|
|
@@ -1259,11 +1287,6 @@ const checkDocumentLayout = (model, fields) => {
|
|
|
1259
1287
|
}));
|
|
1260
1288
|
return issues;
|
|
1261
1289
|
};
|
|
1262
|
-
const canvasRejection = (field) => {
|
|
1263
|
-
if (field.type === "component") return "only a repeatable component can be in the canvas";
|
|
1264
|
-
if (field.type === "media") return "only a media field with several files can be in the canvas";
|
|
1265
|
-
return `a ${field.type} field cannot be in the canvas`;
|
|
1266
|
-
};
|
|
1267
1290
|
const checkDisplay = (definition) => {
|
|
1268
1291
|
const fields = new Map(definition.fields.map((field) => [field.id, field]));
|
|
1269
1292
|
const fieldIds = new Set(fields.keys());
|
|
@@ -2412,7 +2435,9 @@ const BUILT_IN_THEME_KEYS = [
|
|
|
2412
2435
|
"shapio",
|
|
2413
2436
|
"classic",
|
|
2414
2437
|
"murdered-out",
|
|
2415
|
-
"
|
|
2438
|
+
"forest",
|
|
2439
|
+
"snowed",
|
|
2440
|
+
"butter"
|
|
2416
2441
|
];
|
|
2417
2442
|
/**
|
|
2418
2443
|
* A token value an extension may use: a hex colour or a colour function whose arguments are numbers, units
|
|
@@ -2525,6 +2550,89 @@ const checkThemeContrast = (tokens, variant) => {
|
|
|
2525
2550
|
return [...THEME_TEXT_PAIRS.map((pair) => measure(pair, 4.5)), ...uiPairs.map((pair) => measure(pair, 3))];
|
|
2526
2551
|
};
|
|
2527
2552
|
//#endregion
|
|
2528
|
-
|
|
2553
|
+
//#region src/seo/definition.ts
|
|
2554
|
+
/**
|
|
2555
|
+
* The built-in SEO component as first created. All fields are public and none has a length limit: the 70 and
|
|
2556
|
+
* 160 character marks are admin-only counters (`SEO_TITLE_SOFT_MAX`), never validation.
|
|
2557
|
+
*/
|
|
2558
|
+
const seoComponentDefinition = () => normalizeDefinition({
|
|
2559
|
+
id: SEO_COMPONENT_ID,
|
|
2560
|
+
kind: "component",
|
|
2561
|
+
apiKey: "seo",
|
|
2562
|
+
label: "SEO",
|
|
2563
|
+
description: "Search and social metadata: title, description, image, canonical URL and indexing.",
|
|
2564
|
+
category: "Built-in",
|
|
2565
|
+
icon: "search",
|
|
2566
|
+
fields: [
|
|
2567
|
+
{
|
|
2568
|
+
id: SEO_FIELD_IDS.title,
|
|
2569
|
+
apiKey: "title",
|
|
2570
|
+
label: "Title",
|
|
2571
|
+
type: "string"
|
|
2572
|
+
},
|
|
2573
|
+
{
|
|
2574
|
+
id: SEO_FIELD_IDS.description,
|
|
2575
|
+
apiKey: "description",
|
|
2576
|
+
label: "Description",
|
|
2577
|
+
type: "text"
|
|
2578
|
+
},
|
|
2579
|
+
{
|
|
2580
|
+
id: SEO_FIELD_IDS.image,
|
|
2581
|
+
apiKey: "image",
|
|
2582
|
+
label: "Social image",
|
|
2583
|
+
type: "media",
|
|
2584
|
+
settings: {
|
|
2585
|
+
multiple: false,
|
|
2586
|
+
allowedKinds: ["image"]
|
|
2587
|
+
}
|
|
2588
|
+
},
|
|
2589
|
+
{
|
|
2590
|
+
id: SEO_FIELD_IDS.canonical,
|
|
2591
|
+
apiKey: "canonical",
|
|
2592
|
+
label: "Canonical URL",
|
|
2593
|
+
type: "url",
|
|
2594
|
+
settings: { protocols: ["https", "http"] }
|
|
2595
|
+
},
|
|
2596
|
+
{
|
|
2597
|
+
id: SEO_FIELD_IDS.noindex,
|
|
2598
|
+
apiKey: "noindex",
|
|
2599
|
+
label: "Hide from search engines",
|
|
2600
|
+
type: "boolean"
|
|
2601
|
+
}
|
|
2602
|
+
],
|
|
2603
|
+
display: { titleFieldId: SEO_FIELD_IDS.title }
|
|
2604
|
+
});
|
|
2605
|
+
//#endregion
|
|
2606
|
+
//#region src/seo/defaults.ts
|
|
2607
|
+
const closed = { additionalProperties: false };
|
|
2608
|
+
/** Media asset IDs are UUIDs. */
|
|
2609
|
+
const ASSET_ID_PATTERN = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
2610
|
+
/** One locale's default texts. Every key is optional: a missing one falls back along the locale chain. */
|
|
2611
|
+
const SeoLocaleDefaultsSchema = Type.Object({
|
|
2612
|
+
siteName: Type.Optional(Type.String({
|
|
2613
|
+
minLength: 1,
|
|
2614
|
+
maxLength: 200
|
|
2615
|
+
})),
|
|
2616
|
+
titleTemplate: Type.Optional(Type.String({
|
|
2617
|
+
minLength: 2,
|
|
2618
|
+
maxLength: 200,
|
|
2619
|
+
pattern: SEO_TITLE_TEMPLATE_PATTERN.source
|
|
2620
|
+
})),
|
|
2621
|
+
description: Type.Optional(Type.String({
|
|
2622
|
+
minLength: 1,
|
|
2623
|
+
maxLength: 1e3
|
|
2624
|
+
}))
|
|
2625
|
+
}, closed);
|
|
2626
|
+
/**
|
|
2627
|
+
* A site's SEO defaults (`sites.seo_defaults`): texts per locale code, the default social image (a public
|
|
2628
|
+
* image asset of the same site) and the Twitter handle for the whole site.
|
|
2629
|
+
*/
|
|
2630
|
+
const SeoDefaultsSchema = Type.Object({
|
|
2631
|
+
locales: Type.Record(Type.String({ pattern: LOCALE_CODE_PATTERN.source }), SeoLocaleDefaultsSchema, { additionalProperties: false }),
|
|
2632
|
+
imageId: Type.Union([Type.String({ pattern: ASSET_ID_PATTERN.source }), Type.Null()]),
|
|
2633
|
+
twitterHandle: Type.Union([Type.String({ pattern: SEO_TWITTER_HANDLE_PATTERN.source }), Type.Null()])
|
|
2634
|
+
}, closed);
|
|
2635
|
+
//#endregion
|
|
2636
|
+
export { API_KEY_PATTERN, BIGINTEGER_PATTERN, BUILT_IN_THEME_KEYS, CANVAS_ELIGIBLE, CHANGE_KINDS, COMPONENT_TYPE_SUFFIXES, CUSTOM_EDITOR_ID_PATTERN, ComponentDefinitionInputSchema, ComponentDisplayInputSchema, DATA_TYPES, DATETIME_PATTERN, DATE_PATTERN, DECIMAL_PATTERN, DEFAULT_EDITORS, DEFAULT_SEO_KEYS, DEFINITION_KINDS, DefinitionInputSchema, EDITOR_CATALOGUE, EMPTY_SEO_DEFAULTS, EditorChoiceInputSchema, EnumValueSchema, FieldInputSchema, INLINE_TITLE_TYPES, LOCALE_CODE_PATTERN, LOCK_FILE_FORMAT_VERSION, LOCK_FILE_PATH, LocaleInputSchema, LockEntrySchema, LockFileError, LockFileSchema, MARK_RULES, MAX_API_KEY_LENGTH, MAX_COMPONENT_DEPTH, MAX_DESCRIPTION_LENGTH, MAX_FALLBACK_CHAIN, MAX_FIELDS_PER_DEFINITION, MAX_LABEL_LENGTH, MAX_RICHTEXT_DEPTH, MEDIA_KINDS, MODEL_KINDS, MODEL_TYPE_SUFFIXES, MUTATION_PREFIXES, ModelDefinitionInputSchema, ModelDisplayInputSchema, NODE_RULES, PLURAL_FALLBACK_SUFFIX, REFERENCE_DATA_TYPES, RELATION_CARDINALITIES, RESERVED_DEFINITION_API_KEYS, RESERVED_FIELD_API_KEYS, RESERVED_QUERY_NAMES, RESERVED_TYPE_NAMES, RICHTEXT_FORMAT, RICHTEXT_FORMAT_VERSION, SAFE_LINK_PATTERN, SCALAR_DATA_TYPES, SCHEMA_KIND_DIRECTORIES, SEO_COMPONENT_API_KEY, SEO_COMPONENT_ID, SEO_DESCRIPTION_SOFT_MAX, SEO_EDITOR_ID, SEO_FIELD_IDS, SEO_TITLE_PLACEHOLDER, SEO_TITLE_SOFT_MAX, SEO_TITLE_TEMPLATE_PATTERN, SEO_TWITTER_HANDLE_PATTERN, SETTINGS_SCHEMAS, SITES_DIRECTORY, SLUG_PATTERN, SORT_DIRECTIONS, STABLE_ID_PATTERN, STORAGE_FAMILY, STRIP_DEFAULT_COUNT, SchemaValidationError, SeoDefaultsSchema, SeoLocaleDefaultsSchema, TEXT_TITLE_FIELD_TYPES, THEME_BRAND_TOKENS, THEME_COLOUR_PATTERN, THEME_DARK_UI_PAIRS, THEME_KEY_PATTERN, THEME_SEMANTIC_TOKENS, THEME_TEXT_PAIRS, THEME_TOKENS, THEME_UI_PAIRS, THEME_VARIANTS, TIME_PATTERN, TITLE_FIELD_TYPES, UID_PATTERN, UNIQUE_CAPABLE_DATA_TYPES, applyTitleTemplate, assignIds, canonicalJson, checkApiKeySyntax, checkDefaultValue, checkThemeContrast, classifyChange, classifyChanges, classifyLocaleChange, collectionQueryName, contrastRatio, createStableId, decideSync, diffDefinitions, effectiveLayout, effectiveTitleField, embeddedComponentIds, escapeHtml, findDependentModels, findReferencingDefinitions, foldApiKey, generatedNames, hashDefinition, isApiKeySyntaxValid, isCanvasEligible, isComponentDefinition, isCoverEligible, isCustomEditorId, isDataType, isDocumentPlaceable, isEmptyRichText, isModelDefinition, isSeoField, isStableId, isValidTitleTemplate, issue, listCompatibleEditors, lockCoversSite, lockEntriesForSite, mergePulledLock, normalizeDefinition, parseDefinition, parseLockFile, plainTextToRichText, renderRichTextHtml, renormalizeDefinition, resolveSeo, richTextMediaIds, richTextToPlainText, routeKeyOf, schemaFilePath, scopeDirectory, scopeOfSchemaFilePath, seoComponentDefinition, seoDefaultsForLocale, serializeDefinition, serializeLockFile, sha256, stripFieldsOf, suggestPlural, summarizeChanges, toTypeName, upgradeLockFile, validateDefinition, validateRichText, validateSchema, withDerivedPlural, withSiteCovered };
|
|
2529
2637
|
|
|
2530
2638
|
//# sourceMappingURL=index.js.map
|