@caltho/site-kit 0.2.11 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +84 -3
  2. package/dist/behaviors/accordion.d.ts +4 -0
  3. package/dist/behaviors/accordion.js +56 -0
  4. package/dist/behaviors/carousel.d.ts +4 -0
  5. package/dist/behaviors/carousel.js +127 -0
  6. package/dist/behaviors/filter.d.ts +1 -0
  7. package/dist/behaviors/filter.js +25 -0
  8. package/dist/behaviors/index.d.ts +14 -0
  9. package/dist/behaviors/index.js +78 -0
  10. package/dist/behaviors/motion.d.ts +13 -0
  11. package/dist/behaviors/motion.js +88 -0
  12. package/dist/behaviors/tabs.d.ts +3 -0
  13. package/dist/behaviors/tabs.js +55 -0
  14. package/dist/built/css.d.ts +2 -0
  15. package/dist/built/css.js +26 -0
  16. package/dist/built/theme.d.ts +19 -0
  17. package/dist/built/theme.js +86 -0
  18. package/dist/built/tree.d.ts +24 -0
  19. package/dist/built/tree.js +96 -0
  20. package/dist/built.test.d.ts +1 -0
  21. package/dist/built.test.js +257 -0
  22. package/dist/consent.d.ts +33 -0
  23. package/dist/consent.js +36 -0
  24. package/dist/consent.test.d.ts +1 -0
  25. package/dist/consent.test.js +61 -0
  26. package/dist/content/index.d.ts +170 -40
  27. package/dist/content/index.js +155 -0
  28. package/dist/content/lists.d.ts +69 -0
  29. package/dist/content/lists.js +77 -0
  30. package/dist/content/lists.test.d.ts +1 -0
  31. package/dist/content/lists.test.js +257 -0
  32. package/dist/content/look.d.ts +13 -13
  33. package/dist/content-migrate.d.ts +51 -0
  34. package/dist/content-migrate.js +69 -0
  35. package/dist/finish.d.ts +1 -1
  36. package/dist/finish.js +18 -5
  37. package/dist/frame.test.js +25 -0
  38. package/dist/next/built.d.ts +40 -0
  39. package/dist/next/built.js +318 -0
  40. package/dist/next/consent.d.ts +4 -0
  41. package/dist/next/consent.js +13 -0
  42. package/dist/next/data.d.ts +2 -2
  43. package/dist/next/data.js +6 -4
  44. package/dist/next/frame.d.ts +9 -1
  45. package/dist/next/frame.js +5 -2
  46. package/dist/next/index.d.ts +2 -0
  47. package/dist/next/index.js +2 -0
  48. package/dist/next/metadata.js +3 -2
  49. package/dist/next/parts/Img.d.ts +6 -1
  50. package/dist/next/parts/Img.js +11 -3
  51. package/dist/next/preview.js +242 -15
  52. package/dist/next/sections.js +5 -0
  53. package/dist/policies.js +22 -4
  54. package/dist/prepare.d.ts +10 -0
  55. package/dist/prepare.js +67 -3
  56. package/dist/preview-match.d.ts +21 -0
  57. package/dist/preview-match.js +25 -0
  58. package/dist/schema.js +3 -1
  59. package/dist/sections/icons.d.ts +2 -0
  60. package/dist/sections/icons.js +4 -0
  61. package/dist/sections/index.d.ts +38 -16
  62. package/dist/sections/index.js +7 -2
  63. package/dist/sections.css +324 -0
  64. package/dist/seo.js +2 -1
  65. package/dist/tree/behaviors.d.ts +50 -0
  66. package/dist/tree/behaviors.js +67 -0
  67. package/dist/tree/classes.d.ts +24 -0
  68. package/dist/tree/classes.js +94 -0
  69. package/dist/tree/index.d.ts +145 -0
  70. package/dist/tree/index.js +196 -0
  71. package/dist/tree/messages.d.ts +45 -0
  72. package/dist/tree/messages.js +7 -0
  73. package/package.json +15 -1
@@ -1,11 +1,14 @@
1
1
  import type { ShareOptions } from "../share.ts";
2
2
  export type { CardNode, ShareCard, ShareFont, ShareOptions } from "../share.ts";
3
3
  import { z } from "zod";
4
+ import { type Node } from "../tree/index.ts";
5
+ import { type Consent } from "../consent.ts";
4
6
  import { type Look, type LookOptions } from "./look.ts";
5
7
  export { COLOUR_ROLES, colourOf, contrast, HEADER_LAYOUTS, HEADER_STYLES, headerOf, isDark, KIT_FONTS, KIT_TYPE, lookCss, lookProblems, lookVars, swatchSlug, TYPE_ROLES, uploadedFontCss, buttonStyleClass, BUTTON_SIZES, MAX_BUTTON_STYLES, THEME_BUTTONS, type ButtonStyle, type ColourRole, type HeaderLayout, type HeaderLook, type HeaderStyle, type Look, type LookOptions, type Space, type Swatch, type TypeRole, type TypeScale, type TypeStyle, type UploadedFont, } from "./look.ts";
6
8
  export { fontFaceCss, fontFiles, googleFamily, googleFont, googleFonts, isFontName, servedAt, type FontFile, type FontKind, type GoogleFont } from "../fonts-google.ts";
