@shapio/schema 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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$2 = { additionalProperties: false };
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$2);
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$2);
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$2);
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$2)),
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$2);
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$2);
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$2);
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$2);
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$1 = { additionalProperties: false };
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$1);
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$1),
275
- text: Type.Object(lengthRules, closed$1),
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$1),
280
+ }, closed$2),
280
281
  number: Type.Object({
281
282
  min: Type.Optional(Type.Number()),
282
283
  max: Type.Optional(Type.Number())
283
- }, closed$1),
284
+ }, closed$2),
284
285
  integer: Type.Object({
285
286
  min: Type.Optional(Type.Integer()),
286
287
  max: Type.Optional(Type.Integer())
287
- }, closed$1),
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$1),
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$1),
304
- boolean: Type.Object({}, closed$1),
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$1),
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$1),
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$1),
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$1),
324
- json: Type.Object({}, closed$1),
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$1),
329
- email: Type.Object({}, closed$1),
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$1),
336
- uid: Type.Object(lengthRules, closed$1),
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$1),
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$1),
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$1),
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$1)
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]));
@@ -939,7 +950,10 @@ const RESERVED_TYPE_NAMES = [
939
950
  "SnapshotChangeKind",
940
951
  "SnapshotChangeLocale",
941
952
  "SnapshotChangePage",
942
- "SnapshotInfo"
953
+ "SnapshotInfo",
954
+ "SiteInfo",
955
+ "SiteSeoDefaults",
956
+ "SiteSeoLocale"
943
957
  ];
944
958
  /**
945
959
  * Model and component API keys the admin uses as static URL segments (`/content/new`,
@@ -973,7 +987,8 @@ const RESERVED_FIELD_API_KEYS = [
973
987
  const RESERVED_QUERY_NAMES = [
974
988
  "_schemaVersion",
975
989
  "_changes",
976
- "_snapshot"
990
+ "_snapshot",
991
+ "_site"
977
992
  ];
978
993
  /** Every name a definition contributes to the GraphQL schema. */
979
994
  const generatedNames = (definition) => {
@@ -2344,6 +2359,272 @@ const plainTextToRichText = (text) => {
2344
2359
  };
2345
2360
  };
2346
2361
  //#endregion
