@caltho/site-kit 0.2.10 → 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 (140) hide show
  1. package/README.md +146 -16
  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/cli/check.js +31 -1
  23. package/dist/consent.d.ts +33 -0
  24. package/dist/consent.js +36 -0
  25. package/dist/consent.test.d.ts +1 -0
  26. package/dist/consent.test.js +61 -0
  27. package/dist/content/index.d.ts +450 -33
  28. package/dist/content/index.js +297 -15
  29. package/dist/content/lists.d.ts +69 -0
  30. package/dist/content/lists.js +77 -0
  31. package/dist/content/lists.test.d.ts +1 -0
  32. package/dist/content/lists.test.js +257 -0
  33. package/dist/content/look.d.ts +260 -9
  34. package/dist/content/look.js +386 -22
  35. package/dist/content-migrate.d.ts +51 -0
  36. package/dist/content-migrate.js +69 -0
  37. package/dist/finish.d.ts +1 -1
  38. package/dist/finish.js +27 -6
  39. package/dist/fonts/families/dm-sans/LICENSE +93 -0
  40. package/dist/fonts/families/dm-sans/dm-sans-latin-ext-wght-italic.woff2 +0 -0
  41. package/dist/fonts/families/dm-sans/dm-sans-latin-ext-wght-normal.woff2 +0 -0
  42. package/dist/fonts/families/dm-sans/dm-sans-latin-wght-italic.woff2 +0 -0
  43. package/dist/fonts/families/dm-sans/dm-sans-latin-wght-normal.woff2 +0 -0
  44. package/dist/fonts/families/fraunces/LICENSE +93 -0
  45. package/dist/fonts/families/fraunces/fraunces-latin-ext-wght-italic.woff2 +0 -0
  46. package/dist/fonts/families/fraunces/fraunces-latin-ext-wght-normal.woff2 +0 -0
  47. package/dist/fonts/families/fraunces/fraunces-latin-wght-italic.woff2 +0 -0
  48. package/dist/fonts/families/fraunces/fraunces-latin-wght-normal.woff2 +0 -0
  49. package/dist/fonts/families/inter/LICENSE +93 -0
  50. package/dist/fonts/families/inter/inter-latin-ext-wght-italic.woff2 +0 -0
  51. package/dist/fonts/families/inter/inter-latin-ext-wght-normal.woff2 +0 -0
  52. package/dist/fonts/families/inter/inter-latin-wght-italic.woff2 +0 -0
  53. package/dist/fonts/families/inter/inter-latin-wght-normal.woff2 +0 -0
  54. package/dist/fonts/families/lora/LICENSE +93 -0
  55. package/dist/fonts/families/lora/lora-latin-ext-wght-italic.woff2 +0 -0
  56. package/dist/fonts/families/lora/lora-latin-ext-wght-normal.woff2 +0 -0
  57. package/dist/fonts/families/lora/lora-latin-wght-italic.woff2 +0 -0
  58. package/dist/fonts/families/lora/lora-latin-wght-normal.woff2 +0 -0
  59. package/dist/fonts/families/manrope/LICENSE +93 -0
  60. package/dist/fonts/families/manrope/manrope-latin-ext-wght-normal.woff2 +0 -0
  61. package/dist/fonts/families/manrope/manrope-latin-wght-normal.woff2 +0 -0
  62. package/dist/fonts/families/newsreader/LICENSE +93 -0
  63. package/dist/fonts/families/newsreader/newsreader-latin-ext-wght-italic.woff2 +0 -0
  64. package/dist/fonts/families/newsreader/newsreader-latin-ext-wght-normal.woff2 +0 -0
  65. package/dist/fonts/families/newsreader/newsreader-latin-wght-italic.woff2 +0 -0
  66. package/dist/fonts/families/newsreader/newsreader-latin-wght-normal.woff2 +0 -0
  67. package/dist/fonts/families/outfit/LICENSE +93 -0
  68. package/dist/fonts/families/outfit/outfit-latin-ext-wght-normal.woff2 +0 -0
  69. package/dist/fonts/families/outfit/outfit-latin-wght-normal.woff2 +0 -0
  70. package/dist/fonts/families/playfair-display/LICENSE +93 -0
  71. package/dist/fonts/families/playfair-display/playfair-display-latin-ext-wght-italic.woff2 +0 -0
  72. package/dist/fonts/families/playfair-display/playfair-display-latin-ext-wght-normal.woff2 +0 -0
  73. package/dist/fonts/families/playfair-display/playfair-display-latin-wght-italic.woff2 +0 -0
  74. package/dist/fonts/families/playfair-display/playfair-display-latin-wght-normal.woff2 +0 -0
  75. package/dist/fonts/families/source-serif-4/LICENSE +93 -0
  76. package/dist/fonts/families/source-serif-4/source-serif-4-latin-ext-wght-italic.woff2 +0 -0
  77. package/dist/fonts/families/source-serif-4/source-serif-4-latin-ext-wght-normal.woff2 +0 -0
  78. package/dist/fonts/families/source-serif-4/source-serif-4-latin-wght-italic.woff2 +0 -0
  79. package/dist/fonts/families/source-serif-4/source-serif-4-latin-wght-normal.woff2 +0 -0
  80. package/dist/fonts/families/work-sans/LICENSE +93 -0
  81. package/dist/fonts/families/work-sans/work-sans-latin-ext-wght-italic.woff2 +0 -0
  82. package/dist/fonts/families/work-sans/work-sans-latin-ext-wght-normal.woff2 +0 -0
  83. package/dist/fonts/families/work-sans/work-sans-latin-wght-italic.woff2 +0 -0
  84. package/dist/fonts/families/work-sans/work-sans-latin-wght-normal.woff2 +0 -0
  85. package/dist/fonts/google.json +1 -0
  86. package/dist/fonts-google.d.ts +35 -0
  87. package/dist/fonts-google.js +70 -0
  88. package/dist/fonts.css +365 -0
  89. package/dist/frame.test.d.ts +1 -0
  90. package/dist/frame.test.js +58 -0
  91. package/dist/migrate.d.ts +45 -0
  92. package/dist/migrate.js +284 -0
  93. package/dist/next/block-mark.d.ts +7 -0
  94. package/dist/next/block-mark.js +16 -0
  95. package/dist/next/blocks.d.ts +11 -0
  96. package/dist/next/blocks.js +35 -0
  97. package/dist/next/built.d.ts +40 -0
  98. package/dist/next/built.js +318 -0
  99. package/dist/next/consent.d.ts +4 -0
  100. package/dist/next/consent.js +13 -0
  101. package/dist/next/data.d.ts +6 -2
  102. package/dist/next/data.js +10 -4
  103. package/dist/next/frame.d.ts +42 -0
  104. package/dist/next/frame.js +73 -0
  105. package/dist/next/index.d.ts +8 -2
  106. package/dist/next/index.js +9 -3
  107. package/dist/next/menu-group.d.ts +11 -0
  108. package/dist/next/menu-group.js +33 -0
  109. package/dist/next/metadata.js +3 -2
  110. package/dist/next/parts/Img.d.ts +6 -1
  111. package/dist/next/parts/Img.js +11 -3
  112. package/dist/next/parts/LookStyle.js +3 -1
  113. package/dist/next/preview.js +511 -27
  114. package/dist/next/sections.d.ts +19 -10
  115. package/dist/next/sections.js +134 -17
  116. package/dist/policies.js +22 -4
  117. package/dist/prepare.d.ts +15 -1
  118. package/dist/prepare.js +125 -3
  119. package/dist/preview-match.d.ts +29 -0
  120. package/dist/preview-match.js +58 -1
  121. package/dist/schema.d.ts +10 -0
  122. package/dist/schema.js +29 -1
  123. package/dist/sections/icons.d.ts +49 -0
  124. package/dist/sections/icons.js +55 -0
  125. package/dist/sections/index.d.ts +188 -12
  126. package/dist/sections/index.js +136 -10
  127. package/dist/sections/presets.d.ts +13 -0
  128. package/dist/sections/presets.js +300 -0
  129. package/dist/sections.css +1584 -41
  130. package/dist/sections.test.js +276 -14
  131. package/dist/seo.js +2 -1
  132. package/dist/tree/behaviors.d.ts +50 -0
  133. package/dist/tree/behaviors.js +67 -0
  134. package/dist/tree/classes.d.ts +24 -0
  135. package/dist/tree/classes.js +94 -0
  136. package/dist/tree/index.d.ts +145 -0
  137. package/dist/tree/index.js +196 -0
  138. package/dist/tree/messages.d.ts +45 -0
  139. package/dist/tree/messages.js +7 -0
  140. package/package.json +22 -3
