@marktiderman/genesis-sites 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +465 -2
- package/dist/assets/form-island.c0f6fac859.js +51 -0
- package/dist/assets/islands.77d11f5697.js +1 -0
- package/dist/assets/sites-forms.4e6aa42497.css +10 -0
- package/dist/assets/sites-forms.css +10 -0
- package/dist/assets/sites.css +2 -0
- package/dist/assets/sites.e1ffd2708b.css +2 -0
- package/dist/blocks.d.ts +13 -0
- package/dist/blocks.js +26 -0
- package/dist/blocks.js.map +1 -0
- package/dist/chunk-3VKTBE7S.js +69 -0
- package/dist/chunk-3VKTBE7S.js.map +1 -0
- package/dist/chunk-C375ZPU2.js +2241 -0
- package/dist/chunk-C375ZPU2.js.map +1 -0
- package/dist/chunk-IXMY4B56.js +877 -0
- package/dist/chunk-IXMY4B56.js.map +1 -0
- package/dist/chunk-VZUDRPFG.js +13 -0
- package/dist/chunk-VZUDRPFG.js.map +1 -0
- package/dist/chunk-ZGWGQGT2.js +595 -0
- package/dist/chunk-ZGWGQGT2.js.map +1 -0
- package/dist/import.d.ts +130 -0
- package/dist/import.js +330 -0
- package/dist/import.js.map +1 -0
- package/dist/index.d.ts +273 -0
- package/dist/index.js +254 -0
- package/dist/index.js.map +1 -0
- package/dist/registry-D_DCWRTc.d.ts +1030 -0
- package/dist/sections.d.ts +802 -0
- package/dist/sections.js +6 -0
- package/dist/sections.js.map +1 -0
- package/dist/store-DkSycDhq.d.ts +443 -0
- package/dist/supabase.d.ts +41 -0
- package/dist/supabase.js +283 -0
- package/dist/supabase.js.map +1 -0
- package/dist/web.d.ts +136 -0
- package/dist/web.js +479 -0
- package/dist/web.js.map +1 -0
- package/migrations/0001_genesis_sites.sql +821 -0
- package/migrations/0002_genesis_sites_posts.sql +404 -0
- package/migrations/tests/0001_genesis_sites.test.sql +349 -0
- package/migrations/tests/0002_genesis_sites_posts.test.sql +242 -0
- package/package.json +152 -3
package/dist/sections.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { Anchor, ButtonLink, CHIP_TONES, HiddenTitle, ICONS, IconName, ItemTitle, MAP_PROVIDERS, SectionBand, SectionIntro, SectionTitle, SiteFormHeader, SiteImage, TextLink, WEEKDAYS, blogListSection, builtInSections, ctaSection, faqSection, featuresSection, fileDescription, fileHref, fileSection, footerSection, formEmbedSection, formatPostDate, formatTime, gallerySection, heroSection, linkAttrs, logosSection, mapEmbedUrl, mapPageUrl, mapSection, navSection, postBodySection, pricingSection, resolveHref, richTextSection, scheduleSection, siteFormSlots, statsSection, teamSection, testimonialsSection, timelineSection, titleId } from './chunk-C375ZPU2.js';
|
|
2
|
+
import './chunk-VZUDRPFG.js';
|
|
3
|
+
import './chunk-ZGWGQGT2.js';
|
|
4
|
+
import './chunk-3VKTBE7S.js';
|
|
5
|
+
//# sourceMappingURL=sections.js.map
|
|
6
|
+
//# sourceMappingURL=sections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"sections.js"}
|
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
import { h as FontChoice, a as SiteDoc, A as AnySectionDefinition, N as ReleaseSnapshot, R as ReleasePage, J as Release, r as PageMeta, P as PageDoc, f as PageKind, K as ReleaseMeta, b as PostSummary, L as LivePost, x as PostMeta, w as PostDraft, V as SavePostInput, W as SavePostResult, k as ImportPost, l as ImportPostsResult, e as SiteDocInput, u as PostDoc, z as PostStatus } from './registry-D_DCWRTc.js';
|
|
2
|
+
import { CanonicalBrand } from '@marktiderman/genesis-design-system';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A site's theme, from its brand: one `<style>` of CSS variables per site.
|
|
6
|
+
*
|
|
7
|
+
* The brand comes from the host (`BrandSource`) at render time, never copied
|
|
8
|
+
* into the site. A publish checks the brand's colours and records its hash;
|
|
9
|
+
* a page rendered with a different brand (edited since) is served with a
|
|
10
|
+
* short cache and a warning until the site is published again, which checks
|
|
11
|
+
* the new colours and purges the old pages. It
|
|
12
|
+
* may be a full `CanonicalBrand` or the minimal shape a brand screen collects
|
|
13
|
+
* (`SiteBrand`: a name, a primary colour for light and dark, a heading and a
|
|
14
|
+
* body font from a fixed list, a logo).
|
|
15
|
+
*
|
|
16
|
+
* The variables are design-system `themeFromBrand(brand).variables` (the
|
|
17
|
+
* brand's scales, `--color-*`) plus the genesis-ui roles the sections are
|
|
18
|
+
* styled with (`--primary`, `--background`, `--font-display`, ...), so the
|
|
19
|
+
* one shared stylesheet serves every brand. Foregrounds are picked for
|
|
20
|
+
* contrast: the text on a primary button is whichever of the brand's light
|
|
21
|
+
* or dark text reads better, and the focus ring falls back to the text
|
|
22
|
+
* colour when the primary is too faint on the page.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/** The minimal brand: what a brand screen collects. */
|
|
26
|
+
interface SiteBrand {
|
|
27
|
+
name: string;
|
|
28
|
+
colors: {
|
|
29
|
+
/** The primary colour in light mode, as #rrggbb. */
|
|
30
|
+
primary: string;
|
|
31
|
+
/** The primary in dark mode. Default: the light primary, lightened. */
|
|
32
|
+
primaryDark?: string;
|
|
33
|
+
};
|
|
34
|
+
fonts?: {
|
|
35
|
+
heading?: FontChoice;
|
|
36
|
+
body?: FontChoice;
|
|
37
|
+
};
|
|
38
|
+
/** The logo, shown in the header and footer and named in JSON-LD. */
|
|
39
|
+
logo?: {
|
|
40
|
+
src: string;
|
|
41
|
+
alt?: string;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** What a `BrandSource` may return: the minimal brand, or a full design-system brand (with an optional logo). */
|
|
45
|
+
type BrandInput = SiteBrand | (CanonicalBrand & {
|
|
46
|
+
logo?: {
|
|
47
|
+
src: string;
|
|
48
|
+
alt?: string;
|
|
49
|
+
};
|
|
50
|
+
displayName?: string;
|
|
51
|
+
});
|
|
52
|
+
/** The host's brand, read at render time. */
|
|
53
|
+
interface BrandSource {
|
|
54
|
+
getBrand(siteId: string): Promise<BrandInput | null> | BrandInput | null;
|
|
55
|
+
}
|
|
56
|
+
interface FontInfo {
|
|
57
|
+
label: string;
|
|
58
|
+
/** The CSS font stack. */
|
|
59
|
+
stack: string;
|
|
60
|
+
/** The Google Fonts `family=` value, when it isn't a system font. */
|
|
61
|
+
google?: string;
|
|
62
|
+
}
|
|
63
|
+
/** The fonts a brand or site may choose. */
|
|
64
|
+
declare const FONT_CHOICES: Readonly<Record<FontChoice, FontInfo>>;
|
|
65
|
+
/** `#abc` / `#aabbcc` → [r, g, b] (0–255). Throws on anything else. */
|
|
66
|
+
declare function parseHex(hex: string): [number, number, number];
|
|
67
|
+
/** WCAG relative luminance. */
|
|
68
|
+
declare function luminance(hex: string): number;
|
|
69
|
+
/** WCAG contrast ratio (1–21) of two colours. */
|
|
70
|
+
declare function contrastRatio(a: string, b: string): number;
|
|
71
|
+
/** Mix a colour toward white (`amount` > 0) or black (< 0), 0–1. */
|
|
72
|
+
declare function mix(hex: string, amount: number): string;
|
|
73
|
+
/** `#4f46e5` → `243 75% 59%`: the HSL triplet genesis-ui's variables hold. */
|
|
74
|
+
declare function hexToHslTriplet(hex: string): string;
|
|
75
|
+
declare function isSiteBrand(brand: BrandInput): brand is SiteBrand;
|
|
76
|
+
/** The minimal brand as a design-system brand (Genesis's neutrals and status colours, the brand's primary and fonts). */
|
|
77
|
+
declare function toCanonicalBrand(brand: BrandInput): CanonicalBrand;
|
|
78
|
+
/** The brand's display name: the minimal brand's `name`, a canonical brand's `displayName` or `name`. */
|
|
79
|
+
declare function brandDisplayName(brand: BrandInput): string;
|
|
80
|
+
interface ThemePair {
|
|
81
|
+
/** What the pair is, for an accessibility issue: "body text on the page". */
|
|
82
|
+
name: string;
|
|
83
|
+
mode: "light" | "dark";
|
|
84
|
+
foreground: string;
|
|
85
|
+
background: string;
|
|
86
|
+
/** The ratio it must reach: 4.5 for text, 3 for the focus ring and other non-text marks. */
|
|
87
|
+
minimum: number;
|
|
88
|
+
/**
|
|
89
|
+
* `error` (default) blocks the publish; `warning` is shown but doesn't. A
|
|
90
|
+
* mark that never carries meaning alone (each one also has text or a shape)
|
|
91
|
+
* warns, so a light brand colour can still publish.
|
|
92
|
+
*/
|
|
93
|
+
severity?: "error" | "warning";
|
|
94
|
+
}
|
|
95
|
+
interface SiteTheme {
|
|
96
|
+
light: Record<string, string>;
|
|
97
|
+
dark: Record<string, string>;
|
|
98
|
+
colorScheme: "light" | "dark" | "auto";
|
|
99
|
+
/** The fonts the site uses, to load. */
|
|
100
|
+
fonts: FontInfo[];
|
|
101
|
+
/** The colour pairs the sections draw, for the contrast check. */
|
|
102
|
+
pairs: ThemePair[];
|
|
103
|
+
/** Hash of the brand and overrides it was made from (recorded on a release). */
|
|
104
|
+
brandHash: string;
|
|
105
|
+
}
|
|
106
|
+
/** FNV-1a, 32 bits twice: a short, stable hash for cache keys and release records (not for security). */
|
|
107
|
+
declare function stableHash(value: unknown): string;
|
|
108
|
+
/**
|
|
109
|
+
* The site's theme: variables for light and dark, the fonts to load, the
|
|
110
|
+
* pairs the contrast check reads, and a hash of what it was made from.
|
|
111
|
+
*
|
|
112
|
+
* @stability Beta
|
|
113
|
+
*/
|
|
114
|
+
declare function siteTheme(brand: BrandInput, theme?: Partial<SiteDoc["theme"]>): SiteTheme;
|
|
115
|
+
/** The site's `<style>` body: the light variables on `:root`, the dark ones by scheme. */
|
|
116
|
+
declare function themeCss(theme: SiteTheme): string;
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* URL and path rules shared by the schemas, the renderer and the database.
|
|
120
|
+
*
|
|
121
|
+
* Every link a site renders comes from stored props, so every link is
|
|
122
|
+
* untrusted. A link is safe when it is a site path (`/about`), a fragment
|
|
123
|
+
* (`#pricing`), or an absolute `https:`, `http:`, `mailto:` or `tel:` URL.
|
|
124
|
+
* Everything else (`javascript:`, `data:`, `vbscript:`, protocol-relative
|
|
125
|
+
* `//host`, a backslash trick, a control character) is refused, both when a
|
|
126
|
+
* document is parsed and again when it is rendered.
|
|
127
|
+
*/
|
|
128
|
+
/** The longest URL a site stores. */
|
|
129
|
+
declare const MAX_URL_LENGTH = 2048;
|
|
130
|
+
/** Is this a link a site may render: a site path, a fragment, or an http(s), mailto or tel URL? */
|
|
131
|
+
declare function isSafeHref(value: unknown): value is string;
|
|
132
|
+
/** Is this a URL an image may load from: a site path or an `https:` URL? */
|
|
133
|
+
declare function isSafeImageSrc(value: unknown): value is string;
|
|
134
|
+
/** Is this an external (absolute) link, opened with `rel="noopener noreferrer"`? */
|
|
135
|
+
declare function isExternalHref(href: string): boolean;
|
|
136
|
+
/**
|
|
137
|
+
* A page's path: `/`, or lowercase segments of letters, digits and hyphens
|
|
138
|
+
* (`/about`, `/classes/strength`). No trailing slash, no dots, at most 8
|
|
139
|
+
* segments and 200 characters. The database checks the same pattern.
|
|
140
|
+
*/
|
|
141
|
+
declare const PAGE_PATH: RegExp;
|
|
142
|
+
declare const MAX_PAGE_PATH_LENGTH = 200;
|
|
143
|
+
/**
|
|
144
|
+
* Paths a site never owns, because it shares its hostname with the host app:
|
|
145
|
+
* the app's API and server functions, its auth callbacks, and this package's
|
|
146
|
+
* own assets. A host passes its own list (or a predicate) to
|
|
147
|
+
* `handleSiteRequest` and `validatePagePath`; the database's default is the
|
|
148
|
+
* same list (`genesis_sites.reserved_paths()`).
|
|
149
|
+
*/
|
|
150
|
+
declare const DEFAULT_RESERVED_PATHS: readonly string[];
|
|
151
|
+
/** A reserved-path rule: a list of path prefixes, or a predicate. */
|
|
152
|
+
type ReservedPaths = readonly string[] | ((path: string) => boolean);
|
|
153
|
+
/** Is `path` reserved: equal to a reserved prefix, or under it (`/api`, `/api/x`, not `/apiary`)? */
|
|
154
|
+
declare function isReservedPath(path: string, reserved?: ReservedPaths): boolean;
|
|
155
|
+
/** Problems with a page path, empty when it is fine. */
|
|
156
|
+
declare function pagePathProblems(path: unknown, reserved?: ReservedPaths): string[];
|
|
157
|
+
/** Throws when the path can't be a page's. */
|
|
158
|
+
declare function validatePagePath(path: string, reserved?: ReservedPaths): string;
|
|
159
|
+
/**
|
|
160
|
+
* The page path a request path asks for: decoded, lowercase, without a
|
|
161
|
+
* trailing slash. Returns null for a path that can't name a page (so the
|
|
162
|
+
* caller falls through to the host app).
|
|
163
|
+
*/
|
|
164
|
+
declare function normalizeRequestPath(pathname: string): string | null;
|
|
165
|
+
|
|
166
|
+
interface SiteIssue {
|
|
167
|
+
severity: "error" | "warning";
|
|
168
|
+
code: string;
|
|
169
|
+
message: string;
|
|
170
|
+
pageId?: string;
|
|
171
|
+
path?: string;
|
|
172
|
+
sectionId?: string;
|
|
173
|
+
}
|
|
174
|
+
declare const isPublishable: (issues: readonly SiteIssue[]) => boolean;
|
|
175
|
+
type Lookup = (type: string) => AnySectionDefinition | undefined;
|
|
176
|
+
/** Issues on one page: its sections, limits and the h1. */
|
|
177
|
+
declare function checkPage(page: ReleasePage, lookup?: Lookup, reserved?: ReservedPaths): SiteIssue[];
|
|
178
|
+
/** Issues with the site's own parts: header, footer, redirects. */
|
|
179
|
+
declare function checkSiteDoc(site: SiteDoc, lookup?: Lookup): SiteIssue[];
|
|
180
|
+
/** Colour pairs below their minimum (1.4.3 text, 1.4.11 focus ring and primary borders and markers). */
|
|
181
|
+
declare function checkContrast(theme: SiteTheme): SiteIssue[];
|
|
182
|
+
interface CheckReleaseOptions {
|
|
183
|
+
/** The brand's theme: adds the contrast check. */
|
|
184
|
+
theme?: SiteTheme;
|
|
185
|
+
lookup?: Lookup;
|
|
186
|
+
reserved?: ReservedPaths;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Everything a publish checks, for a whole release.
|
|
190
|
+
*
|
|
191
|
+
* @stability Beta
|
|
192
|
+
*/
|
|
193
|
+
declare function checkRelease(snapshot: ReleaseSnapshot, options?: CheckReleaseOptions): SiteIssue[];
|
|
194
|
+
/** The blog's rules: one post page, not the home page, with a post-body section; a post list needs a post page. */
|
|
195
|
+
declare function checkBlog(snapshot: ReleaseSnapshot, lookup?: Lookup): SiteIssue[];
|
|
196
|
+
/**
|
|
197
|
+
* The checks a post passes before it is published: a slug, a title, alt
|
|
198
|
+
* text on its images, and a heading outline under the post's own title
|
|
199
|
+
* (the page's h1): no level 1 heading, none skipped, starting at 2.
|
|
200
|
+
*
|
|
201
|
+
* @stability Beta
|
|
202
|
+
*/
|
|
203
|
+
declare function checkPost(slug: string, doc: unknown): SiteIssue[];
|
|
204
|
+
/**
|
|
205
|
+
* A rendered page's outline, from its HTML: one `h1`, no skipped heading
|
|
206
|
+
* level, every `<img>` with an `alt` attribute. For tests and for hosts that
|
|
207
|
+
* want to audit what was actually served.
|
|
208
|
+
*/
|
|
209
|
+
declare function checkHtmlOutline(html: string): SiteIssue[];
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Where sites live: the `SiteStore` contract, the publish pipeline every
|
|
213
|
+
* store shares, cache invalidation, and an in-memory store for tests, demos
|
|
214
|
+
* and previews. `@marktiderman/genesis-sites/supabase` has the Postgres one.
|
|
215
|
+
*/
|
|
216
|
+
|
|
217
|
+
type SaveResult<T> = {
|
|
218
|
+
rev: number;
|
|
219
|
+
} | {
|
|
220
|
+
conflict: T;
|
|
221
|
+
rev: number;
|
|
222
|
+
};
|
|
223
|
+
declare const isConflict: <T>(result: SaveResult<T>) => result is {
|
|
224
|
+
conflict: T;
|
|
225
|
+
rev: number;
|
|
226
|
+
};
|
|
227
|
+
/**
|
|
228
|
+
* The file types a site asset may be, as the database checks them: images
|
|
229
|
+
* and PDF. Never SVG or HTML, which a browser runs as a page.
|
|
230
|
+
*/
|
|
231
|
+
declare const ASSET_TYPES: readonly string[];
|
|
232
|
+
/**
|
|
233
|
+
* The storage key for an upload: `sites/<siteId>/<id>-<name>` for an image,
|
|
234
|
+
* `sites/<siteId>/files/<id>-<name>` for a file to download (a PDF), so
|
|
235
|
+
* storage policies scope by site with `can_edit`.
|
|
236
|
+
*/
|
|
237
|
+
declare function assetPath(siteId: string, id: string, name: string | undefined, mime: string): string;
|
|
238
|
+
interface AssetMeta {
|
|
239
|
+
alt?: string;
|
|
240
|
+
width?: number;
|
|
241
|
+
height?: number;
|
|
242
|
+
/** The file's name, for the storage key. */
|
|
243
|
+
name?: string;
|
|
244
|
+
}
|
|
245
|
+
interface Asset {
|
|
246
|
+
id: string;
|
|
247
|
+
siteId: string;
|
|
248
|
+
/** The storage key: always `sites/<siteId>/...`, so storage policies can scope by site. */
|
|
249
|
+
path: string;
|
|
250
|
+
/** Where the browser loads it. */
|
|
251
|
+
url: string;
|
|
252
|
+
mime: string;
|
|
253
|
+
alt: string | null;
|
|
254
|
+
width: number | null;
|
|
255
|
+
height: number | null;
|
|
256
|
+
}
|
|
257
|
+
interface PublishOptions {
|
|
258
|
+
note?: string;
|
|
259
|
+
/** The brand to check contrast against (and record the hash of). Required: a publish without it is blocked. */
|
|
260
|
+
brand?: BrandInput | null;
|
|
261
|
+
reserved?: ReservedPaths;
|
|
262
|
+
}
|
|
263
|
+
interface NewPage {
|
|
264
|
+
path: string;
|
|
265
|
+
kind?: PageKind;
|
|
266
|
+
doc: PageDoc;
|
|
267
|
+
}
|
|
268
|
+
interface SiteDraft {
|
|
269
|
+
site: SiteDoc;
|
|
270
|
+
rev: number;
|
|
271
|
+
}
|
|
272
|
+
interface PageDraft {
|
|
273
|
+
doc: PageDoc;
|
|
274
|
+
rev: number;
|
|
275
|
+
siteId: string;
|
|
276
|
+
path: string;
|
|
277
|
+
kind: PageKind;
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* A site's storage.
|
|
281
|
+
*
|
|
282
|
+
* Drafts change by compare-and-swap on a revision: a save names the revision
|
|
283
|
+
* it started from and gets the new one, or the current document when someone
|
|
284
|
+
* saved first. A publish checks the drafts (`checkRelease`) and freezes them
|
|
285
|
+
* into an immutable release that the live site points at; a rollback moves
|
|
286
|
+
* the pointer back. `getLive` is the public read and needs no session.
|
|
287
|
+
*
|
|
288
|
+
* @stability Beta
|
|
289
|
+
*/
|
|
290
|
+
interface SiteStore {
|
|
291
|
+
/** The release the live site serves, or null (not published, archived, or unknown). */
|
|
292
|
+
getLive(siteId: string): Promise<Release | null>;
|
|
293
|
+
getDraft(siteId: string): Promise<SiteDraft | null>;
|
|
294
|
+
saveSiteDraft(siteId: string, rev: number, site: SiteDoc): Promise<SaveResult<SiteDoc>>;
|
|
295
|
+
listPages(siteId: string): Promise<PageMeta[]>;
|
|
296
|
+
getPageDraft(pageId: string): Promise<PageDraft | null>;
|
|
297
|
+
savePageDraft(pageId: string, rev: number, doc: PageDoc): Promise<SaveResult<PageDoc>>;
|
|
298
|
+
createPage(siteId: string, page: NewPage): Promise<PageMeta>;
|
|
299
|
+
/** Move a page to a new path; the old path redirects to it (301). */
|
|
300
|
+
movePage(pageId: string, path: string): Promise<PageMeta>;
|
|
301
|
+
deletePage(pageId: string): Promise<void>;
|
|
302
|
+
publish(siteId: string, options?: PublishOptions): Promise<Release>;
|
|
303
|
+
rollback(siteId: string, releaseId: string): Promise<Release>;
|
|
304
|
+
listReleases(siteId: string): Promise<ReleaseMeta[]>;
|
|
305
|
+
/** Store an upload under `sites/<siteId>/...` (a PDF under `files/`) and record it. Images and PDF only. */
|
|
306
|
+
putAsset?(siteId: string, file: Blob, meta?: AssetMeta): Promise<Asset>;
|
|
307
|
+
/** The published posts, newest first, without bodies (at most `MAX_LIVE_POSTS`). Public, like `getLive`. */
|
|
308
|
+
listLivePosts?(siteId: string): Promise<PostSummary[]>;
|
|
309
|
+
/** One published post. Public, like `getLive`. Null for a draft, an unknown slug, or a site that isn't live. */
|
|
310
|
+
getLivePost?(siteId: string, slug: string): Promise<LivePost | null>;
|
|
311
|
+
/** Every post's meta for the editor (drafts too), newest first. */
|
|
312
|
+
listPosts?(siteId: string): Promise<PostMeta[]>;
|
|
313
|
+
getPost?(postId: string): Promise<PostDraft | null>;
|
|
314
|
+
/** Create a post (no `id`) or save its draft by compare-and-swap. A post keeps its slug once published. */
|
|
315
|
+
savePost?(siteId: string, input: SavePostInput): Promise<SavePostResult>;
|
|
316
|
+
/** Check the draft (`checkPost`) and make it the published copy. Throws `PublishBlockedError`. */
|
|
317
|
+
publishPost?(postId: string): Promise<PostMeta>;
|
|
318
|
+
/** Take the post down; its draft stays. */
|
|
319
|
+
unpublishPost?(postId: string): Promise<PostMeta>;
|
|
320
|
+
/** Delete a post (its row stays, marked deleted). Returns its site, for the cache purge; null when there was no such post. */
|
|
321
|
+
deletePost?(postId: string): Promise<{
|
|
322
|
+
siteId: string;
|
|
323
|
+
} | null>;
|
|
324
|
+
/**
|
|
325
|
+
* A one-time import from another site: each post keeps its slug and its
|
|
326
|
+
* date, and is published unless asked otherwise or a check fails (then it
|
|
327
|
+
* is a draft, with its issues). A slug the site already has is skipped.
|
|
328
|
+
*/
|
|
329
|
+
importPosts?(siteId: string, posts: readonly ImportPost[]): Promise<ImportPostsResult>;
|
|
330
|
+
}
|
|
331
|
+
/** A publish the checks refused: `issues` lists why. */
|
|
332
|
+
declare class PublishBlockedError extends Error {
|
|
333
|
+
readonly issues: SiteIssue[];
|
|
334
|
+
constructor(issues: SiteIssue[]);
|
|
335
|
+
}
|
|
336
|
+
/** Someone saved first; the store's current document is in the result of the save that raised this. */
|
|
337
|
+
declare class SiteConflictError extends Error {
|
|
338
|
+
constructor(message?: string);
|
|
339
|
+
}
|
|
340
|
+
interface PreparedRelease {
|
|
341
|
+
snapshot: ReleaseSnapshot;
|
|
342
|
+
issues: SiteIssue[];
|
|
343
|
+
/** Drafts whose props a migration changed: a store saves these before it publishes. */
|
|
344
|
+
migratedPages: {
|
|
345
|
+
id: string;
|
|
346
|
+
doc: PageDoc;
|
|
347
|
+
}[];
|
|
348
|
+
migratedSite: SiteDoc | null;
|
|
349
|
+
brandHash: string | null;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* The publish pipeline every store shares: migrate every node, freeze the
|
|
353
|
+
* drafts into a snapshot, and check it. Throws `PublishBlockedError` when a
|
|
354
|
+
* check fails. Pages come in the order they are listed.
|
|
355
|
+
*
|
|
356
|
+
* @stability Beta
|
|
357
|
+
*/
|
|
358
|
+
declare function prepareRelease(site: SiteDoc, pages: readonly {
|
|
359
|
+
id: string;
|
|
360
|
+
path: string;
|
|
361
|
+
kind: PageKind;
|
|
362
|
+
doc: PageDoc;
|
|
363
|
+
}[], options?: PublishOptions): PreparedRelease;
|
|
364
|
+
/** The cache tag every response of a site carries (`Vercel-Cache-Tag`). */
|
|
365
|
+
declare function siteCacheTag(siteId: string): string;
|
|
366
|
+
/** The cache tag of every page that embeds a form: purge it when the host changes that form. */
|
|
367
|
+
declare function formCacheTag(slug: string): string;
|
|
368
|
+
/** Why a site's cache is being purged. */
|
|
369
|
+
interface InvalidationInfo {
|
|
370
|
+
siteId: string;
|
|
371
|
+
/** `post`: a post was published, taken down, deleted or imported. */
|
|
372
|
+
action: "publish" | "rollback" | "post";
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* A store that purges the site's cached pages after every publish and
|
|
376
|
+
* rollback: `invalidate` gets `["site:<id>"]` and what happened.
|
|
377
|
+
*
|
|
378
|
+
* Mind what your CDN's purge does. On Vercel, `invalidateByTag` marks pages
|
|
379
|
+
* stale, so each cached URL serves the old release once more while it
|
|
380
|
+
* regenerates (no stampede); `dangerouslyDeleteByTag` drops them, so the
|
|
381
|
+
* next request waits for a render. Use the delete for a rollback that takes
|
|
382
|
+
* something down:
|
|
383
|
+
*
|
|
384
|
+
* withCacheInvalidation(store, (tags, { action }) =>
|
|
385
|
+
* action === "rollback" ? dangerouslyDeleteByTag(tags) : invalidateByTag(tags));
|
|
386
|
+
*
|
|
387
|
+
* Archiving a site goes through your own server or the database (the
|
|
388
|
+
* `genesis_sites` notification), so purge there too. A failed purge is
|
|
389
|
+
* logged and rethrown, so the editor shows that the live site may be stale.
|
|
390
|
+
*
|
|
391
|
+
* @stability Beta
|
|
392
|
+
*/
|
|
393
|
+
declare function withCacheInvalidation(store: SiteStore, invalidate: (tags: string[], info: InvalidationInfo) => Promise<void> | void): SiteStore;
|
|
394
|
+
/**
|
|
395
|
+
* One post of an import, checked: its document parsed, its status (a draft
|
|
396
|
+
* when asked for, or when a check fails, with the reasons) and its date.
|
|
397
|
+
* Every store's `importPosts` runs it.
|
|
398
|
+
*/
|
|
399
|
+
declare function prepareImportPost(item: ImportPost): {
|
|
400
|
+
doc: PostDoc;
|
|
401
|
+
status: PostStatus;
|
|
402
|
+
publishedAt: string;
|
|
403
|
+
issues: string[];
|
|
404
|
+
} | {
|
|
405
|
+
slug: string;
|
|
406
|
+
reason: string;
|
|
407
|
+
};
|
|
408
|
+
interface NewSite {
|
|
409
|
+
id?: string;
|
|
410
|
+
/** The site document (defaults applied). */
|
|
411
|
+
site: SiteDocInput;
|
|
412
|
+
pages: {
|
|
413
|
+
id?: string;
|
|
414
|
+
path: string;
|
|
415
|
+
kind?: PageKind;
|
|
416
|
+
doc: PageDoc;
|
|
417
|
+
}[];
|
|
418
|
+
}
|
|
419
|
+
interface MemorySiteStore extends SiteStore {
|
|
420
|
+
createSite(input: NewSite): Promise<{
|
|
421
|
+
siteId: string;
|
|
422
|
+
pageIds: string[];
|
|
423
|
+
}>;
|
|
424
|
+
/** Archive (or bring back) a site: an archived site serves nothing. */
|
|
425
|
+
setStatus(siteId: string, status: "draft" | "live" | "archived"): Promise<void>;
|
|
426
|
+
}
|
|
427
|
+
interface MemoryStoreOptions {
|
|
428
|
+
reserved?: ReservedPaths;
|
|
429
|
+
/** Who may edit (default: everyone). Mirrors the database's `can_edit` hook, for tests. */
|
|
430
|
+
canEdit?: (siteId: string) => boolean;
|
|
431
|
+
now?: () => Date;
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* An in-memory `SiteStore`: the same rules as the database (compare-and-swap
|
|
435
|
+
* revisions, path checks, redirects on move, immutable releases), for tests,
|
|
436
|
+
* demos and local previews. Documents are copied in and out, so callers
|
|
437
|
+
* can't change stored state by mutating what they hold.
|
|
438
|
+
*
|
|
439
|
+
* @stability Beta
|
|
440
|
+
*/
|
|
441
|
+
declare function createMemorySiteStore(options?: MemoryStoreOptions): MemorySiteStore;
|
|
442
|
+
|
|
443
|
+
export { pagePathProblems as $, ASSET_TYPES as A, type BrandInput as B, type CheckReleaseOptions as C, DEFAULT_RESERVED_PATHS as D, checkSiteDoc as E, FONT_CHOICES as F, contrastRatio as G, createMemorySiteStore as H, type InvalidationInfo as I, formCacheTag as J, hexToHslTriplet as K, isConflict as L, MAX_PAGE_PATH_LENGTH as M, type NewPage as N, isExternalHref as O, PAGE_PATH as P, isPublishable as Q, type ReservedPaths as R, type SiteStore as S, type ThemePair as T, isReservedPath as U, isSafeHref as V, isSafeImageSrc as W, isSiteBrand as X, luminance as Y, mix as Z, normalizeRequestPath as _, type SiteBrand as a, parseHex as a0, prepareImportPost as a1, prepareRelease as a2, siteCacheTag as a3, siteTheme as a4, stableHash as a5, themeCss as a6, toCanonicalBrand as a7, validatePagePath as a8, withCacheInvalidation as a9, type Asset as b, type AssetMeta as c, type BrandSource as d, type FontInfo as e, MAX_URL_LENGTH as f, type MemorySiteStore as g, type MemoryStoreOptions as h, type NewSite as i, type PageDraft as j, type PreparedRelease as k, PublishBlockedError as l, type PublishOptions as m, type SaveResult as n, SiteConflictError as o, type SiteDraft as p, type SiteIssue as q, type SiteTheme as r, assetPath as s, brandDisplayName as t, checkBlog as u, checkContrast as v, checkHtmlOutline as w, checkPage as x, checkPost as y, checkRelease as z };
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
+
import { S as SiteStore } from './store-DkSycDhq.js';
|
|
3
|
+
export { A as ASSET_TYPES } from './store-DkSycDhq.js';
|
|
4
|
+
import './registry-D_DCWRTc.js';
|
|
5
|
+
import 'react';
|
|
6
|
+
import 'zod';
|
|
7
|
+
import '@marktiderman/genesis-design-system';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @marktiderman/genesis-sites/supabase: `createSupabaseSiteStore`, the
|
|
11
|
+
* `SiteStore` over the `genesis_sites` schema (migration
|
|
12
|
+
* `migrations/0001_genesis_sites.sql`).
|
|
13
|
+
*
|
|
14
|
+
* Pass a client signed in as the editor for drafts and publishing (row level
|
|
15
|
+
* security and `can_edit` / `can_view` decide), or an anonymous one for the
|
|
16
|
+
* public read (`getLive`, through `genesis_sites.live_release`). The schema
|
|
17
|
+
* must be exposed to the API.
|
|
18
|
+
*
|
|
19
|
+
* @packageDocumentation
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
declare const SITES_SCHEMA = "genesis_sites";
|
|
23
|
+
|
|
24
|
+
interface SupabaseSiteStoreOptions {
|
|
25
|
+
/** The storage bucket for `putAsset`. Default `site-assets`. */
|
|
26
|
+
bucket?: string;
|
|
27
|
+
}
|
|
28
|
+
/** A database error with its SQLSTATE (`GS409` a conflict, `42501` not allowed). */
|
|
29
|
+
declare class SiteStoreError extends Error {
|
|
30
|
+
readonly code: string | undefined;
|
|
31
|
+
readonly detail?: string | undefined;
|
|
32
|
+
constructor(message: string, code: string | undefined, detail?: string | undefined);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A `SiteStore` on Supabase.
|
|
36
|
+
*
|
|
37
|
+
* @stability Beta
|
|
38
|
+
*/
|
|
39
|
+
declare function createSupabaseSiteStore(client: SupabaseClient, options?: SupabaseSiteStoreOptions): SiteStore;
|
|
40
|
+
|
|
41
|
+
export { SITES_SCHEMA, SiteStoreError, type SupabaseSiteStoreOptions, createSupabaseSiteStore };
|