7
9
  export { blockLabel, checkStructure, describePath, type StructureIssue } from "../schema.ts";
8
10
  export { z };
11
+ export { allPages, FIELD_KINDS, fromList, itemOf, itemPages, itemPath, PAGE_LINK, THIS_ITEM, withListItems, type FieldKind, type FromList, type List, type ListField, type ListItem, type ListPage } from "./lists.ts";
9
12
  export declare const text: z.ZodString;
10
13
  /** Longer plain text: drawn as a text area. */
11
14
  export declare const longText: z.ZodString;
@@ -57,19 +60,19 @@ export declare const placement: z.ZodObject<{
57
60
  roomy: "roomy";
58
61
  }>>;
59
62
  show: z.ZodDefault<z.ZodEnum<{
60
- phone: "phone";
61
63
  all: "all";
64
+ phone: "phone";
62
65
  desktop: "desktop";
63
66
  }>>;
64
67
  lines: z.ZodOptional<z.ZodEnum<{
65
- both: "both";
66
- none: "none";
67
68
  top: "top";
69
+ none: "none";
70
+ both: "both";
68
71
  bottom: "bottom";
69
72
  }>>;
70
73
  lineColour: z.ZodOptional<z.ZodEnum<{
71
- quiet: "quiet";
72
74
  strong: "strong";
75
+ quiet: "quiet";
73
76
  }>>;
74
77
  background: z.ZodOptional<z.ZodObject<{
75
78
  color: z.ZodOptional<z.ZodString>;
@@ -79,8 +82,8 @@ export declare const placement: z.ZodObject<{
79
82
  }, z.core.$strip>>;
80
83
  overlay: z.ZodDefault<z.ZodEnum<{
81
84
  dark: "dark";
82
- light: "light";
83
85
  none: "none";
86
+ light: "light";
84
87
  }>>;
85
88
  }, z.core.$strip>>;
86
89
  }, z.core.$strip>;
@@ -223,6 +226,8 @@ export type Footer = {
223
226
  note?: string;
224
227
  };
225
228
  export type SiteBase = {
229
+ format?: number; /** Whether it asks before counting visits, and its banner (../consent.ts). */
230
+ consent?: Consent;
226
231
  url: string;
227
232
  lang: string;
228
233
  business: Business;
@@ -232,7 +237,8 @@ export type SiteBase = {
232
237
  look?: Look;
233
238
  redirects: Redirect[]; /** Whether page paths end in a slash (the default). */
234
239
  trailingSlash?: boolean; /** Set by prepare, never in content: CalOS counts this site's visits (its pages carry the script), which its privacy policy says. */
235
- countsVisits?: boolean;
240
+ countsVisits?: boolean; /** Set by prepare (and CalOS's preview), never in content: the business's forms' addresses by key, for a built form's sendTo. */
241
+ forms?: Record<string, string>;
236
242
  };
237
243
  /**
238
244
  * The site's menu, as its header draws it: the one edited in CalOS (its links in order, one picked out,
@@ -283,12 +289,17 @@ export type DefineOptions = {
283
289
  /** The site's Look, chosen in CalOS (fonts from the ones it carries, colours, corners, buttons), drawn through CSS variables (./look.ts). */
284
290
  look?: LookOptions;
285
291
  };