2347
- export { API_KEY_PATTERN, BIGINTEGER_PATTERN, CANVAS_ELIGIBLE, CHANGE_KINDS, COMPONENT_TYPE_SUFFIXES, CUSTOM_EDITOR_ID_PATTERN, ComponentDefinitionInputSchema, ComponentDisplayInputSchema, DATA_TYPES, DATETIME_PATTERN, DATE_PATTERN, DECIMAL_PATTERN, DEFAULT_EDITORS, DEFINITION_KINDS, DefinitionInputSchema, EDITOR_CATALOGUE, 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, SETTINGS_SCHEMAS, SITES_DIRECTORY, SLUG_PATTERN, SORT_DIRECTIONS, STABLE_ID_PATTERN, STORAGE_FAMILY, STRIP_DEFAULT_COUNT, SchemaValidationError, TEXT_TITLE_FIELD_TYPES, TIME_PATTERN, TITLE_FIELD_TYPES, UID_PATTERN, UNIQUE_CAPABLE_DATA_TYPES, assignIds, canonicalJson, checkApiKeySyntax, checkDefaultValue, classifyChange, classifyChanges, classifyLocaleChange, collectionQueryName, createStableId, decideSync, diffDefinitions, effectiveLayout, effectiveTitleField, embeddedComponentIds, escapeHtml, findDependentModels, findReferencingDefinitions, foldApiKey, generatedNames, hashDefinition, isApiKeySyntaxValid, isCanvasEligible, isComponentDefinition, isCoverEligible, isCustomEditorId, isDataType, isEmptyRichText, isModelDefinition, isStableId, issue, listCompatibleEditors, lockCoversSite, lockEntriesForSite, mergePulledLock, normalizeDefinition, parseDefinition, parseLockFile, plainTextToRichText, renderRichTextHtml, renormalizeDefinition, richTextMediaIds, richTextToPlainText, routeKeyOf, schemaFilePath, scopeDirectory, scopeOfSchemaFilePath, serializeDefinition, serializeLockFile, sha256, stripFieldsOf, suggestPlural, summarizeChanges, toTypeName, upgradeLockFile, validateDefinition, validateRichText, validateSchema, withDerivedPlural, withSiteCovered };
2362
+ //#region src/themes/tokens.ts
2363
+ /**
2364
+ * Admin themes (docs/plans/themes.md): a theme is a named, complete set of these tokens for a light variant, a
2365
+ * dark variant or both. One list, shared by the admin's stylesheets (and their tests) and the API, which
2366
+ * validates themes declared by extensions (`shapio.config` `themes`).
2367
+ */
2368
+ /** The UI tokens: every theme variant sets all of them. */
2369
+ const THEME_SEMANTIC_TOKENS = [
2370
+ "background",
2371
+ "foreground",
2372
+ "card",
2373
+ "card-foreground",
2374
+ "popover",
2375
+ "popover-foreground",
2376
+ "primary",
2377
+ "primary-foreground",
2378
+ "primary-hover",
2379
+ "secondary",
2380
+ "secondary-foreground",
2381
+ "muted",
2382
+ "muted-foreground",
2383
+ "accent",
2384
+ "accent-foreground",
2385
+ "destructive",
2386
+ "destructive-foreground",
2387
+ "destructive-muted",
2388
+ "success",
2389
+ "success-muted",
2390
+ "warning",
2391
+ "warning-muted",
2392
+ "info",
2393
+ "info-muted",
2394
+ "border",
2395
+ "input",
2396
+ "ring",
2397
+ "link",
2398
+ "overlay",
2399
+ "sidebar",
2400
+ "sidebar-foreground",
2401
+ "sidebar-primary",
2402
+ "sidebar-primary-foreground",
2403
+ "sidebar-accent",
2404
+ "sidebar-accent-foreground",
2405
+ "sidebar-border",
2406
+ "sidebar-ring"
2407
+ ];
2408
+ /**
2409
+ * The logo (tile, S, letters) and the signed-out screens' brand panel. Built-in themes set them all; an
2410
+ * extension theme may leave them out and gets Shapio's for that variant.
2411
+ */
2412
+ const THEME_BRAND_TOKENS = [
2413
+ "brand-logo",
2414
+ "brand-logo-foreground",
2415
+ "brand-letters",
2416
+ "brand-panel-from",
2417
+ "brand-panel-to",
2418
+ "brand-panel-foreground",
2419
+ "brand-panel-mark"
2420
+ ];
2421
+ const THEME_TOKENS = [...THEME_SEMANTIC_TOKENS, ...THEME_BRAND_TOKENS];
2422
+ const THEME_VARIANTS = ["light", "dark"];
2423
+ /** A theme key: used in `data-theme` and in CSS selectors, so deliberately narrow. */
2424
+ const THEME_KEY_PATTERN = /^[a-z][a-z0-9-]{0,40}$/;
2425
+ /** The themes the admin ships; an extension theme may not reuse these keys. */
2426
+ const BUILT_IN_THEME_KEYS = [
2427
+ "shapio",
2428
+ "classic",
2429
+ "murdered-out",
2430
+ "forest",
2431
+ "snowed",
2432
+ "butter"
2433
+ ];
2434
+ /**
2435
+ * A token value an extension may use: a hex colour or a colour function whose arguments are numbers, units
2436
+ * and separators only (no `;`, quotes, braces or nested functions such as `url()` or `var()`), so a value
2437
+ * can never escape its declaration.
2438
+ */
2439
+ const THEME_COLOUR_PATTERN = /^(?:#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})|(?:rgba?|hsla?|oklch|oklab)\([0-9a-z.,%\s/+-]{1,80}\))$/;
2440
+ //#endregion
2441
+ //#region src/themes/contrast.ts
2442
+ const SURFACES = [
2443
+ "background",
2444
+ "card",
2445
+ "popover"
2446
+ ];
2447
+ const THEME_TEXT_PAIRS = [
2448
+ ...SURFACES.map((surface) => ["foreground", surface]),
2449
+ ...[
2450
+ ...SURFACES,
2451
+ "muted",
2452
+ "secondary",
2453
+ "accent",
2454
+ "sidebar"
2455
+ ].map((surface) => ["muted-foreground", surface]),
2456
+ ["primary-foreground", "primary"],
2457
+ ["primary-foreground", "primary-hover"],
2458
+ ["secondary-foreground", "secondary"],
2459
+ ["accent-foreground", "accent"],
2460
+ ["destructive-foreground", "destructive"],
2461
+ ["destructive", "background"],
2462
+ ["destructive", "card"],
2463
+ ["success", "background"],
2464
+ ["success", "card"],
2465
+ ["success", "success-muted"],
2466
+ ["warning", "warning-muted"],
2467
+ ["warning", "card"],
2468
+ ["info", "info-muted"],
2469
+ ["info", "card"],
2470
+ ["destructive", "destructive-muted"],
2471
+ ["foreground", "muted"],
2472
+ ["link", "background"],
2473
+ ["link", "card"],
2474
+ ["sidebar-foreground", "sidebar"],
2475
+ ["sidebar-primary-foreground", "sidebar-primary"],
2476
+ ["sidebar-accent-foreground", "sidebar-accent"],
2477
+ ["sidebar-foreground", "sidebar-accent"],
2478
+ ["muted-foreground", "sidebar-accent"],
2479
+ ...[
2480
+ "destructive",
2481
+ "success",
2482
+ "warning",
2483
+ "link"
2484
+ ].flatMap((text) => [[text, "popover"], [text, "muted"]]),
2485
+ ["brand-panel-foreground", "brand-panel-from"],
2486
+ ["brand-panel-foreground", "brand-panel-to"]
2487
+ ];
2488
+ const THEME_UI_PAIRS = [
2489
+ ...[
2490
+ ...SURFACES,
2491
+ "muted",
2492
+ "secondary"
2493
+ ].map((surface) => ["input", surface]),
2494
+ ...SURFACES.map((surface) => ["ring", surface]),
2495
+ ["ring", "muted"],
2496
+ ["ring", "accent"],
2497
+ ["primary", "background"],
2498
+ ["primary", "card"],
2499
+ ["primary", "popover"],
2500
+ ["primary", "muted"],
2501
+ ["info", "muted"],
2502
+ ["sidebar-ring", "sidebar"],
2503
+ ["sidebar-primary", "sidebar"],
2504
+ ["sidebar-primary", "sidebar-accent"]
2505
+ ];
2506
+ /**
2507
+ * Dark only: control borders on the selection surface. In light, selected table rows use accent/40, which
2508
+ * passes, and other selected surfaces hold no controls.
2509
+ */
2510
+ const THEME_DARK_UI_PAIRS = [["input", "accent"]];
2511
+ const HEX = /^#[0-9a-fA-F]{6}$/;
2512
+ const channel = (value) => {
2513
+ const c = value / 255;
2514
+ return c <= .03928 ? c / 12.92 : ((c + .055) / 1.055) ** 2.4;
2515
+ };
2516
+ const luminance = (hex) => {
2517
+ const [r, g, b] = [
2518
+ 1,
2519
+ 3,
2520
+ 5
2521
+ ].map((index) => channel(parseInt(hex.slice(index, index + 2), 16)));
2522
+ return .2126 * (r ?? 0) + .7152 * (g ?? 0) + .0722 * (b ?? 0);
2523
+ };
2524
+ /** The WCAG contrast ratio of two `#rrggbb` colours. */
2525
+ const contrastRatio = (a, b) => {
2526
+ const [high = 0, low = 0] = [luminance(a), luminance(b)].sort((x, y) => y - x);
2527
+ return (high + .05) / (low + .05);
2528
+ };
2529
+ /** Every pair the variant must meet, with its ratio. Missing tokens report an undefined ratio. */
2530
+ const checkThemeContrast = (tokens, variant) => {
2531
+ const uiPairs = variant === "dark" ? [...THEME_UI_PAIRS, ...THEME_DARK_UI_PAIRS] : THEME_UI_PAIRS;
2532
+ const measure = ([foreground, background], required) => {
2533
+ const a = tokens[foreground];
2534
+ const b = tokens[background];
2535
+ return {
2536
+ foreground,
2537
+ background,
2538
+ required,
2539
+ ratio: a && b && HEX.test(a) && HEX.test(b) ? contrastRatio(a, b) : void 0
2540
+ };
2541
+ };
2542
+ return [...THEME_TEXT_PAIRS.map((pair) => measure(pair, 4.5)), ...uiPairs.map((pair) => measure(pair, 3))];
2543
+ };
2544
+ //#endregion
2545
+ //#region src/seo/definition.ts
2546
+ /**
2547
+ * The built-in SEO component as first created. All fields are public and none has a length limit: the 70 and
2548
+ * 160 character marks are admin-only counters (`SEO_TITLE_SOFT_MAX`), never validation.
2549
+ */
2550
+ const seoComponentDefinition = () => normalizeDefinition({
2551
+ id: SEO_COMPONENT_ID,
2552
+ kind: "component",
2553
+ apiKey: "seo",
2554
+ label: "SEO",
2555
+ description: "Search and social metadata: title, description, image, canonical URL and indexing.",
2556
+ category: "Built-in",
2557
+ icon: "search",
2558
+ fields: [
2559
+ {
2560
+ id: SEO_FIELD_IDS.title,
2561
+ apiKey: "title",
2562
+ label: "Title",
2563
+ type: "string"
2564
+ },
2565
+ {
2566
+ id: SEO_FIELD_IDS.description,
2567
+ apiKey: "description",
2568
+ label: "Description",
2569
+ type: "text"
2570
+ },
2571
+ {
2572
+ id: SEO_FIELD_IDS.image,
2573
+ apiKey: "image",
2574
+ label: "Social image",
2575
+ type: "media",
2576
+ settings: {
2577
+ multiple: false,
2578
+ allowedKinds: ["image"]
2579
+ }
2580
+ },
2581
+ {
2582
+ id: SEO_FIELD_IDS.canonical,
2583
+ apiKey: "canonical",
2584
+ label: "Canonical URL",
2585
+ type: "url",
2586
+ settings: { protocols: ["https", "http"] }
2587
+ },
2588
+ {
2589
+ id: SEO_FIELD_IDS.noindex,
2590
+ apiKey: "noindex",
2591
+ label: "Hide from search engines",
2592
+ type: "boolean"
2593
+ }
2594
+ ],
2595
+ display: { titleFieldId: SEO_FIELD_IDS.title }
2596
+ });
2597
+ //#endregion
2598
+ //#region src/seo/defaults.ts
2599
+ const closed = { additionalProperties: false };
2600
+ /** Media asset IDs are UUIDs. */
2601
+ 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}$/;
2602
+ /** One locale's default texts. Every key is optional: a missing one falls back along the locale chain. */
2603
+ const SeoLocaleDefaultsSchema = Type.Object({
2604
+ siteName: Type.Optional(Type.String({
2605
+ minLength: 1,
2606
+ maxLength: 200
2607
+ })),
2608
+ titleTemplate: Type.Optional(Type.String({
2609
+ minLength: 2,
2610
+ maxLength: 200,
2611
+ pattern: SEO_TITLE_TEMPLATE_PATTERN.source
2612
+ })),
2613
+ description: Type.Optional(Type.String({
2614
+ minLength: 1,
2615
+ maxLength: 1e3
2616
+ }))
2617
+ }, closed);
2618
+ /**
2619
+ * A site's SEO defaults (`sites.seo_defaults`): texts per locale code, the default social image (a public
2620
+ * image asset of the same site) and the Twitter handle for the whole site.
2621
+ */
2622
+ const SeoDefaultsSchema = Type.Object({
2623
+ locales: Type.Record(Type.String({ pattern: LOCALE_CODE_PATTERN.source }), SeoLocaleDefaultsSchema, { additionalProperties: false }),
2624
+ imageId: Type.Union([Type.String({ pattern: ASSET_ID_PATTERN.source }), Type.Null()]),
2625
+ twitterHandle: Type.Union([Type.String({ pattern: SEO_TWITTER_HANDLE_PATTERN.source }), Type.Null()])
2626
+ }, closed);
2627
+ //#endregion
2628
+ 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, 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 };
2348
2629
 
2349
2630
  //# sourceMappingURL=index.js.map