@@ -0,0 +1,284 @@
1
+ // Moving a site's own code to what a new kit expects, as text: CalOS reads a site's files from its
2
+ // repository, runs the migrations it hasn't had, and commits what they change on the kit update's
3
+ // branch, never main, so Cloudflare builds it as a preview first and it goes live only once that built.
4
+ // Nothing here runs the site's code, and a migration changes a file only where it's exactly the
5
+ // starter's (a known version, or a known block within it): a file the site has made its own is
6
+ // left as it is and the migration reported for a person, who moves it by hand. What a site has had
7
+ // is kept in its repository (.caltho/migrations.json), so each runs once.
8
+ export const MIGRATIONS_FILE = ".caltho/migrations.json";
9
+ async function sha256(text) {
10
+ const bytes = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text)));
11
+ return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
12
+ }
13
+ // The starter's own header, footer and menu group, every version it has shipped.
14
+ const STARTER_FRAME_FILES = {
15
+ "components/Header.tsx": [
16
+ "0bf8abc156b59a5e6302921a7b0a16d4d391f519ee595de61e039aeb4b92ac92",
17
+ "4b101f874022fe290f659dd519849cc89b79c68791e8bc92386ae86718902bf5",
18
+ "e4ff7a7f332919c3611bf55f1fe7a29877b24445d164ab99f2875121af3895b9",
19
+ ],
20
+ "components/Footer.tsx": ["7971cfd0c2bdf4cef2a69871869dcc90c678bd272d43ce0ac6c8adf45ee8cf54", "88d6efc19fe636fe8c517faa303b4e05ede779942c2d07a3c551011d969873b4"],
21
+ "components/MenuGroup.tsx": ["4f2e53fbd9bc5fdf3c9e033e28f4e6582470a70a4e05ac51270a233a05345aa8"],
22
+ };
23
+ const OLD_FRAME = `export function Frame({ site, current, children }: { site: Content; current: string; children: ReactNode }) {
24
+ return (
25
+ <>
26
+ <a className="skip" href="#main">
27
+ Skip to content
28
+ </a>
29
+ <Header site={site} current={current} />
30
+ <main id="main" tabIndex={-1}>
31
+ {children}
32
+ </main>
33
+ <Footer site={site} />
34
+ </>
35
+ );
36
+ }`;
37
+ const NEW_FRAME = `/**
38
+ * The frame around every page: the kit's (skip link, header, footer), so a new header or footer
39
+ * option reaches this site with a kit release. A site with a header of its own passes it:
40
+ * <KitFrame header={<MyHeader … />}>, and leaves \`header\` out of content/look.ts unless it follows
41
+ * the Look's Header (headerOf).
42
+ */
43
+ export function Frame({ site, current, children }: { site: Content; current: string; children: ReactNode }) {
44
+ return (
45
+ <KitFrame site={site} current={current} look={look}>
46
+ {children}
47
+ </KitFrame>
48
+ );
49
+ }`;
50
+ /**
51
+ * The starter's header and footer become the kit's (KitFrame), which follow the Look's Header, so
52
+ * the site's look.ts lists one and CalOS offers its options. The starter's footer was its Slate:
53
+ * kept, where the site's CSS has --dark. The old header and footer CSS is left, unused.
54
+ */
55
+ /** The starter's design, told to the kit's frame: its dark footer and its words on it, and its buttons' weight. */
56
+ const FRAME_CSS = `/* The kit draws the header and footer (KitFrame): in this design's dark, its buttons as its own. */
57
+ :root {
58
+ --kit-footer-bg: var(--dark);
59
+ --kit-footer-fg: var(--on-dark);
60
+ --kit-footer-muted: var(--muted-on-dark);
61
+ --kit-footer-line: var(--line-on-dark);
62
+ --kit-button-weight: var(--b-weight);
63
+ }
64
+ `;
65
+ const kitFrame = {
66
+ id: "kit-frame",
67
+ since: "0.2.11",
68
+ title: "The header and footer are the kit's (KitFrame), so new header and footer options reach this site with kit releases",
69
+ async run(read) {
70
+ const page = await read("components/SitePage.tsx");
71
+ if (!page)
72
+ return { manual: "it has no components/SitePage.tsx: its pages are drawn its own way" };
73
+ if (page.includes("KitFrame"))
74
+ return { done: true };
75
+ if (!page.includes(OLD_FRAME))
76
+ return { manual: "its components/SitePage.tsx draws the frame its own way (not the starter's Frame)" };
77
+ for (const [path, known] of Object.entries(STARTER_FRAME_FILES)) {
78
+ const text = await read(path);
79
+ if (text !== null && !known.includes(await sha256(text)))
80
+ return { manual: `its ${path} is its own, not the starter's` };
81
+ }
82
+ const look = await read("content/look.ts");
83
+ if (!look || !/export const look: LookOptions = \{[\s\S]*\n\};\s*$/.test(look))
84
+ return { manual: "its content/look.ts isn't the starter's shape" };
85
+ const imports = /import \{([^}]*)\} from "@caltho\/site-kit\/next";/;
86
+ const m = page.match(imports);
87
+ if (!m)
88
+ return { manual: "its components/SitePage.tsx doesn't import from @caltho/site-kit/next" };
89
+ const names = [...new Set([...m[1].split(",").map((x) => x.trim()).filter(Boolean), "KitFrame"])].sort((a, b) => a.localeCompare(b));
90
+ let nextPage = page
91
+ .replace(OLD_FRAME, NEW_FRAME)
92
+ .replace(imports, `import { ${names.join(", ")} } from "@caltho/site-kit/next";`)
93
+ .replace('import { Footer } from "./Footer";\n', "")
94
+ .replace('import { Header } from "./Header";\n', "");
95
+ if (!nextPage.includes('from "@/content/look"'))
96
+ nextPage = nextPage.replace(imports, (line) => `${line}\nimport { look } from "@/content/look";`);
97
+ const files = [{ path: "components/SitePage.tsx", text: nextPage }];
98
+ if (!/^\s*header:/m.test(look)) {
99
+ files.push({
100
+ path: "content/look.ts",
101
+ text: look.replace(/\n\};\s*$/, `\n /** The kit's header (KitFrame) follows the Look's Header, so CalOS offers its options: sticky, with a line under it, name left and menu right to start. */\n header: { sticky: true, style: "line", layout: "spread" },\n};\n`),
102
+ });
103
+ }
104
+ const css = await read("app/globals.css");
105
+ if (css && /--dark\s*:/.test(css) && !css.includes("--kit-footer-bg")) {
106
+ files.push({ path: "app/globals.css", text: `${css.replace(/\s*$/, "")}\n\n${FRAME_CSS}` });
107
+ }
108
+ for (const path of Object.keys(STARTER_FRAME_FILES))
109
+ if ((await read(path)) !== null)
110
+ files.push({ path, remove: true });
111
+ return { files };
112
+ },
113
+ };
114
+ const ACCENT_CSS = `/* The accent colour: a button that stands out on a photo or a dark band (an action's style "accent"). */
115
+ .btn-accent {
116
+ background: var(--look-accent, var(--accent));
117
+ border-color: var(--look-accent, var(--accent));
118
+ color: var(--look-on-accent, var(--ink));
119
+ }
120
+ .btn-accent:hover {
121
+ background: color-mix(in srgb, var(--look-accent, var(--accent)) 88%, #000000);
122
+ }
123
+ `;
124
+ /**
125
+ * Actions may be in the accent colour (style "accent"), which the starter's design draws as
126
+ * .btn-accent: added to a site whose CSS has the starter's buttons (.btn-primary) and none yet.
127
+ */
128
+ const accentButtons = {
129
+ id: "accent-buttons",
130
+ since: "0.2.11",
131
+ title: "Buttons in the accent colour: an action's style can be the accent, for one that stands out on a photo or a dark band",
132
+ async run(read) {
133
+ const css = await read("app/globals.css");
134
+ if (!css)
135
+ return { manual: "it has no app/globals.css" };
136
+ if (css.includes(".btn-accent"))
137
+ return { done: true };
138
+ if (!css.includes(".btn-primary {"))
139
+ return { manual: "its buttons aren't the starter's (.btn-primary): it draws an accent button its own way" };
140
+ return { files: [{ path: "app/globals.css", text: `${css.replace(/\s*$/, "")}\n\n${ACCENT_CSS}` }] };
141
+ },
142
+ };
143
+ /** The starter's dark, a palette colour the business may remove (Cal does, building from a picture). */
144
+ const STARTER_DARK = "--dark:var(--look-swatch-slate, #1d2a33);";
145
+ /**
146
+ * The design's dark (its dark sections and the footer) following the theme when the business has
147
+ * taken its Slate away: the theme's own dark (the darker of its page and text), not a navy no
148
+ * colour in the theme shows.
149
+ */
150
+ const darkFollowsLook = {
151
+ id: "dark-follows-look",
152
+ since: "0.2.11",
153
+ title: "The design's dark follows the theme: with its Slate colour removed, dark sections and the footer take the theme's own dark, not a hidden navy",
154
+ async run(read) {
155
+ const css = await read("app/globals.css");
156
+ if (!css)
157
+ return { manual: "it has no app/globals.css" };
158
+ if (css.includes("--dark:var(--look-swatch-slate, var(--look-dark"))
159
+ return { done: true };
160
+ if (!css.includes(STARTER_DARK))
161
+ return { manual: "its dark isn't the starter's (--dark from Slate): it draws its dark its own way" };
162
+ return { files: [{ path: "app/globals.css", text: css.replace(STARTER_DARK, "--dark:var(--look-swatch-slate, var(--look-dark, #1d2a33));") }] };
163
+ },
164
+ };
165
+ /** The starter's buttons' border, before and after it reads the Look's button border (width and colour). */
166
+ const BUTTON_BORDER = [
167
+ [" border: 2px solid transparent;\n border-radius: var(--radius-button);", " border: var(--look-button-border-width, 2px) solid transparent;\n border-radius: var(--radius-button);"],
168
+ [" border-color: color-mix(in srgb, var(--brand-fill) var(--fill), var(--c-link));", " border-color: var(--look-button-border-colour, color-mix(in srgb, var(--brand-fill) var(--fill), var(--c-link)));"],
169
+ [" border-color: var(--look-accent, var(--accent));\n color: var(--look-on-accent, var(--ink));", " border-color: var(--look-button-border-colour, var(--look-accent, var(--accent)));\n color: var(--look-on-accent, var(--ink));"],
170
+ [" border-color: var(--on-brand);\n color: color-mix(in srgb, var(--brand-fill) var(--fill), var(--on-brand));", " border-color: var(--look-button-border-colour, var(--on-brand));\n color: color-mix(in srgb, var(--brand-fill) var(--fill), var(--on-brand));"],
171
+ ];
172
+ /**
173
+ * Buttons with the Look's border: its width and colour, where the business chooses one (a dark
174
+ * outline on a yellow button). Every line the starter's, or it's left for a person.
175
+ */
176
+ const buttonBorders = {
177
+ id: "button-borders",
178
+ since: "0.2.11",
179
+ title: "Button borders: the Look's button border (its width and colour) on the site's buttons",
180
+ async run(read) {
181
+ const css = await read("app/globals.css");
182
+ if (!css)
183
+ return { manual: "it has no app/globals.css" };
184
+ if (css.includes("--look-button-border-width"))
185
+ return { done: true };
186
+ if (!BUTTON_BORDER.every(([from]) => css.includes(from)))
187
+ return { manual: "its buttons aren't the starter's: it draws their borders its own way" };
188
+ return { files: [{ path: "app/globals.css", text: BUTTON_BORDER.reduce((t, [from, to]) => t.replace(from, to), css) }] };
189
+ },
190
+ };
191
+ /** The starter's own font imports, which the kit's fonts.css takes the place of. */
192
+ const STARTER_FONT_IMPORTS = /^import "@fontsource\/(fraunces|inter)\/latin-\d+\.css";\n/gm;
193
+ const STARTER_FONTS_LINE = ` fonts: { Inter: '"Inter", system-ui, Arial, sans-serif', Fraunces: '"Fraunces", Georgia, serif' },`;
194
+ /**
195
+ * The kit's fonts (fonts.css: ten variable families, every weight) in place of the starter's two
196
+ * fixed-weight ones, and listed in the site's look so the business can pick any of them. Where the
197
+ * site loads fonts of its own, or lists its own, it's left for a person.
198
+ */
199
+ const kitFonts = {
200
+ id: "kit-fonts",
201
+ since: "0.2.11",
202
+ title: "The kit's fonts: ten variable families with every weight, any of them a choice in the site's Look",
203
+ async run(read) {
204
+ const layout = await read("app/layout.tsx");
205
+ const look = await read("content/look.ts");
206
+ if (!layout || !look)
207
+ return { manual: "it has no app/layout.tsx or content/look.ts like the starter's" };
208
+ if (layout.includes("@caltho/site-kit/fonts.css"))
209
+ return { done: true };
210
+ const own = layout.match(/^import "@fontsource[^"]*";$/gm) ?? [];
211
+ if (!own.length || own.some((l) => !/^import "@fontsource\/(fraunces|inter)\/latin-\d+\.css";$/.test(l)))
212
+ return { manual: "its app/layout.tsx loads fonts of its own" };
213
+ if (!look.includes(STARTER_FONTS_LINE))
214
+ return { manual: "its content/look.ts lists fonts of its own" };
215
+ const nextLayout = layout.replace(STARTER_FONT_IMPORTS, "").replace('import "@caltho/site-kit/sections.css";', 'import "@caltho/site-kit/sections.css";\nimport "@caltho/site-kit/fonts.css";');
216
+ if (!nextLayout.includes("@caltho/site-kit/fonts.css"))
217
+ return { manual: "its app/layout.tsx doesn't import the kit's sections.css, where the fonts go beside it" };
218
+ const files = [
219
+ { path: "app/layout.tsx", text: nextLayout },
220
+ {
221
+ path: "content/look.ts",
222
+ text: look
223
+ .replace(STARTER_FONTS_LINE, " fonts: KIT_FONTS,")
224
+ .replace('import type { LookOptions } from "@caltho/site-kit/content";', 'import { KIT_FONTS, type LookOptions } from "@caltho/site-kit/content";'),
225
+ },
226
+ ];
227
+ const css = await read("app/globals.css");
228
+ if (css)
229
+ files.push({ path: "app/globals.css", text: css.replace('--font-body: "Inter";', '--font-body: "Inter Variable";').replace('--font-display: "Fraunces";', '--font-display: "Fraunces Variable";') });
230
+ const pkg = await read("package.json");
231
+ if (pkg)
232
+ files.push({ path: "package.json", text: pkg.replace(/^\s*"@fontsource\/(fraunces|inter)": "[^"]*",\n/gm, "") });
233
+ return { files };
234
+ },
235
+ };
236
+ /** Every migration, oldest first. */
237
+ export const MIGRATIONS = [kitFrame, accentButtons, kitFonts, darkFollowsLook, buttonBorders];
238
+ const parts = (v) => v.split(".").map(Number);
239
+ function atLeast(v, min) {
240
+ const [a, b] = [parts(v), parts(min)];
241
+ for (let i = 0; i < 3; i++)
242
+ if ((a[i] ?? 0) !== (b[i] ?? 0))
243
+ return (a[i] ?? 0) > (b[i] ?? 0);
244
+ return true;
245
+ }
246
+ /**
247
+ * What moving a site to kit `version` changes in its own files: each migration up to that version it
248
+ * hasn't had, in order, each seeing the files as the ones before it left them.
249
+ */
250
+ export async function migrateSite(read, version, migrations = MIGRATIONS) {
251
+ const recorded = await read(MIGRATIONS_FILE);
252
+ let had = [];
253
+ try {
254
+ const parsed = recorded ? JSON.parse(recorded) : null;
255
+ if (Array.isArray(parsed?.applied))
256
+ had = parsed.applied.filter((x) => typeof x === "string");
257
+ }
258
+ catch {
259
+ // An unreadable record: as if none, each migration checking the files for itself.
260
+ }
261
+ const changed = new Map();
262
+ const view = async (path) => {
263
+ const c = changed.get(path);
264
+ return c ? ("remove" in c ? null : c.text) : read(path);
265
+ };
266
+ const applied = [];
267
+ const manual = [];
268
+ for (const m of migrations) {
269
+ if (had.includes(m.id) || !atLeast(version, m.since))
270
+ continue;
271
+ const r = await m.run(view);
272
+ if ("manual" in r) {
273
+ manual.push({ id: m.id, title: m.title, why: r.manual });
274
+ continue;
275
+ }
276
+ if ("files" in r)
277
+ for (const f of r.files)
278
+ changed.set(f.path, f);
279
+ applied.push({ id: m.id, title: m.title });
280
+ }
281
+ if (applied.length)
282
+ changed.set(MIGRATIONS_FILE, { path: MIGRATIONS_FILE, text: `${JSON.stringify({ applied: [...had, ...applied.map((a) => a.id)] }, null, 2)}\n` });
283
+ return { files: [...changed.values()], applied, manual };
284
+ }
@@ -0,0 +1,7 @@
1
+ /** True inside CalOS's preview (SitePreview provides it). */
2
+ export declare const InPreview: import("react").Context<boolean>;
3
+ /** The attribute a block's mark carries: the block's index on its page. */
4
+ export declare const BLOCK_MARK = "data-calos-block";
5
+ export declare function BlockMark({ at }: {
6
+ at: number;
7
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,16 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ // Where each block starts, for CalOS's preview: a site puts <BlockMark at={i} /> before each block it
4
+ // draws, and the preview knows exactly what each block drew (none, one, or several elements), with
5
+ // nothing guessed from the words. On the live site it draws nothing; in the preview, an empty
6
+ // <template>, which the browser never shows and styles never see as drawn.
7
+ import { createContext, useContext } from "react";
8
+ /** True inside CalOS's preview (SitePreview provides it). */
9
+ export const InPreview = createContext(false);
10
+ /** The attribute a block's mark carries: the block's index on its page. */
11
+ export const BLOCK_MARK = "data-calos-block";
12
+ export function BlockMark({ at }) {
13
+ if (!useContext(InPreview))
14
+ return null;
15
+ return _jsx("template", { [BLOCK_MARK]: String(at) });
16
+ }
@@ -0,0 +1,11 @@
1
+ import { type ReactNode } from "react";
2
+ import { type KitBlock, type KitClasses } from "./sections.tsx";
3
+ export declare function KitBlocks<B extends KitBlock>({ blocks, render, classes, level, }: {
4
+ blocks: readonly B[];
5
+ /** The site's own component for a block, or null (or undefined) for a kind it leaves to the kit. */
6
+ render: (block: B, i: number) => ReactNode | null | undefined;
7
+ /** Classes of the site's own for kit sections, in place of the kit's (KitSection). */
8
+ classes?: Partial<KitClasses>;
9
+ /** The heading level of a block's own heading: 1 for the one that carries the page's h1. */
10
+ level?: (i: number) => number;
11
+ }): import("react").JSX.Element;
@@ -0,0 +1,35 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ // A page's blocks drawn, the same way on every site: each after its mark (BlockMark, for CalOS's
3
+ // preview), in a frame when its Layout has a background (a colour, or a photo under a shade), then
4
+ // the site's own component for it, else the kit's (KitSection). A site hands it how it draws its own
5
+ // kinds (`render`), and nothing else: a block whose kind it doesn't draw (one the business put
6
+ // together in CalOS, a slideshow, one the kit adds later) is drawn by the kit in the site's Look.
7
+ //
8
+ // Classes only, no style attribute (the content security policy blocks them): the frame's colour is
9
+ // a class (sections.css reads the Look's variables), and its words go light by a class when its
10
+ // background is dark, worked out from the Look's colour values (lookValues: at build, and as sent
11
+ // in the preview).
12
+ import { Fragment } from "react";
13
+ import { frameClass, hasBackground, linesClass } from "../content/index.js";
14
+ import { BlockMark } from "./block-mark.js";
15
+ import { lookValues } from "./data.js";
16
+ import { Img } from "./parts/Img.js";
17
+ import { KitSection } from "./sections.js";
18
+ export function KitBlocks({ blocks, render, classes, level = () => 2, }) {
19
+ const vars = lookValues();
20
+ return (_jsx(_Fragment, { children: blocks.map((block, i) => {
21
+ const own = render(block, i);
22
+ const drawn = own ?? _jsx(KitSection, { s: block, level: level(i), classes: classes });
23
+ return (_jsxs(Fragment, { children: [_jsx(BlockMark, { at: i }), lined(block, hasBackground(block) ? _jsx(Frame, { block: block, vars: vars, children: drawn }) : drawn)] }, i));
24
+ }) }));
25
+ }
26
+ /** A section between lines across the page's full width, where its Layout says. */
27
+ function lined(block, drawn) {
28
+ const cls = linesClass(block);
29
+ return cls ? _jsx("div", { className: cls, children: drawn }) : drawn;
30
+ }
31
+ /** What's behind a section: its colour, and a photo under a shade, the section drawn over them. */
32
+ function Frame({ block, vars, children }) {
33
+ const bg = block.placement?.background;
34
+ return (_jsxs("div", { className: frameClass(block, vars), children: [bg?.photo?.src && (_jsx("div", { className: "kit-frame-bg", "aria-hidden": "true", children: _jsx(Img, { src: bg.photo.src, alt: "", sizes: "100vw" }) })), children] }));
35
+ }
@@ -0,0 +1,40 @@
1
+ import { type ImageRef } from "../tree/index.ts";
2
+ import type { KitBlock } from "./sections.tsx";
3
+ /** What a slot of a component is filled with where it's used. */
4
+ type Fill = {
5
+ text?: string;
6
+ href?: string;
7
+ image?: ImageRef;
8
+ };
9
+ /** A built section: its tree, the block's id and Layout on its root. */
10
+ export declare function BuiltSection({ s }: {
11
+ s: KitBlock;
12
+ }): import("react").JSX.Element | null;
13
+ /** A component used by name, its slots filled; nothing (a quiet box in the preview) when the site has no such component. */
14
+ export declare function UseSection({ s }: {
15
+ s: KitBlock;
16
+ }): import("react").JSX.Element | null;
17
+ /**
18
+ * One of the site's components drawn outside a page's blocks (a cookie banner's design): its slots
19
+ * filled, the kit's attributes on the elements of the slots named in `marks`. Nothing for a
20
+ * component the site hasn't.
21
+ */
22
+ export declare function ComponentTree({ name, slots, marks }: {
23
+ name: string;
24
+ slots: Record<string, Fill>;
25
+ marks?: Record<string, Record<string, string>>;
26
+ }): import("react").JSX.Element | null;
27
+ /**
28
+ * The site's own header or footer (content's ownHeader, ownFooter): its tree drawn as a built
29
+ * section's, its links to the page it's on marked (aria-current).
30
+ */
31
+ export declare function BuiltFrame({ tree, current, which }: {
32
+ tree: {
33
+ root?: unknown;
34
+ };
35
+ current: string;
36
+ which?: "header" | "footer";
37
+ }): import("react").JSX.Element | null;
38
+ /** A bound value as a link: an email address is a mailto:, a phone number a tel: (dialled as its digits), anything else as it is. */
39
+ export declare function linkFor(v: string): string;
40
+ export {};