@marktiderman/genesis-sites 0.2.0 → 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.
- package/README.md +89 -5
- package/dist/assets/sites-forms.732cc7e0c1.css +10 -0
- package/dist/assets/sites-forms.css +1 -1
- package/dist/assets/sites.716b4ac1a5.css +2 -0
- package/dist/assets/sites.css +1 -1
- package/dist/assets.generated-QSB7EJWX.js +3 -0
- package/dist/assets.generated-QSB7EJWX.js.map +1 -0
- package/dist/chunk-5IXDFFEG.js +465 -0
- package/dist/chunk-5IXDFFEG.js.map +1 -0
- package/dist/{chunk-IXMY4B56.js → chunk-FBQF2L7O.js} +90 -89
- package/dist/chunk-FBQF2L7O.js.map +1 -0
- package/dist/chunk-H6ED3K4R.js +18 -0
- package/dist/chunk-H6ED3K4R.js.map +1 -0
- package/dist/chunk-KPOX7D4S.js +250 -0
- package/dist/chunk-KPOX7D4S.js.map +1 -0
- package/dist/editor.d.ts +526 -0
- package/dist/editor.js +2480 -0
- package/dist/editor.js.map +1 -0
- package/dist/handler-QHH453YG.js +9 -0
- package/dist/handler-QHH453YG.js.map +1 -0
- package/dist/import.d.ts +2 -2
- package/dist/index.d.ts +11 -5
- package/dist/index.js +2 -249
- package/dist/index.js.map +1 -1
- package/dist/{registry-D_DCWRTc.d.ts → registry-HBn1X60x.d.ts} +1 -1
- package/dist/sections.d.ts +1 -1
- package/dist/{store-DkSycDhq.d.ts → store-BXiZeZzH.d.ts} +2 -2
- package/dist/supabase.d.ts +3 -3
- package/dist/supabase.js +2 -2
- package/dist/web.d.ts +4 -4
- package/dist/web.js +6 -476
- package/dist/web.js.map +1 -1
- package/package.json +13 -3
- package/dist/assets/sites-forms.4e6aa42497.css +0 -10
- package/dist/assets/sites.e1ffd2708b.css +0 -2
- package/dist/chunk-IXMY4B56.js.map +0 -1
package/dist/editor.d.ts
ADDED
|
@@ -0,0 +1,526 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { P as PageKind, a as PageDoc, b as PageMeta, R as Release, F as FormsResolver, c as RenderContext, d as ResolvedForm, S as SectionCategory, A as AnySectionDefinition, e as Seo, f as SectionNode, g as SiteDoc } from './registry-HBn1X60x.js';
|
|
3
|
+
import { A as Asset, a as AssetMeta, S as SiteStore, B as BrandInput, b as SiteIssue, P as PublishOptions, R as ReservedPaths, c as SaveResult } from './store-BXiZeZzH.js';
|
|
4
|
+
import { Config, Fields, Overrides } from '@puckeditor/core';
|
|
5
|
+
export { Config as PuckConfig, Data as PuckData, Overrides as PuckOverrides, createUsePuck, usePuck } from '@puckeditor/core';
|
|
6
|
+
import 'zod';
|
|
7
|
+
import '@marktiderman/genesis-design-system';
|
|
8
|
+
|
|
9
|
+
/** The host's asset library, beside the store's `putAsset` uploads. */
|
|
10
|
+
interface EditorAssets {
|
|
11
|
+
/** The images and files a site already has (shown in the picker's Library). */
|
|
12
|
+
list?: (siteId: string) => Promise<Asset[]>;
|
|
13
|
+
/** Upload, instead of the store's `putAsset` (for a host that stores files elsewhere). */
|
|
14
|
+
upload?: (siteId: string, file: File, meta: AssetMeta) => Promise<Asset>;
|
|
15
|
+
/** The largest upload, in bytes. Default 10 MB. */
|
|
16
|
+
maxBytes?: number;
|
|
17
|
+
}
|
|
18
|
+
interface PickedImage {
|
|
19
|
+
src: string;
|
|
20
|
+
alt: string;
|
|
21
|
+
decorative?: boolean;
|
|
22
|
+
width?: number;
|
|
23
|
+
height?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* What the editor's panels do to a site, as plain functions over a
|
|
28
|
+
* `SiteStore`: check and publish, add, move, delete and reorder pages, and
|
|
29
|
+
* the page templates. No React, so each is tested against the memory store.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/** A store the editor can drive. `reorderPages` is optional: without it, page order is the store's. */
|
|
33
|
+
type EditorSiteStore = SiteStore & {
|
|
34
|
+
reorderPages?(siteId: string, pageIds: string[]): Promise<void>;
|
|
35
|
+
};
|
|
36
|
+
interface SiteCheck {
|
|
37
|
+
issues: SiteIssue[];
|
|
38
|
+
/** No errors: the site can be published. */
|
|
39
|
+
publishable: boolean;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Run the publish checks on the drafts as they are now (accessibility,
|
|
43
|
+
* contrast, placeholders, paths), without publishing: what the Publish
|
|
44
|
+
* panel lists, errors first.
|
|
45
|
+
*
|
|
46
|
+
* @stability Beta
|
|
47
|
+
*/
|
|
48
|
+
declare function checkSite(store: SiteStore, siteId: string, options?: Pick<PublishOptions, "brand" | "reserved">): Promise<SiteCheck>;
|
|
49
|
+
/** Publish after checking: a site with errors is refused before the store is asked. */
|
|
50
|
+
declare function publishSite(store: SiteStore, siteId: string, options: {
|
|
51
|
+
brand: BrandInput;
|
|
52
|
+
note?: string;
|
|
53
|
+
reserved?: ReservedPaths;
|
|
54
|
+
}): Promise<{
|
|
55
|
+
release: Release;
|
|
56
|
+
} | {
|
|
57
|
+
blocked: SiteIssue[];
|
|
58
|
+
}>;
|
|
59
|
+
/** Why a path can't be a page's here (empty when it can): the pattern, reserved paths, and pages already there. */
|
|
60
|
+
declare function pathProblems(path: string, pages: readonly Pick<PageMeta, "id" | "path">[], options?: {
|
|
61
|
+
reserved?: ReservedPaths;
|
|
62
|
+
except?: string;
|
|
63
|
+
}): string[];
|
|
64
|
+
/** A page's title as a path segment: "Our classes" → "/our-classes". */
|
|
65
|
+
declare function slugify(title: string): string;
|
|
66
|
+
/**
|
|
67
|
+
* Move a page to a new address. The store writes a 301 from the old path
|
|
68
|
+
* (and rewrites older redirects, so there are no chains). Refuses the home
|
|
69
|
+
* page, reserved paths and a path another page has, before asking the store.
|
|
70
|
+
*/
|
|
71
|
+
declare function renamePage(store: SiteStore, pageId: string, path: string, options?: {
|
|
72
|
+
reserved?: ReservedPaths;
|
|
73
|
+
}): Promise<PageMeta>;
|
|
74
|
+
/** A page a person can start from. */
|
|
75
|
+
interface PageTemplate {
|
|
76
|
+
id: string;
|
|
77
|
+
label: string;
|
|
78
|
+
description: string;
|
|
79
|
+
kind: PageKind;
|
|
80
|
+
doc(input: {
|
|
81
|
+
title: string;
|
|
82
|
+
brand: BrandInput;
|
|
83
|
+
}): PageDoc;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* The templates the Add page dialog offers: a blank page, the one-click
|
|
87
|
+
* landing page from the brand, and each page of the starter site (about,
|
|
88
|
+
* contact) after its home page.
|
|
89
|
+
*
|
|
90
|
+
* @stability Beta
|
|
91
|
+
*/
|
|
92
|
+
declare function pageTemplates(): PageTemplate[];
|
|
93
|
+
/** Move one page up or down the list (through `reorderPages`, when the store has it). */
|
|
94
|
+
declare function movePageInList(store: EditorSiteStore, siteId: string, pageId: string, by: -1 | 1): Promise<PageMeta[]>;
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* `<SiteEditor>`: the visual editor for a genesis-sites site.
|
|
98
|
+
*
|
|
99
|
+
* <SiteEditor siteId="riverside" store={store} brand={brand} />
|
|
100
|
+
*
|
|
101
|
+
* Puck runs the canvas, drag and drop, the fields and the history; every
|
|
102
|
+
* panel around it is Genesis UI. The canvas is an iframe drawn by each
|
|
103
|
+
* section's own renderer with the site's stylesheet and the brand's theme;
|
|
104
|
+
* "Preview" runs the real `/web` request handler. Drafts autosave by
|
|
105
|
+
* compare-and-swap; publishing runs the checks first, and holds the canvas
|
|
106
|
+
* and the autosaves while it runs. `readOnly` opens a site to look at only.
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
interface SiteEditorProps {
|
|
110
|
+
siteId: string;
|
|
111
|
+
/** Any `SiteStore`: the Supabase one signed in as the editor, or the memory store. */
|
|
112
|
+
store: EditorSiteStore;
|
|
113
|
+
/** The site's brand: the canvas theme, and what a publish checks contrast against. */
|
|
114
|
+
brand: BrandInput;
|
|
115
|
+
/** Resolves form embeds' slugs, for the canvas and preview. */
|
|
116
|
+
forms?: FormsResolver;
|
|
117
|
+
/** The asset library and upload, beside the store's `putAsset`. */
|
|
118
|
+
assets?: EditorAssets;
|
|
119
|
+
/** After each publish. */
|
|
120
|
+
onPublish?: (release: Release) => void;
|
|
121
|
+
/**
|
|
122
|
+
* Whether anything isn't saved yet (waiting, saving, a conflict, a failed save, or a change that
|
|
123
|
+
* can't be saved until it's fixed). Block your app's own navigation while it's true: closing the
|
|
124
|
+
* tab is already guarded, and unmounting the editor saves what it can but can't ask.
|
|
125
|
+
*/
|
|
126
|
+
onDirtyChange?: (dirty: boolean) => void;
|
|
127
|
+
/** Paths the site can't use (default: the store's own rule). */
|
|
128
|
+
reserved?: ReservedPaths;
|
|
129
|
+
/** A link to the host's preview route for a page path (opens in a new tab). */
|
|
130
|
+
previewUrl?: (path: string) => string;
|
|
131
|
+
/** Milliseconds after the last change before a save (default 800). */
|
|
132
|
+
autosaveDelay?: number;
|
|
133
|
+
/** The page to open first (default: the home page). */
|
|
134
|
+
initialPageId?: string;
|
|
135
|
+
/**
|
|
136
|
+
* The URL of the package's site stylesheet (`@marktiderman/genesis-sites/sites.css`, for example
|
|
137
|
+
* Vite's `?url` import, or `/_gs/sites.<hash>.css` where `handleSiteRequest` is mounted). Without it
|
|
138
|
+
* the canvas loads the stylesheet from the renderer's bundled assets, a larger download.
|
|
139
|
+
*/
|
|
140
|
+
siteStylesheet?: string;
|
|
141
|
+
/**
|
|
142
|
+
* View only: the canvas, fields and SEO can't be changed, there is no Add, no
|
|
143
|
+
* page actions, no Publish and no Roll back, and nothing is ever saved. For
|
|
144
|
+
* someone who may see the drafts but not edit them.
|
|
145
|
+
*/
|
|
146
|
+
readOnly?: boolean;
|
|
147
|
+
className?: string;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* The visual editor.
|
|
151
|
+
*
|
|
152
|
+
* @stability Beta
|
|
153
|
+
*/
|
|
154
|
+
declare function SiteEditor({ siteId, store, brand, forms, assets, onPublish, reserved, previewUrl, autosaveDelay, initialPageId, siteStylesheet, readOnly, onDirtyChange, className }: SiteEditorProps): React.JSX.Element;
|
|
155
|
+
|
|
156
|
+
/** What the Add picker and its limits know about one Puck component. */
|
|
157
|
+
interface ComponentInfo {
|
|
158
|
+
type: string;
|
|
159
|
+
/** The preset's name (the Add picker's label). */
|
|
160
|
+
label: string;
|
|
161
|
+
/** The section's own label. */
|
|
162
|
+
sectionLabel: string;
|
|
163
|
+
category: SectionCategory;
|
|
164
|
+
placement: "header" | "body" | "footer";
|
|
165
|
+
limit?: number;
|
|
166
|
+
}
|
|
167
|
+
/** What every section on the canvas is told about the page (Puck metadata). */
|
|
168
|
+
interface CanvasMetadata {
|
|
169
|
+
site: RenderContext["site"];
|
|
170
|
+
pagePath: string;
|
|
171
|
+
/** Plain objects, not Maps: Puck copies its metadata. */
|
|
172
|
+
forms: Readonly<Record<string, ResolvedForm | null>>;
|
|
173
|
+
/** The section that gets the page's h1. */
|
|
174
|
+
h1Id: string | null;
|
|
175
|
+
/** Template placeholders' original props, by section id. */
|
|
176
|
+
placeholders: Readonly<Record<string, string>>;
|
|
177
|
+
}
|
|
178
|
+
interface EditorConfigOptions {
|
|
179
|
+
/** The page kind: sections not `enabledOn` it are left out of the picker. */
|
|
180
|
+
kind?: PageKind;
|
|
181
|
+
/** Sections to offer (default: every registered section). */
|
|
182
|
+
sections?: readonly AnySectionDefinition[];
|
|
183
|
+
/** How many of each type the page holds now; read when Puck asks whether one more may be added. */
|
|
184
|
+
counts?: () => ReadonlyMap<string, number>;
|
|
185
|
+
/**
|
|
186
|
+
* Whether the page can't be changed now (a view-only editor, or a publish
|
|
187
|
+
* running). Read with `counts`: a component's own `insert` overrides the
|
|
188
|
+
* global permissions Puck is given, so it has to say no itself.
|
|
189
|
+
*/
|
|
190
|
+
readOnly?: () => boolean;
|
|
191
|
+
}
|
|
192
|
+
interface EditorConfig {
|
|
193
|
+
config: Config;
|
|
194
|
+
/** Puck component name → what it is. */
|
|
195
|
+
components: ReadonlyMap<string, ComponentInfo>;
|
|
196
|
+
/** The props of a section type that hold nested sections. */
|
|
197
|
+
slotKeys: (type: string) => readonly string[];
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* The Puck config for one page kind, from the registry.
|
|
201
|
+
*
|
|
202
|
+
* @stability Beta
|
|
203
|
+
*/
|
|
204
|
+
declare function createEditorConfig(options?: EditorConfigOptions): EditorConfig;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Editor fields from a section's JSON Schema (`sectionJsonSchema`, the zod
|
|
208
|
+
* schema's input side), so a new section gets its fields with no editor
|
|
209
|
+
* change:
|
|
210
|
+
*
|
|
211
|
+
* - `x-genesis-kind: "image"` (or any object with `src` and `alt`) → the
|
|
212
|
+
* image picker, which asks for alt text;
|
|
213
|
+
* - `x-genesis-kind: "rich-text"` → the Genesis rich text editor;
|
|
214
|
+
* - `x-genesis-kind: "slot"` → a Puck slot (nested sections);
|
|
215
|
+
* - an enum, or a union of constants → a select;
|
|
216
|
+
* - a boolean → a yes/no radio; a number → a number input;
|
|
217
|
+
* - a string → text, or a textarea when it may run past 160 characters;
|
|
218
|
+
* - an object → its fields (a record keyed by an enum, such as
|
|
219
|
+
* `z.partialRecord(Day, …)`, gets one optional field per key); an array of
|
|
220
|
+
* objects → a list of them; an array of strings → one per line.
|
|
221
|
+
*
|
|
222
|
+
* Anything else gets a field that says it can't be edited here, and a
|
|
223
|
+
* warning in the console, so a gap is visible rather than silent.
|
|
224
|
+
*/
|
|
225
|
+
|
|
226
|
+
/** `primaryCta` → "Primary CTA"; `hideOn` → "Hide on". */
|
|
227
|
+
declare function humanize(key: string): string;
|
|
228
|
+
interface SectionFields {
|
|
229
|
+
fields: Fields;
|
|
230
|
+
/** Props that hold nested sections (Puck slots). */
|
|
231
|
+
slotKeys: string[];
|
|
232
|
+
/** Props no field could be made for (shown as "can't be edited here"). */
|
|
233
|
+
unsupported: string[];
|
|
234
|
+
}
|
|
235
|
+
/**
|
|
236
|
+
* Every field of a section: its layout (variant) first, then its props from
|
|
237
|
+
* the JSON Schema, then where it shows.
|
|
238
|
+
*
|
|
239
|
+
* @stability Beta
|
|
240
|
+
*/
|
|
241
|
+
declare function sectionFields(def: AnySectionDefinition, options?: {
|
|
242
|
+
slotAllow?: string[];
|
|
243
|
+
}): SectionFields;
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Between Genesis documents and Puck's data.
|
|
247
|
+
*
|
|
248
|
+
* A Genesis section node is `{ id, type, v, variant, hidden, hideOn,
|
|
249
|
+
* placeholder, props }`; a Puck item is `{ type, props: { id, ...props } }`.
|
|
250
|
+
* The node's own fields travel in the Puck props under `_` keys (`_variant`,
|
|
251
|
+
* `_hidden`, `_hideOn`, `_v`, `_placeholder`) that no section schema may use,
|
|
252
|
+
* and come back out on save. A page's header and footer (site parts) are
|
|
253
|
+
* slots on Puck's root, so the canvas shows and edits the whole page.
|
|
254
|
+
*
|
|
255
|
+
* The editor's Add picker offers each section's presets: a preset after the
|
|
256
|
+
* first is its own Puck component, `type__n`, and turns back into `type` on
|
|
257
|
+
* save, so stored documents only ever name registered types.
|
|
258
|
+
*/
|
|
259
|
+
|
|
260
|
+
/** Puck's item shape, as far as the editor needs it. */
|
|
261
|
+
interface PuckItem {
|
|
262
|
+
type: string;
|
|
263
|
+
props: Record<string, unknown> & {
|
|
264
|
+
id: string;
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
/** Puck's page data as the editor builds it. */
|
|
268
|
+
interface EditorData {
|
|
269
|
+
root: {
|
|
270
|
+
props: EditorRootProps;
|
|
271
|
+
};
|
|
272
|
+
content: PuckItem[];
|
|
273
|
+
}
|
|
274
|
+
interface EditorRootProps {
|
|
275
|
+
title: string;
|
|
276
|
+
hideSiteChrome?: boolean;
|
|
277
|
+
seo?: Seo;
|
|
278
|
+
/** The site header's sections (slot). */
|
|
279
|
+
header: PuckItem[];
|
|
280
|
+
/** The site footer's sections (slot). */
|
|
281
|
+
footer: PuckItem[];
|
|
282
|
+
[key: string]: unknown;
|
|
283
|
+
}
|
|
284
|
+
/** Keys a section schema may not use: they carry the node's own fields. */
|
|
285
|
+
declare const NODE_KEYS: readonly ["_variant", "_hidden", "_hideOn", "_v", "_placeholder"];
|
|
286
|
+
/** The Puck component name of a section's preset: `hero` for the first, `hero__1` for the second (`_` never appears in a section type, and Puck builds ids from the name, so it must be id-safe). */
|
|
287
|
+
declare const presetComponentName: (type: string, index: number) => string;
|
|
288
|
+
/** The section type a Puck component name stands for. */
|
|
289
|
+
declare const sectionTypeOf: (componentName: string) => string;
|
|
290
|
+
type Lookup = (type: string) => AnySectionDefinition | undefined;
|
|
291
|
+
type SlotKeys = (type: string) => readonly string[];
|
|
292
|
+
/** A node as a Puck item. Unknown types keep their props, so nothing is lost. */
|
|
293
|
+
declare function nodeToItem(node: SectionNode, slotKeys?: SlotKeys): PuckItem;
|
|
294
|
+
/**
|
|
295
|
+
* Empty optional values out: `""`, and objects left with nothing in them
|
|
296
|
+
* (an optional button whose fields were cleared). A required field that
|
|
297
|
+
* empties is then reported by the section's schema, never saved as `""`.
|
|
298
|
+
* Strings inside arrays stay as they are (an empty one is the schema's to
|
|
299
|
+
* report, not ours to drop).
|
|
300
|
+
*/
|
|
301
|
+
declare function pruneEmpty(value: unknown): unknown;
|
|
302
|
+
/**
|
|
303
|
+
* A section id the schema accepts (1–64 letters, digits, `-`, `_`). Puck makes
|
|
304
|
+
* ids for dragged-in and duplicated sections from the component name and a
|
|
305
|
+
* UUID; the same id always maps to the same safe one, so saves stay stable.
|
|
306
|
+
*/
|
|
307
|
+
declare function safeSectionId(id: string): string;
|
|
308
|
+
interface FromItemOptions {
|
|
309
|
+
lookup?: Lookup;
|
|
310
|
+
slotKeys?: SlotKeys;
|
|
311
|
+
/**
|
|
312
|
+
* A hash of the props each template placeholder had when the editor
|
|
313
|
+
* opened it (`placeholderSnapshot`), by section id. A placeholder stays one only while its props are unchanged:
|
|
314
|
+
* editing it clears the flag.
|
|
315
|
+
*/
|
|
316
|
+
placeholders?: ReadonlyMap<string, string>;
|
|
317
|
+
}
|
|
318
|
+
/** A Puck item as a node. */
|
|
319
|
+
declare function itemToNode(item: PuckItem, options?: FromItemOptions): SectionNode;
|
|
320
|
+
/** A hash of every placeholder node's props, to tell later whether it was edited. */
|
|
321
|
+
declare function placeholderSnapshot(nodes: readonly SectionNode[], into?: Map<string, string>): Map<string, string>;
|
|
322
|
+
/** A page (and its site's header and footer) as the editor's Puck data. */
|
|
323
|
+
declare function toEditorData(page: PageDoc, site: Pick<SiteDoc, "header" | "footer">, slotKeys?: SlotKeys): EditorData;
|
|
324
|
+
interface FromEditorResult {
|
|
325
|
+
page: PageDoc;
|
|
326
|
+
header: SectionNode[];
|
|
327
|
+
footer: SectionNode[];
|
|
328
|
+
}
|
|
329
|
+
/** The editor's Puck data back as a page and the site's header and footer. */
|
|
330
|
+
declare function fromEditorData(data: {
|
|
331
|
+
root?: {
|
|
332
|
+
props?: Record<string, unknown>;
|
|
333
|
+
};
|
|
334
|
+
content?: unknown[];
|
|
335
|
+
}, options?: FromItemOptions): FromEditorResult;
|
|
336
|
+
/** The id of the section that gets the page's `h1`: the first visible one that provides it. */
|
|
337
|
+
declare function h1SectionId(content: readonly PuckItem[], lookup?: Lookup): string | null;
|
|
338
|
+
/** How many sections of each type the page holds, in every zone (for the Add picker's limits). */
|
|
339
|
+
declare function countTypes(data: Pick<EditorData, "content"> & {
|
|
340
|
+
root?: {
|
|
341
|
+
props?: Partial<EditorRootProps>;
|
|
342
|
+
};
|
|
343
|
+
}): Map<string, number>;
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Autosave with compare-and-swap: a document is saved a moment after the
|
|
347
|
+
* last change, naming the revision it started from. When someone else saved
|
|
348
|
+
* first the store answers with their document, and the autosaver stops and
|
|
349
|
+
* waits: the person chooses to load theirs (`reload`) or keep their own
|
|
350
|
+
* (`overwrite`). Nothing is dropped on the way:
|
|
351
|
+
*
|
|
352
|
+
* - a change made while a save is in flight is saved right after it, on the
|
|
353
|
+
* new revision;
|
|
354
|
+
* - during a conflict, edits keep collecting and are what `overwrite` saves;
|
|
355
|
+
* - a failed save (offline, a server error) keeps the change and retries
|
|
356
|
+
* with backoff, and on the next change;
|
|
357
|
+
* - `flush()` saves now and resolves once nothing is waiting (page switch,
|
|
358
|
+
* publish), or rejects when it can't;
|
|
359
|
+
* - `pause()` holds every save (while a publish runs) and `resume()` saves
|
|
360
|
+
* what collected meanwhile, after `rebase` has adopted the new revision.
|
|
361
|
+
*
|
|
362
|
+
* Pure TypeScript with injectable timers, so it is tested without a DOM.
|
|
363
|
+
*/
|
|
364
|
+
|
|
365
|
+
type AutosaveStatus = "saved" | "pending" | "saving" | "conflict" | "error";
|
|
366
|
+
interface AutosaveState<T> {
|
|
367
|
+
status: AutosaveStatus;
|
|
368
|
+
/** The revision the next save names. */
|
|
369
|
+
rev: number;
|
|
370
|
+
/** When status is `conflict`: the store's document and revision. */
|
|
371
|
+
conflict: {
|
|
372
|
+
doc: T;
|
|
373
|
+
rev: number;
|
|
374
|
+
} | null;
|
|
375
|
+
error: Error | null;
|
|
376
|
+
/** When the last save landed. */
|
|
377
|
+
savedAt: Date | null;
|
|
378
|
+
}
|
|
379
|
+
interface AutosaverOptions<T> {
|
|
380
|
+
rev: number;
|
|
381
|
+
save: (rev: number, doc: T) => Promise<SaveResult<T>>;
|
|
382
|
+
/** Wait this long after the last change (default 800 ms). */
|
|
383
|
+
delay?: number;
|
|
384
|
+
/** Retry a failed save after this long, doubling up to a minute (default 2 s). */
|
|
385
|
+
retryDelay?: number;
|
|
386
|
+
onState?: (state: AutosaveState<T>) => void;
|
|
387
|
+
/** Two documents are the same save (default: the same content, in any key order). */
|
|
388
|
+
equals?: (a: T, b: T) => boolean;
|
|
389
|
+
setTimer?: (fn: () => void, ms: number) => unknown;
|
|
390
|
+
clearTimer?: (handle: unknown) => void;
|
|
391
|
+
now?: () => Date;
|
|
392
|
+
}
|
|
393
|
+
/** Thrown by `flush` when the document can't be saved yet (a conflict, or a failed save). */
|
|
394
|
+
declare class AutosaveBlockedError extends Error {
|
|
395
|
+
readonly status: AutosaveStatus;
|
|
396
|
+
constructor(status: AutosaveStatus, message: string);
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* One document's autosave.
|
|
400
|
+
*
|
|
401
|
+
* @stability Beta
|
|
402
|
+
*/
|
|
403
|
+
declare class Autosaver<T> {
|
|
404
|
+
private state;
|
|
405
|
+
/** The newest document not yet saved, or null. */
|
|
406
|
+
private pending;
|
|
407
|
+
/** The last document the store accepted (or loaded). */
|
|
408
|
+
private saved;
|
|
409
|
+
private timer;
|
|
410
|
+
private inFlight;
|
|
411
|
+
private retries;
|
|
412
|
+
private waiters;
|
|
413
|
+
private disposed;
|
|
414
|
+
private paused;
|
|
415
|
+
private readonly opts;
|
|
416
|
+
constructor(options: AutosaverOptions<T>, initial?: T);
|
|
417
|
+
get current(): AutosaveState<T>;
|
|
418
|
+
/** Is anything not yet in the store (pending, saving, a conflict or a failed save)? */
|
|
419
|
+
get dirty(): boolean;
|
|
420
|
+
/** The document as the person has it: unsaved changes, or the last save. */
|
|
421
|
+
get latest(): T | null;
|
|
422
|
+
private set;
|
|
423
|
+
/** A change: saved after the delay, unless another change comes first. */
|
|
424
|
+
update(doc: T): void;
|
|
425
|
+
private schedule;
|
|
426
|
+
private run;
|
|
427
|
+
private settle;
|
|
428
|
+
/** Save now; resolves when nothing is waiting, rejects on a conflict or a failed save. */
|
|
429
|
+
flush(): Promise<void>;
|
|
430
|
+
/** Conflict: take the store's document. Returns it; unsaved changes are discarded. */
|
|
431
|
+
reload(): T | null;
|
|
432
|
+
/** Conflict: save this person's document over the store's. */
|
|
433
|
+
overwrite(): Promise<void>;
|
|
434
|
+
/**
|
|
435
|
+
* The store moved on without a conflict (a page move bumped its revision,
|
|
436
|
+
* a publish migrated it): adopt its revision. `doc` replaces the saved
|
|
437
|
+
* copy when given; unsaved changes are kept and saved on the new revision.
|
|
438
|
+
*/
|
|
439
|
+
rebase(rev: number, doc?: T): void;
|
|
440
|
+
/** Is saving held (`pause`)? */
|
|
441
|
+
get isPaused(): boolean;
|
|
442
|
+
/**
|
|
443
|
+
* Hold every save, for as long as the store must not change under the
|
|
444
|
+
* caller (a publish). A save already in flight lands; changes keep
|
|
445
|
+
* collecting and are saved on `resume`.
|
|
446
|
+
*/
|
|
447
|
+
pause(): void;
|
|
448
|
+
/** Save again: what collected while paused is saved now, on the current revision. */
|
|
449
|
+
resume(): void;
|
|
450
|
+
dispose(): void;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* The editor's view of the real output: the `/web` request handler, run in
|
|
455
|
+
* the browser, renders a page of the drafts (`preview`) or of the live
|
|
456
|
+
* release (`live`) exactly as a visitor gets it. Its stylesheet is inlined
|
|
457
|
+
* so the HTML works as an iframe's `srcdoc`, and its script is left out:
|
|
458
|
+
* the frame is sandboxed with no script and no same-origin access, so
|
|
459
|
+
* nothing an editor typed can run with the editor's privileges.
|
|
460
|
+
*
|
|
461
|
+
* The renderer and its assets load on demand (a separate chunk), so opening
|
|
462
|
+
* the editor doesn't download them until a preview or the canvas needs them.
|
|
463
|
+
*/
|
|
464
|
+
|
|
465
|
+
interface PreviewInput {
|
|
466
|
+
store: Pick<SiteStore, "getLive" | "getDraft" | "listPages" | "getPageDraft">;
|
|
467
|
+
siteId: string;
|
|
468
|
+
brand: BrandInput;
|
|
469
|
+
path: string;
|
|
470
|
+
mode: "preview" | "live";
|
|
471
|
+
forms?: FormsResolver;
|
|
472
|
+
}
|
|
473
|
+
interface PreviewResult {
|
|
474
|
+
status: number;
|
|
475
|
+
/** The page as a self-contained document, or null when the address redirects or isn't the site's. */
|
|
476
|
+
html: string | null;
|
|
477
|
+
/** Where a redirect goes. */
|
|
478
|
+
location?: string;
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* One page through `handleSiteRequest`, as an iframe-ready document.
|
|
482
|
+
*
|
|
483
|
+
* @stability Beta
|
|
484
|
+
*/
|
|
485
|
+
declare function renderPreview(input: PreviewInput): Promise<PreviewResult>;
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* The image picker: upload (through the store's `putAsset`, or the host's
|
|
489
|
+
* own upload), choose from the site's library, or link an https image.
|
|
490
|
+
* Every choice needs alt text, or to be marked decorative, before it can be
|
|
491
|
+
* used: the publish check would refuse it anyway, so the picker asks first.
|
|
492
|
+
*/
|
|
493
|
+
|
|
494
|
+
/** Raster images only: the database refuses SVG (it can carry script). */
|
|
495
|
+
declare const IMAGE_TYPES: readonly ["image/png", "image/jpeg", "image/webp", "image/avif", "image/gif"];
|
|
496
|
+
interface AssetPickerProps {
|
|
497
|
+
open: boolean;
|
|
498
|
+
siteId: string;
|
|
499
|
+
store: Pick<SiteStore, "putAsset">;
|
|
500
|
+
assets?: EditorAssets | undefined;
|
|
501
|
+
current?: PickedImage | undefined;
|
|
502
|
+
/** The field takes only the address; its alt text is a field of its own (a gallery photo). */
|
|
503
|
+
altElsewhere?: boolean | undefined;
|
|
504
|
+
onDone: (image: PickedImage | null) => void;
|
|
505
|
+
}
|
|
506
|
+
/** Why an upload can't be used (or null). */
|
|
507
|
+
declare function uploadProblem(file: {
|
|
508
|
+
type: string;
|
|
509
|
+
size: number;
|
|
510
|
+
} | null, maxBytes?: number): string | null;
|
|
511
|
+
declare function AssetPicker({ open, siteId, store, assets, current, altElsewhere, onDone }: AssetPickerProps): React.JSX.Element;
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Puck's chrome, drawn with Genesis UI: every field type, field labels, and
|
|
515
|
+
* the Add picker's items (which also enforce each section's limit). So the
|
|
516
|
+
* editor reads as part of the host app, not as stock Puck, and every
|
|
517
|
+
* control has a real label.
|
|
518
|
+
*/
|
|
519
|
+
|
|
520
|
+
interface GenesisOverrideOptions {
|
|
521
|
+
/** Say what an action did (a section added, moved or removed), for a live region. */
|
|
522
|
+
announce?: (message: string) => void;
|
|
523
|
+
}
|
|
524
|
+
declare function genesisOverrides(info: ReadonlyMap<string, ComponentInfo>, extra?: Partial<Overrides>, options?: GenesisOverrideOptions): Partial<Overrides>;
|
|
525
|
+
|
|
526
|
+
export { AssetPicker, type AssetPickerProps, AutosaveBlockedError, type AutosaveState, type AutosaveStatus, Autosaver, type AutosaverOptions, type CanvasMetadata, type ComponentInfo, type EditorAssets, type EditorConfig, type EditorConfigOptions, type EditorData, type EditorRootProps, type EditorSiteStore, type FromEditorResult, IMAGE_TYPES, NODE_KEYS, type PageTemplate, type PickedImage, type PreviewInput, type PreviewResult, type PuckItem, type SectionFields, type SiteCheck, SiteEditor, type SiteEditorProps, checkSite, countTypes, createEditorConfig, fromEditorData, genesisOverrides, h1SectionId, humanize, itemToNode, movePageInList, nodeToItem, pageTemplates, pathProblems, placeholderSnapshot, presetComponentName, pruneEmpty, publishSite, renamePage, renderPreview, safeSectionId, sectionFields, sectionTypeOf, slugify, toEditorData, uploadProblem };
|