286
- /**
287
- * A site's content shape from its blocks: each a name and the fields it has. The site's own block
288
- * type is `z.output<typeof content.schema>["pages"][number]["blocks"][number]`.
289
- */
290
292
  export declare function defineSite<const B extends Record<string, z.ZodObject>>(blocks: B, o?: DefineOptions): {
291
293
  schema: z.ZodObject<{
294
+ saved: z.ZodDefault<z.ZodArray<z.ZodObject<{
295
+ name: z.ZodString;
296
+ block: z.ZodDiscriminatedUnion<[z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>, ...z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>[]], "type">;
297
+ }, z.core.$strip>>>;
298
+ components?: z.ZodDefault<z.ZodArray<z.ZodObject<{
299
+ key: z.ZodString;
300
+ name: z.ZodString;
301
+ root: z.ZodType<Node, unknown, z.core.$ZodTypeInternals<Node, unknown>>;
302
+ }, z.core.$strip>>> | undefined;
292
303
  pages: z.ZodArray<z.ZodObject<{
293
304
  blocks: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>, ...z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>[]], "type">>;
294
305
  path: z.ZodString;
@@ -303,9 +314,38 @@ export declare function defineSite<const B extends Record<string, z.ZodObject>>(
303
314
  shareText: z.ZodOptional<z.ZodString>;
304
315
  noindex: z.ZodDefault<z.ZodBoolean>;
305
316
  }, z.core.$strip>>;
306
- saved: z.ZodDefault<z.ZodArray<z.ZodObject<{
317
+ lists: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
307
318
  name: z.ZodString;
308
- block: z.ZodDiscriminatedUnion<[z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>, ...z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>[]], "type">;
319
+ fields: z.ZodArray<z.ZodObject<{
320
+ key: z.ZodString;
321
+ label: z.ZodString;
322
+ kind: z.ZodEnum<{
323
+ number: "number";
324
+ date: "date";
325
+ text: "text";
326
+ image: "image";
327
+ link: "link";
328
+ longText: "longText";
329
+ }>;
330
+ }, z.core.$strip>>;
331
+ items: z.ZodDefault<z.ZodArray<z.ZodObject<{
332
+ id: z.ZodString;
333
+ slug: z.ZodString;
334
+ }, z.core.$catchall<z.ZodUnknown>>>>;
335
+ page: z.ZodOptional<z.ZodObject<{
336
+ path: z.ZodString;
337
+ title: z.ZodString;
338
+ description: z.ZodOptional<z.ZodString>;
339
+ blocks: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>, ...z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>[]], "type">>;
340
+ }, z.core.$strip>>;
341
+ source: z.ZodOptional<z.ZodObject<{
342
+ table: z.ZodString;
343
+ fields: z.ZodRecord<z.ZodString, z.ZodString>;
344
+ publish: z.ZodDefault<z.ZodEnum<{
345
+ draft: "draft";
346
+ auto: "auto";
347
+ }>>;
348
+ }, z.core.$strip>>;
309
349
  }, z.core.$strip>>>;
310
350
  look?: z.ZodOptional<z.ZodObject<{
311
351
  uploadedFonts?: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -331,13 +371,13 @@ export declare function defineSite<const B extends Record<string, z.ZodObject>>(
331
371
  floating: "floating";
332
372
  }>>;
333
373
  layout: z.ZodDefault<z.ZodEnum<{
374
+ left: "left";
334
375
  spread: "spread";
335
376
  centered: "centered";
336
- left: "left";
337
377
  }>>;
338
378
  menuShows: z.ZodDefault<z.ZodEnum<{
339
- both: "both";
340
379
  icon: "icon";
380
+ both: "both";
341
381
  word: "word";
342
382
  }>>;
343
383
  menuStyle: z.ZodOptional<z.ZodString>;
@@ -354,39 +394,39 @@ export declare function defineSite<const B extends Record<string, z.ZodObject>>(
354
394
  color: z.ZodString;
355
395
  }, z.core.$strip>>>;
356
396
  type: z.ZodDefault<z.ZodObject<{
357
- display: z.ZodOptional<z.ZodObject<{
397
+ h1: z.ZodOptional<z.ZodObject<{
358
398
  size: z.ZodOptional<z.ZodNumber>;
359
- weight: z.ZodOptional<z.ZodLiteral<400 | 500 | 600 | 700 | 300>>;
399
+ weight: z.ZodOptional<z.ZodLiteral<400 | 600 | 500 | 300 | 700>>;
360
400
  tracking: z.ZodOptional<z.ZodNumber>;
361
401
  leading: z.ZodOptional<z.ZodNumber>;
362
402
  }, z.core.$strip>>;
363
- h1: z.ZodOptional<z.ZodObject<{
403
+ h2: z.ZodOptional<z.ZodObject<{
364
404
  size: z.ZodOptional<z.ZodNumber>;
365
- weight: z.ZodOptional<z.ZodLiteral<400 | 500 | 600 | 700 | 300>>;
405
+ weight: z.ZodOptional<z.ZodLiteral<400 | 600 | 500 | 300 | 700>>;
366
406
  tracking: z.ZodOptional<z.ZodNumber>;
367
407
  leading: z.ZodOptional<z.ZodNumber>;
368
408
  }, z.core.$strip>>;
369
- h2: z.ZodOptional<z.ZodObject<{
409
+ h3: z.ZodOptional<z.ZodObject<{
370
410
  size: z.ZodOptional<z.ZodNumber>;
371
- weight: z.ZodOptional<z.ZodLiteral<400 | 500 | 600 | 700 | 300>>;
411
+ weight: z.ZodOptional<z.ZodLiteral<400 | 600 | 500 | 300 | 700>>;
372
412
  tracking: z.ZodOptional<z.ZodNumber>;
373
413
  leading: z.ZodOptional<z.ZodNumber>;
374
414
  }, z.core.$strip>>;
375
- h3: z.ZodOptional<z.ZodObject<{
415
+ small: z.ZodOptional<z.ZodObject<{
376
416
  size: z.ZodOptional<z.ZodNumber>;
377
- weight: z.ZodOptional<z.ZodLiteral<400 | 500 | 600 | 700 | 300>>;
417
+ weight: z.ZodOptional<z.ZodLiteral<400 | 600 | 500 | 300 | 700>>;
378
418
  tracking: z.ZodOptional<z.ZodNumber>;
379
419
  leading: z.ZodOptional<z.ZodNumber>;
380
420
  }, z.core.$strip>>;
381
421
  body: z.ZodOptional<z.ZodObject<{
382
422
  size: z.ZodOptional<z.ZodNumber>;
383
- weight: z.ZodOptional<z.ZodLiteral<400 | 500 | 600 | 700 | 300>>;
423
+ weight: z.ZodOptional<z.ZodLiteral<400 | 600 | 500 | 300 | 700>>;
384
424
  tracking: z.ZodOptional<z.ZodNumber>;
385
425
  leading: z.ZodOptional<z.ZodNumber>;
386
426
  }, z.core.$strip>>;
387
- small: z.ZodOptional<z.ZodObject<{
427
+ display: z.ZodOptional<z.ZodObject<{
388
428
  size: z.ZodOptional<z.ZodNumber>;
389
- weight: z.ZodOptional<z.ZodLiteral<400 | 500 | 600 | 700 | 300>>;
429
+ weight: z.ZodOptional<z.ZodLiteral<400 | 600 | 500 | 300 | 700>>;
390
430
  tracking: z.ZodOptional<z.ZodNumber>;
391
431
  leading: z.ZodOptional<z.ZodNumber>;
392
432
  }, z.core.$strip>>;
@@ -432,6 +472,7 @@ export declare function defineSite<const B extends Record<string, z.ZodObject>>(
432
472
  headingFont?: z.ZodDefault<z.ZodString> | undefined;
433
473
  bodyFont?: z.ZodDefault<z.ZodString> | undefined;
434
474
  }, z.core.$strip>> | undefined;
475
+ format: z.ZodOptional<z.ZodNumber>;
435
476
  url: z.ZodURL;
436
477
  lang: z.ZodDefault<z.ZodString>;
437
478
  business: z.ZodObject<{
@@ -512,6 +553,31 @@ export declare function defineSite<const B extends Record<string, z.ZodObject>>(
512
553
  }, z.core.$strip>>>;
513
554
  note: z.ZodOptional<z.ZodString>;
514
555
  }, z.core.$strip>>;
556
+ ownHeader: z.ZodOptional<z.ZodObject<{
557
+ name: z.ZodOptional<z.ZodString>;
558
+ root: z.ZodType<Node, unknown, z.core.$ZodTypeInternals<Node, unknown>>;
559
+ }, z.core.$strip>>;
560
+ ownFooter: z.ZodOptional<z.ZodObject<{
561
+ name: z.ZodOptional<z.ZodString>;
562
+ root: z.ZodType<Node, unknown, z.core.$ZodTypeInternals<Node, unknown>>;
563
+ }, z.core.$strip>>;
564
+ consent: z.ZodOptional<z.ZodObject<{
565
+ on: z.ZodDefault<z.ZodBoolean>;
566
+ text: z.ZodDefault<z.ZodString>;
567
+ accept: z.ZodDefault<z.ZodString>;
568
+ decline: z.ZodDefault<z.ZodString>;
569
+ policy: z.ZodOptional<z.ZodObject<{
570
+ label: z.ZodString;
571
+ href: z.ZodString;
572
+ }, z.core.$strip>>;
573
+ place: z.ZodDefault<z.ZodEnum<{
574
+ top: "top";
575
+ bottom: "bottom";
576
+ "bottom-left": "bottom-left";
577
+ "bottom-right": "bottom-right";
578
+ }>>;
579
+ design: z.ZodOptional<z.ZodString>;
580
+ }, z.core.$strip>>;
515
581
  redirects: z.ZodDefault<z.ZodArray<z.ZodObject<{
516
582
  from: z.ZodString;
517
583
  to: z.ZodString;
@@ -521,6 +587,15 @@ export declare function defineSite<const B extends Record<string, z.ZodObject>>(
521
587
  /** The content checked, or every problem in it as "where: what" lines. */
522
588
  parse(raw: unknown): {
523
589
  site: z.output<z.ZodObject<{
590
+ saved: z.ZodDefault<z.ZodArray<z.ZodObject<{
591
+ name: z.ZodString;
592
+ block: z.ZodDiscriminatedUnion<[z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>, ...z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>[]], "type">;
593
+ }, z.core.$strip>>>;
594
+ components?: z.ZodDefault<z.ZodArray<z.ZodObject<{
595
+ key: z.ZodString;
596
+ name: z.ZodString;
597
+ root: z.ZodType<Node, unknown, z.core.$ZodTypeInternals<Node, unknown>>;
598
+ }, z.core.$strip>>> | undefined;
524
599
  pages: z.ZodArray<z.ZodObject<{
525
600
  blocks: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>, ...z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>[]], "type">>;
526
601
  path: z.ZodString;
@@ -535,9 +610,38 @@ export declare function defineSite<const B extends Record<string, z.ZodObject>>(
535
610
  shareText: z.ZodOptional<z.ZodString>;
536
611
  noindex: z.ZodDefault<z.ZodBoolean>;
537
612
  }, z.core.$strip>>;
538
- saved: z.ZodDefault<z.ZodArray<z.ZodObject<{
613
+ lists: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
539
614
  name: z.ZodString;
540
- block: z.ZodDiscriminatedUnion<[z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>, ...z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>[]], "type">;
615
+ fields: z.ZodArray<z.ZodObject<{
616
+ key: z.ZodString;
617
+ label: z.ZodString;
618
+ kind: z.ZodEnum<{
619
+ number: "number";
620
+ date: "date";
621
+ text: "text";
622
+ image: "image";
623
+ link: "link";
624
+ longText: "longText";
625
+ }>;
626
+ }, z.core.$strip>>;
627
+ items: z.ZodDefault<z.ZodArray<z.ZodObject<{
628
+ id: z.ZodString;
629
+ slug: z.ZodString;
630
+ }, z.core.$catchall<z.ZodUnknown>>>>;
631
+ page: z.ZodOptional<z.ZodObject<{
632
+ path: z.ZodString;
633
+ title: z.ZodString;
634
+ description: z.ZodOptional<z.ZodString>;
635
+ blocks: z.ZodArray<z.ZodDiscriminatedUnion<[z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>, ...z.ZodObject<z.core.$ZodLooseShape, z.core.$strip>[]], "type">>;
636
+ }, z.core.$strip>>;
637
+ source: z.ZodOptional<z.ZodObject<{
638
+ table: z.ZodString;
639
+ fields: z.ZodRecord<z.ZodString, z.ZodString>;
640
+ publish: z.ZodDefault<z.ZodEnum<{
641
+ draft: "draft";
642
+ auto: "auto";
643
+ }>>;
644
+ }, z.core.$strip>>;
541
645
  }, z.core.$strip>>>;
542
646
  look?: z.ZodOptional<z.ZodObject<{
543
647
  uploadedFonts?: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -563,13 +667,13 @@ export declare function defineSite<const B extends Record<string, z.ZodObject>>(
563
667
  floating: "floating";
564
668
  }>>;
565
669
  layout: z.ZodDefault<z.ZodEnum<{
670
+ left: "left";
566
671
  spread: "spread";
567
672
  centered: "centered";
568
- left: "left";
569
673
  }>>;
570
674
  menuShows: z.ZodDefault<z.ZodEnum<{
571
- both: "both";
572
675
  icon: "icon";
676
+ both: "both";
573
677
  word: "word";
574
678
  }>>;
575
679
  menuStyle: z.ZodOptional<z.ZodString>;
@@ -586,39 +690,39 @@ export declare function defineSite<const B extends Record<string, z.ZodObject>>(
586
690
  color: z.ZodString;
587
691
  }, z.core.$strip>>>;
588
692
  type: z.ZodDefault<z.ZodObject<{
589
- display: z.ZodOptional<z.ZodObject<{
693
+ h1: z.ZodOptional<z.ZodObject<{
590
694
  size: z.ZodOptional<z.ZodNumber>;
591
- weight: z.ZodOptional<z.ZodLiteral<400 | 500 | 600 | 700 | 300>>;
695
+ weight: z.ZodOptional<z.ZodLiteral<400 | 600 | 500 | 300 | 700>>;
592
696
  tracking: z.ZodOptional<z.ZodNumber>;
593
697
  leading: z.ZodOptional<z.ZodNumber>;
594
698
  }, z.core.$strip>>;
595
- h1: z.ZodOptional<z.ZodObject<{
699
+ h2: z.ZodOptional<z.ZodObject<{
596
700
  size: z.ZodOptional<z.ZodNumber>;
597
- weight: z.ZodOptional<z.ZodLiteral<400 | 500 | 600 | 700 | 300>>;
701
+ weight: z.ZodOptional<z.ZodLiteral<400 | 600 | 500 | 300 | 700>>;
598
702
  tracking: z.ZodOptional<z.ZodNumber>;
599
703
  leading: z.ZodOptional<z.ZodNumber>;
600
704
  }, z.core.$strip>>;
601
- h2: z.ZodOptional<z.ZodObject<{
705
+ h3: z.ZodOptional<z.ZodObject<{
602
706
  size: z.ZodOptional<z.ZodNumber>;
603
- weight: z.ZodOptional<z.ZodLiteral<400 | 500 | 600 | 700 | 300>>;
707
+ weight: z.ZodOptional<z.ZodLiteral<400 | 600 | 500 | 300 | 700>>;
604
708
  tracking: z.ZodOptional<z.ZodNumber>;
605
709
  leading: z.ZodOptional<z.ZodNumber>;
606
710
  }, z.core.$strip>>;
607
- h3: z.ZodOptional<z.ZodObject<{
711
+ small: z.ZodOptional<z.ZodObject<{
608
712
  size: z.ZodOptional<z.ZodNumber>;
609
- weight: z.ZodOptional<z.ZodLiteral<400 | 500 | 600 | 700 | 300>>;
713
+ weight: z.ZodOptional<z.ZodLiteral<400 | 600 | 500 | 300 | 700>>;
610
714
  tracking: z.ZodOptional<z.ZodNumber>;
611
715
  leading: z.ZodOptional<z.ZodNumber>;
612
716
  }, z.core.$strip>>;
613
717
  body: z.ZodOptional<z.ZodObject<{
614
718
  size: z.ZodOptional<z.ZodNumber>;
615
- weight: z.ZodOptional<z.ZodLiteral<400 | 500 | 600 | 700 | 300>>;
719
+ weight: z.ZodOptional<z.ZodLiteral<400 | 600 | 500 | 300 | 700>>;
616
720
  tracking: z.ZodOptional<z.ZodNumber>;
617
721
  leading: z.ZodOptional<z.ZodNumber>;
618
722
  }, z.core.$strip>>;
619
- small: z.ZodOptional<z.ZodObject<{
723
+ display: z.ZodOptional<z.ZodObject<{
620
724
  size: z.ZodOptional<z.ZodNumber>;
621
- weight: z.ZodOptional<z.ZodLiteral<400 | 500 | 600 | 700 | 300>>;
725
+ weight: z.ZodOptional<z.ZodLiteral<400 | 600 | 500 | 300 | 700>>;
622
726
  tracking: z.ZodOptional<z.ZodNumber>;
623
727
  leading: z.ZodOptional<z.ZodNumber>;
624
728
  }, z.core.$strip>>;
@@ -664,6 +768,7 @@ export declare function defineSite<const B extends Record<string, z.ZodObject>>(
664
768
  headingFont?: z.ZodDefault<z.ZodString> | undefined;
665
769
  bodyFont?: z.ZodDefault<z.ZodString> | undefined;
666
770
  }, z.core.$strip>> | undefined;
771
+ format: z.ZodOptional<z.ZodNumber>;
667
772
  url: z.ZodURL;
668
773
  lang: z.ZodDefault<z.ZodString>;
669
774
  business: z.ZodObject<{
@@ -744,6 +849,31 @@ export declare function defineSite<const B extends Record<string, z.ZodObject>>(
744
849
  }, z.core.$strip>>>;
745
850
  note: z.ZodOptional<z.ZodString>;
746
851
  }, z.core.$strip>>;
852
+ ownHeader: z.ZodOptional<z.ZodObject<{
853
+ name: z.ZodOptional<z.ZodString>;
854
+ root: z.ZodType<Node, unknown, z.core.$ZodTypeInternals<Node, unknown>>;
855
+ }, z.core.$strip>>;
856
+ ownFooter: z.ZodOptional<z.ZodObject<{
857
+ name: z.ZodOptional<z.ZodString>;
858
+ root: z.ZodType<Node, unknown, z.core.$ZodTypeInternals<Node, unknown>>;
859
+ }, z.core.$strip>>;
860
+ consent: z.ZodOptional<z.ZodObject<{
861
+ on: z.ZodDefault<z.ZodBoolean>;
862
+ text: z.ZodDefault<z.ZodString>;
863
+ accept: z.ZodDefault<z.ZodString>;
864
+ decline: z.ZodDefault<z.ZodString>;
865
+ policy: z.ZodOptional<z.ZodObject<{
866
+ label: z.ZodString;
867
+ href: z.ZodString;
868
+ }, z.core.$strip>>;
869
+ place: z.ZodDefault<z.ZodEnum<{
870
+ top: "top";
871
+ bottom: "bottom";
872
+ "bottom-left": "bottom-left";
873
+ "bottom-right": "bottom-right";
874
+ }>>;
875
+ design: z.ZodOptional<z.ZodString>;
876
+ }, z.core.$strip>>;
747
877
  redirects: z.ZodDefault<z.ZodArray<z.ZodObject<{
748
878
  from: z.ZodString;
749
879
  to: z.ZodString;
@@ -11,11 +11,16 @@
11
11
  import { z } from "zod";
12
12
  import { checkStructure, describePath } from "../schema.js";
13
13
  import { presets } from "../sections/presets.js";
14
+ import { built, component, treeProblems } from "../tree/index.js";
15
+ import { CONTENT_FORMAT } from "../content-migrate.js";
16
+ import { consent } from "../consent.js";
17
+ import { FIELD_KINDS, itemPath } from "./lists.js";
14
18
  import { buttonStyleName, COLOUR_ROLES, colourOf, isDark, lookProblems, lookSchema, swatchSlug } from "./look.js";
15
19
  export { COLOUR_ROLES, colourOf, contrast, HEADER_LAYOUTS, HEADER_STYLES, headerOf, isDark, KIT_FONTS, KIT_TYPE, lookCss, lookProblems, lookVars, swatchSlug, TYPE_ROLES, uploadedFontCss, buttonStyleClass, BUTTON_SIZES, MAX_BUTTON_STYLES, THEME_BUTTONS, } from "./look.js";
16
20
  export { fontFaceCss, fontFiles, googleFamily, googleFont, googleFonts, isFontName, servedAt } from "../fonts-google.js";
17
21
  export { blockLabel, checkStructure, describePath } from "../schema.js";
18
22
  export { z };
23
+ export { allPages, FIELD_KINDS, fromList, itemOf, itemPages, itemPath, PAGE_LINK, THIS_ITEM, withListItems } from "./lists.js";
19
24
  // Fields carry an "x-ui" hint in the JSON Schema, so CalOS draws the right control: a text area, a
20
25
  // Markdown box, a picture picker.
21
26
  export const text = z.string().trim().min(1);
@@ -213,6 +218,76 @@ export function footerOf(site) {
213
218
  * A site's content shape from its blocks: each a name and the fields it has. The site's own block
214
219
  * type is `z.output<typeof content.schema>["pages"][number]["blocks"][number]`.
215
220
  */
221
+ /** A list's key and a field's: what Shows and Repeats for name them by (`lists.products.items`, `this.price`). */
222
+ const listKey = z.string().regex(/^[a-z][a-zA-Z0-9]{0,39}$/, { error: "Letters and numbers, starting with a lowercase letter, like products or teamMembers" });
223
+ const fieldKey = z
224
+ .string()
225
+ .regex(/^[a-z][a-zA-Z0-9]{0,39}$/, { error: "Letters and numbers, starting with a lowercase letter, like name or price" })
226
+ .refine((k) => k !== "id" && k !== "slug", { error: "id and slug are an item's own" });
227
+ /** An item: its id, its address part, and its values (each checked against its field's kind: listProblems). */
228
+ const listItem = z
229
+ .object({
230
+ id: z.string().regex(/^[\w-]{1,64}$/),
231
+ slug: z.string().max(80).regex(/^[a-z0-9]+(-[a-z0-9]+)*$/, { error: "Lowercase letters, numbers and dashes, like walnut-desk" }),
232
+ })
233
+ .catchall(z.unknown());
234
+ /** Each kind of a list's field, as a value is checked. */
235
+ const VALUE = {
236
+ text: z.string().trim().max(300),
237
+ longText: z.string().max(5000),
238
+ image,
239
+ link: href,
240
+ number: z.number().finite(),
241
+ date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, { error: "A date like 2026-11-09" }),
242
+ };
243
+ /**
244
+ * What a list's shape can't say alone: fields named once, each item's values of their field's kind
245
+ * and no others, ids and addresses used once, a page titled and described by a field of words, and no
246
+ * item at the address of one of the site's pages.
247
+ */
248
+ function listProblems(site) {
249
+ const out = [];
250
+ const lists = (site.lists ?? {});
251
+ const pagePaths = new Set(site.pages.map((p) => p.path));
252
+ for (const [key, list] of Object.entries(lists)) {
253
+ const at = ["lists", key];
254
+ const fields = new Map();
255
+ list.fields.forEach((f, i) => {
256
+ if (fields.has(f.key))
257
+ out.push({ path: [...at, "fields", i, "key"], message: `Two fields are called ${f.key}` });
258
+ fields.set(f.key, f.kind);
259
+ });
260
+ const ids = new Set();
261
+ const slugs = new Set();
262
+ list.items.forEach((item, i) => {
263
+ if (ids.has(item.id))
264
+ out.push({ path: [...at, "items", i, "id"], message: `Two items have the id ${item.id}` });
265
+ ids.add(item.id);
266
+ if (slugs.has(item.slug))
267
+ out.push({ path: [...at, "items", i, "slug"], message: `Two items have the address ${item.slug}` });
268
+ slugs.add(item.slug);
269
+ for (const [k, v] of Object.entries(item)) {
270
+ if (k === "id" || k === "slug" || v === undefined || v === null || v === "")
271
+ continue;
272
+ const kind = fields.get(k);
273
+ if (!kind)
274
+ out.push({ path: [...at, "items", i, k], message: `${k} isn't one of ${list.name}'s fields` });
275
+ else if (!VALUE[kind].safeParse(v).success)
276
+ out.push({ path: [...at, "items", i, k], message: `Not a ${kind === "longText" ? "text" : kind} value` });
277
+ }
278
+ if (list.page && pagePaths.has(itemPath(list.page, item.slug, site.trailingSlash !== false)))
279
+ out.push({ path: [...at, "items", i, "slug"], message: `There's a page at ${itemPath(list.page, item.slug, site.trailingSlash !== false)} already` });
280
+ });
281
+ if (list.page) {
282
+ const words = (k) => !k || fields.get(k) === "text" || fields.get(k) === "longText";
283
+ if (!fields.has(list.page.title) || fields.get(list.page.title) !== "text")
284
+ out.push({ path: [...at, "page", "title"], message: "The title is one of the list's fields of words" });
285
+ if (list.page.description && (!fields.has(list.page.description) || !words(list.page.description)))
286
+ out.push({ path: [...at, "page", "description"], message: "The description is one of the list's fields of words" });
287
+ }
288
+ }
289
+ return out;
290
+ }
216
291
  export function defineSite(blocks, o = {}) {
217
292
  const names = Object.keys(blocks);
218
293
  if (!names.length)
@@ -229,6 +304,14 @@ export function defineSite(blocks, o = {}) {
229
304
  const page = z.object({ ...pageBase(trailingSlash), ...o.pageExtend, blocks: z.array(union).min(1) });
230
305
  const schema = z
231
306
  .object({
307
+ /** Which format of the kit's the content is in (../content-migrate.ts): none for the first; set by the migrations, never edited. */
308
+ format: z
309
+ .number()
310
+ .int()
311
+ .min(0)
312
+ .max(CONTENT_FORMAT, { error: `A newer format than this kit's (${CONTENT_FORMAT}): the site needs a newer @caltho/site-kit` })
313
+ .optional()
314
+ .meta({ title: "Content format", readOnly: true, "x-ui": "hidden" }),
232
315
  /** The address it's served at once live (its main domain). */
233
316
  url: z.url({ protocol: /^https$/, error: "The site's address, starting https://" }).meta({ title: "Web address" }),
234
317
  lang: z.string().default("en-US").meta({ title: "Language" }),
@@ -267,11 +350,48 @@ export function defineSite(blocks, o = {}) {
267
350
  })
268
351
  .optional()
269
352
  .meta({ title: "Footer" }),
353
+ /**
354
+ * A header or footer of the business's own, built as a tree like a built section (../tree), in
355
+ * place of the kit's: its links, logo and button are elements, a phone menu an accordion. Left
356
+ * out, the kit's header and footer (from the menu, footer and Look above).
357
+ */
358
+ ownHeader: built.optional().meta({ title: "Your own header", "x-ui": "hidden" }),
359
+ ownFooter: built.optional().meta({ title: "Your own footer", "x-ui": "hidden" }),
360
+ /** A cookie banner, and visits counted only once a visitor accepts: off unless turned on (../consent.ts). */
361
+ consent: consent.optional(),
270
362
  /** Old addresses kept answering, when CalOS doesn't supply them at build. */
271
363
  redirects: z.array(z.object({ from: z.string().startsWith("/"), to: href, status: z.union([z.literal(301), z.literal(302)]).default(301) })).default([]),
272
364
  ...(o.look ? { look: lookSchema(o.look) } : {}),
273
365
  ...o.extend,
274
366
  pages: z.array(page).min(1),
367
+ /**
368
+ * The site's lists (./lists.ts): items of one kind kept once, repeated by built sections and,
369
+ * where a list has a page, each at its own address from one design.
370
+ */
371
+ lists: z
372
+ .record(listKey, z.object({
373
+ name: text.meta({ title: "Name" }),
374
+ fields: z.array(z.object({ key: fieldKey, label: text, kind: z.enum(FIELD_KINDS) })).max(30),
375
+ items: z.array(listItem).max(2000).default([]),
376
+ page: z
377
+ .object({
378
+ path: pageBase(trailingSlash).path,
379
+ title: fieldKey.meta({ title: "The field each page is titled by" }),
380
+ description: fieldKey.optional().meta({ title: "The field each page is described by" }),
381
+ blocks: z.array(union).min(1),
382
+ })
383
+ .optional()
384
+ .meta({ title: "A page for each item" }),
385
+ /** Where its items come from, when CalOS fills it from one of the business's tables: kept by CalOS, never drawn. */
386
+ source: z
387
+ .object({ table: z.string().max(60), fields: z.record(z.string(), z.string().max(60)), publish: z.enum(["draft", "auto"]).default("draft") })
388
+ .optional()
389
+ .meta({ "x-ui": "hidden" }),
390
+ }))
391
+ .default({})
392
+ .meta({ title: "Lists", "x-ui": "hidden" }),
393
+ /** Trees kept once and used by name (`use` blocks), changed in one place for every use: on a site that takes built sections. */
394
+ ...(names.includes("built") || names.includes("use") ? { components: z.array(component).max(50).default([]).meta({ title: "Components" }) } : {}),
275
395
  /** Sections the business put together and named, to add again anywhere ("Package card"): CalOS offers them beside the site's own kinds. */
276
396
  saved: z
277
397
  .array(z.object({ name: text.meta({ title: "Name" }), block: union }))
@@ -289,6 +409,10 @@ export function defineSite(blocks, o = {}) {
289
409
  if (placed)
290
410
  for (const i of backgroundProblems(s, o.look))
291
411
  ctx.addIssue({ code: "custom", path: i.path, message: i.message });
412
+ for (const i of builtProblems(s))
413
+ ctx.addIssue({ code: "custom", path: i.path, message: i.message });
414
+ for (const i of listProblems(s))
415
+ ctx.addIssue({ code: "custom", path: i.path, message: i.message });
292
416
  o.refine?.(s, ctx);
293
417
  });
294
418
  return {
@@ -332,6 +456,37 @@ function backgroundProblems(site, look) {
332
456
  saved.forEach((s, i) => s?.block && check(s.block, ["saved", i, "block"]));
333
457
  return out;
334
458
  }
459
+ /**
460
+ * What a built section's tree can't say alone (../tree: depth, size, an id used twice), for every
461
+ * tree the content keeps, and that each component a `use` block names is one the site has.
462
+ */
463
+ function builtProblems(site) {
464
+ const out = [];
465
+ const components = Array.isArray(site.components) ? site.components : [];
466
+ const keys = new Set();
467
+ components.forEach((c, i) => {
468
+ if (c?.key && keys.has(c.key))
469
+ out.push({ path: ["components", i, "key"], message: `Two components are called ${c.key}` });
470
+ if (c?.key)
471
+ keys.add(c.key);
472
+ if (c?.root)
473
+ for (const m of treeProblems(c.root))
474
+ out.push({ path: ["components", i, "root"], message: m });
475
+ });
476
+ const design = site.consent?.design;
477
+ if (design && !keys.has(design))
478
+ out.push({ path: ["consent", "design"], message: `The site has no component called ${design}` });
479
+ const check = (b, at) => {
480
+ if (b.type === "built" && b.root && typeof b.root === "object")
481
+ for (const m of treeProblems(b.root))
482
+ out.push({ path: [...at, "root"], message: m });
483
+ if (b.type === "use" && typeof b.component === "string" && !keys.has(b.component))
484
+ out.push({ path: [...at, "component"], message: `The site has no component called ${b.component}` });
485
+ };
486
+ site.pages.forEach((p, i) => p.blocks.forEach((b, j) => check(b, ["pages", i, "blocks", j])));
487
+ (site.saved ?? []).forEach((s, i) => s?.block && check(s.block, ["saved", i, "block"]));
488
+ return out;
489
+ }
335
490
  /** Search results cut a title at about 60 characters and a description at about 160: said, not refused, since the page still works. */
336
491
  function lengthWarnings(site) {
337
492
  return site.pages.flatMap((p, i) => [