efficient-web-builder 0.0.0 → 0.7.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/CHANGELOG.md +174 -0
- package/LICENSE +10 -0
- package/README.md +230 -2
- package/dist/chunks/EditorPreviewCheckout-UMTU2FL4.js +4 -0
- package/dist/chunks/PreviewFooter-3TQMFRPZ.js +1 -0
- package/dist/chunks/SectionEditorShell-FZIT2REV.js +1 -0
- package/dist/chunks/SiteEditorProvider-FHKVGA4K.js +1 -0
- package/dist/chunks/StorefrontEditorChrome-MYOIHQ5J.js +1 -0
- package/dist/chunks/chunk-4G3T6ZD2.js +1 -0
- package/dist/chunks/chunk-4KLEZTLW.js +11 -0
- package/dist/chunks/chunk-IVOJJ6LW.js +1 -0
- package/dist/chunks/chunk-K3Y6UBLO.js +1 -0
- package/dist/chunks/chunk-KR3FTVDW.js +2 -0
- package/dist/chunks/chunk-N35EG6RF.js +143 -0
- package/dist/chunks/chunk-N35EG6RF.js.LEGAL.txt +4 -0
- package/dist/chunks/chunk-RF5DTRJ5.js +1 -0
- package/dist/chunks/chunk-UBYBQ3KR.js +1 -0
- package/dist/chunks/chunk-UTFM4DGR.js +15 -0
- package/dist/chunks/chunk-UTFM4DGR.js.LEGAL.txt +27 -0
- package/dist/chunks/chunk-WMFQNAWF.js +11 -0
- package/dist/chunks/chunk-X6KEAC34.js +1 -0
- package/dist/chunks/chunk-Y6SLVHK3.js +1 -0
- package/dist/chunks/fitment-data-ZLYB5QPR.js +1 -0
- package/dist/chunks/gallery-builds-data-SWA6XKFF.js +1 -0
- package/dist/chunks/headers-3CAQXQEY.js +1 -0
- package/dist/chunks/render-client-A75K43T5.js +1 -0
- package/dist/contract.d.ts +48 -0
- package/dist/contract.js +1 -0
- package/dist/editor.css +1 -0
- package/dist/index.d.ts +541 -0
- package/dist/index.js +10681 -0
- package/package.json +47 -3
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The public types of `efficient-web-builder` (published as dist/index.d.ts).
|
|
3
|
+
*
|
|
4
|
+
* Written out rather than generated: the entry reaches ~700 shop modules and a
|
|
5
|
+
* declaration bundle of them would publish the shop's internals as API. Kept
|
|
6
|
+
* honest by `types.check.ts` beside it, which the shop's own `tsc` compiles —
|
|
7
|
+
* each type here must be mutually assignable with the one the code defines.
|
|
8
|
+
*/
|
|
9
|
+
import type { ComponentType, ReactElement } from "react";
|
|
10
|
+
// The engine's own block type: a host's blocks are written exactly as the
|
|
11
|
+
// editor's are. `efficient-easel` is a peer of this package.
|
|
12
|
+
import type { ComponentConfig } from "efficient-easel/blocks";
|
|
13
|
+
|
|
14
|
+
/** What the page editor is pointed at: a website page (site + slug) or a page
|
|
15
|
+
* of a website template. */
|
|
16
|
+
export type EditorTarget =
|
|
17
|
+
| { kind: "page"; slug: string; siteId?: string; originSlug?: string }
|
|
18
|
+
| { kind: "template"; id: string; pageSlug?: string };
|
|
19
|
+
|
|
20
|
+
/** The standalone one-section editor's target (a section template). */
|
|
21
|
+
export type SectionEditorTarget = { kind: "section"; id: string; type?: string; name?: string };
|
|
22
|
+
|
|
23
|
+
/** A media item the host's picker returns. */
|
|
24
|
+
export interface HostPickedMedia {
|
|
25
|
+
id: string;
|
|
26
|
+
url: string;
|
|
27
|
+
kind: "image" | "video";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** What a field asked the media panel for. `file` is a file dropped straight on
|
|
31
|
+
* the field: the host uploads it and picks it without waiting for a click. */
|
|
32
|
+
export interface MediaPickRequest {
|
|
33
|
+
requestId: string;
|
|
34
|
+
/** `image/*`, `video/*` or `image/svg+xml` — what the field can hold. */
|
|
35
|
+
accept: string;
|
|
36
|
+
/** The field's label, e.g. "Background image". */
|
|
37
|
+
title: string;
|
|
38
|
+
file?: File | null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The props the host's media panel is drawn with. */
|
|
42
|
+
export interface MediaPanelProps {
|
|
43
|
+
/** The field choosing right now, or null while the merchant is browsing. */
|
|
44
|
+
pick: MediaPickRequest | null;
|
|
45
|
+
/** Answer the field. Ignored while browsing. */
|
|
46
|
+
onPick: (media: HostPickedMedia) => void;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The host's media library, as a component the editor draws in its left
|
|
50
|
+
* panel (the rail's Media view, and every image field's pick mode). */
|
|
51
|
+
export type MediaPanelComponent = ComponentType<MediaPanelProps>;
|
|
52
|
+
|
|
53
|
+
/** Every message the editor sends its host. */
|
|
54
|
+
export type EditorToHostMessage =
|
|
55
|
+
| { type: "storefront-editor:ready" }
|
|
56
|
+
| { type: "storefront-editor:auth-expired" }
|
|
57
|
+
| { type: "storefront-editor:dirty" }
|
|
58
|
+
| { type: "storefront-editor:saved" }
|
|
59
|
+
| { type: "storefront-editor:published" }
|
|
60
|
+
| { type: "storefront-editor:save-failed" }
|
|
61
|
+
| { type: "storefront-editor:navigated"; target: EditorTarget }
|
|
62
|
+
| {
|
|
63
|
+
type: "storefront-editor:open-media-picker";
|
|
64
|
+
requestId: string;
|
|
65
|
+
accept: string;
|
|
66
|
+
title: string;
|
|
67
|
+
file?: File;
|
|
68
|
+
}
|
|
69
|
+
/** `mode: "create"` asks for a new form and carries a `requestId` to answer
|
|
70
|
+
* with `form-saved` / `form-closed`. `mode: "edit"` opens the form builder on
|
|
71
|
+
* `formId` and carries none: nothing waits on it. */
|
|
72
|
+
| { type: "storefront-editor:open-form"; requestId?: string; mode: "create" | "edit"; formId?: string }
|
|
73
|
+
| { type: "storefront-editor:open-category"; categoryId: string; name?: string }
|
|
74
|
+
| { type: "storefront-editor:open-product"; slug: string; name?: string }
|
|
75
|
+
| { type: "storefront-editor:open-post"; slug: string; title?: string }
|
|
76
|
+
| { type: "storefront-editor:open-menus"; menuId?: string | null }
|
|
77
|
+
| { type: "storefront-editor:open-page"; slug: string }
|
|
78
|
+
| { type: "storefront-editor:new-page" }
|
|
79
|
+
| { type: "storefront-editor:save-as-template"; siteId: string; slug: string };
|
|
80
|
+
|
|
81
|
+
/** Every message a host sends the editor. */
|
|
82
|
+
export type HostToEditorMessage =
|
|
83
|
+
| {
|
|
84
|
+
type: "storefront-editor:auth";
|
|
85
|
+
token: string;
|
|
86
|
+
siteId?: string;
|
|
87
|
+
brandId?: string;
|
|
88
|
+
theme?: string;
|
|
89
|
+
can?: { canCreatePlatformTemplate?: boolean };
|
|
90
|
+
}
|
|
91
|
+
| { type: "storefront-editor:navigate"; target: EditorTarget | SectionEditorTarget }
|
|
92
|
+
| { type: "storefront-editor:category-updated"; categoryId?: string }
|
|
93
|
+
| { type: "storefront-editor:menus-updated" }
|
|
94
|
+
| { type: "storefront-editor:form-saved"; requestId?: string; form?: { id: string; slug: string } }
|
|
95
|
+
| { type: "storefront-editor:form-closed"; requestId?: string }
|
|
96
|
+
| { type: "storefront-editor:media-picked"; requestId: string; media: HostPickedMedia }
|
|
97
|
+
| { type: "storefront-editor:media-pick-cancelled"; requestId: string };
|
|
98
|
+
|
|
99
|
+
export type EditorMessageType = EditorToHostMessage["type"];
|
|
100
|
+
type PayloadOf<T extends EditorMessageType> = Omit<Extract<EditorToHostMessage, { type: T }>, "type">;
|
|
101
|
+
|
|
102
|
+
export type SendToHost = <T extends EditorMessageType>(
|
|
103
|
+
type: T,
|
|
104
|
+
...payload: Record<string, never> extends PayloadOf<T> ? [PayloadOf<T>?] : [PayloadOf<T>]
|
|
105
|
+
) => void;
|
|
106
|
+
|
|
107
|
+
export type InboundHostMessage = { type: string } & Record<string, unknown>;
|
|
108
|
+
|
|
109
|
+
/** The editor's address — for an embedder, retargets of its own window. */
|
|
110
|
+
export interface EditorHostAddress {
|
|
111
|
+
/** The query the editor was opened with. An embedder that opens templates to
|
|
112
|
+
* edit their layout answers `"?editTemplate=1"`. */
|
|
113
|
+
search(): string;
|
|
114
|
+
/** Keep the embedder's notion of "where" in step (no load). */
|
|
115
|
+
show(target: EditorTarget | SectionEditorTarget): void;
|
|
116
|
+
/** Leave for another document by LOADING it — re-mount the editor there. */
|
|
117
|
+
load(target: EditorTarget): void;
|
|
118
|
+
/** Reload the editor from scratch — re-mount it on the same target. */
|
|
119
|
+
reload(): void;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface EditorHost {
|
|
123
|
+
readonly connected: boolean;
|
|
124
|
+
readonly ownsDocument: boolean;
|
|
125
|
+
send: SendToHost;
|
|
126
|
+
subscribe(listener: (message: InboundHostMessage) => void): () => void;
|
|
127
|
+
readonly address: EditorHostAddress;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** A host for an editor mounted in the embedder's own page. One per editor. */
|
|
131
|
+
export function createEditorHost(options: {
|
|
132
|
+
onMessage: (message: EditorToHostMessage) => void;
|
|
133
|
+
address: EditorHostAddress;
|
|
134
|
+
}): { host: EditorHost; deliver: (message: HostToEditorMessage) => void };
|
|
135
|
+
|
|
136
|
+
// ── The backend (0.4.0) ──────────────────────────────────────────────────────
|
|
137
|
+
//
|
|
138
|
+
// Everything the editor asks a server for, as one contract an embedder may
|
|
139
|
+
// implement. Leave `backend` out of the editor's props and it is EFFICIENT's
|
|
140
|
+
// (`createEfficientBackend`), authorised by the token the host delivers with
|
|
141
|
+
// `storefront-editor:auth` — exactly as before 0.4.0. Supply one and the editor
|
|
142
|
+
// waits for no token, draws no control the backend has no group for, and makes
|
|
143
|
+
// no request of its own.
|
|
144
|
+
//
|
|
145
|
+
// • `pages`, `site.get` and `media.resolve` are required; every other group
|
|
146
|
+
// and method is optional, and ABSENCE IS THE ANSWER — never implement a
|
|
147
|
+
// method to say "not supported".
|
|
148
|
+
// • A failed call rejects with `WebsiteBackendError`; the editor reads its
|
|
149
|
+
// `kind`, never a status code or a response body.
|
|
150
|
+
// • No credential crosses the interface: how a call is authorised is the
|
|
151
|
+
// backend's own business.
|
|
152
|
+
|
|
153
|
+
/** The page document the editor edits: the engine's `Data` (`content`, `root`,
|
|
154
|
+
* `zones`), stored and returned exactly as it was saved. Opaque to a backend. */
|
|
155
|
+
export type PageLayout = Record<string, unknown>;
|
|
156
|
+
|
|
157
|
+
/** One row of the editor's page list. Rows arrive in display order. */
|
|
158
|
+
export type EditorPageRow = {
|
|
159
|
+
id: string;
|
|
160
|
+
slug: string;
|
|
161
|
+
/** What the list calls the page. */
|
|
162
|
+
label: string;
|
|
163
|
+
/** Header text of the row's group (null / absent = top level). */
|
|
164
|
+
group?: string | null;
|
|
165
|
+
/** Stable key of that group — two groups may share a header text. */
|
|
166
|
+
groupKey?: string;
|
|
167
|
+
/** Not published yet (drives the "draft" tag). False where pages never publish. */
|
|
168
|
+
draft: boolean;
|
|
169
|
+
/** Page kind, where the backend has kinds (`dynamic`, `lightbox`, …). */
|
|
170
|
+
kind?: string | null;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* A page's content, as it is stored and saved.
|
|
175
|
+
*
|
|
176
|
+
* `translations` and `seo` are RESERVED for per-language content: a backend
|
|
177
|
+
* that stores a page as this bundle from the start does not migrate its storage
|
|
178
|
+
* when the multilingual overlay ships. 0.4.0 edits `layout` only — it sends
|
|
179
|
+
* neither of the other two on a save, and a backend treats an absent member as
|
|
180
|
+
* "leave what is stored".
|
|
181
|
+
*/
|
|
182
|
+
export type PageBundle = {
|
|
183
|
+
layout: PageLayout;
|
|
184
|
+
/** Per-language overrides of the layout's text, keyed by language tag. */
|
|
185
|
+
translations?: Record<string, unknown>;
|
|
186
|
+
/** Per-language title and description, keyed by language tag. */
|
|
187
|
+
seo?: Record<string, { title?: string; description?: string }>;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
/** A page as the editor opens it. */
|
|
191
|
+
export type PageDocument = PageBundle & {
|
|
192
|
+
id: string;
|
|
193
|
+
/**
|
|
194
|
+
* The VERSION of the stored document this copy is based on — what the next
|
|
195
|
+
* save says it replaces. Any string the backend can compare for equality; the
|
|
196
|
+
* editor passes it back untouched. `null` means the backend keeps no versions
|
|
197
|
+
* for this document: every save is accepted and nothing is ever refused as
|
|
198
|
+
* `changed-elsewhere`.
|
|
199
|
+
*/
|
|
200
|
+
version: string | null;
|
|
201
|
+
/** Page kind, where the backend has kinds. */
|
|
202
|
+
kind?: string | null;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
/** The website a call is about, when the editor's target names one
|
|
206
|
+
* (`EditorTarget.siteId`). A backend that serves one website ignores it. */
|
|
207
|
+
export type SiteScope = { siteId?: string };
|
|
208
|
+
|
|
209
|
+
/** A page by its slug or its id. */
|
|
210
|
+
export type PageRef = ({ slug: string } | { id: string }) & SiteScope;
|
|
211
|
+
|
|
212
|
+
/** What the page list's inline "Add page" hands the backend. */
|
|
213
|
+
export type NewPageInput = { slug: string; title: string; layout: PageLayout } & SiteScope;
|
|
214
|
+
|
|
215
|
+
/** What a save says it expects, and who is writing. */
|
|
216
|
+
export type SaveExpectation = {
|
|
217
|
+
/** The version being replaced (`PageDocument.version`, then each save's own). */
|
|
218
|
+
version: string | null;
|
|
219
|
+
/** This editor tab — the id its lock claim sends. */
|
|
220
|
+
clientId: string;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
/** A set of pages: a website's, or one template's. */
|
|
224
|
+
export interface WebsitePages {
|
|
225
|
+
/** The rows the page list navigates between. */
|
|
226
|
+
list(scope?: SiteScope): Promise<EditorPageRow[]>;
|
|
227
|
+
/** The page, or null when there is none. */
|
|
228
|
+
load(ref: PageRef): Promise<PageDocument | null>;
|
|
229
|
+
/**
|
|
230
|
+
* Store the page's working copy and answer with the version it now has.
|
|
231
|
+
* Reject `changed-elsewhere` when `expect.version` is no longer the stored
|
|
232
|
+
* one, and `lock-taken` when somebody took the page from `expect.clientId`.
|
|
233
|
+
*/
|
|
234
|
+
save(id: string, doc: PageBundle, expect: SaveExpectation): Promise<{ version: string | null }>;
|
|
235
|
+
/** The stored version alone, without the layout. Lets the editor re-base a
|
|
236
|
+
* save its own other tab refused, and reload after a take-over only when the
|
|
237
|
+
* page really moved. */
|
|
238
|
+
version?(id: string): Promise<string | null>;
|
|
239
|
+
/** Make the stored working copy the public one. Without it the editor's main
|
|
240
|
+
* button reads "Save" and nothing is ever a draft. */
|
|
241
|
+
publish?(id: string, expect?: { version: string }): Promise<{ version: string | null }>;
|
|
242
|
+
/** Add a page from the page list. Without it the editor asks its HOST for a
|
|
243
|
+
* new page (`storefront-editor:new-page`). */
|
|
244
|
+
create?(input: NewPageInput): Promise<EditorPageRow>;
|
|
245
|
+
/** Delete a page from the page list. */
|
|
246
|
+
remove?(id: string): Promise<void>;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** The other operator, as the editor's notices describe them. */
|
|
250
|
+
export type LockHolder = {
|
|
251
|
+
name: string;
|
|
252
|
+
/** Seconds since their last heartbeat, as the SERVER measured it. */
|
|
253
|
+
secondsSinceSeen: number;
|
|
254
|
+
/** Seconds since they TOOK the page, as the SERVER measured it. */
|
|
255
|
+
secondsSinceHeld: number;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/** A refused save: the stored page moved on since this editor loaded it. */
|
|
259
|
+
export type SaveConflict = {
|
|
260
|
+
/** Who saved. Empty when the backend cannot attribute the write. */
|
|
261
|
+
savedBy: string;
|
|
262
|
+
/** True when it was this same person, in another tab. */
|
|
263
|
+
savedByYou: boolean;
|
|
264
|
+
secondsAgo: number;
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
/** The advisory "somebody is editing this page" lock. */
|
|
268
|
+
export interface WebsiteEditLock {
|
|
269
|
+
/**
|
|
270
|
+
* Claim the page, refresh the claim (the heartbeat) or — `takeover` — take it
|
|
271
|
+
* from whoever holds it. Resolves once this tab holds the page; rejects
|
|
272
|
+
* `lock-taken` (with `holder`) while somebody else does.
|
|
273
|
+
*/
|
|
274
|
+
claim(id: string, clientId: string, takeover: boolean): Promise<{ heartbeatSeconds: number }>;
|
|
275
|
+
/** Hand the page back. Best-effort: a lock expires by itself. */
|
|
276
|
+
release(id: string, clientId: string): Promise<void>;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** What the editor reads of the website itself. */
|
|
280
|
+
export interface SiteRecord {
|
|
281
|
+
/** The website's display name. */
|
|
282
|
+
name: string;
|
|
283
|
+
/** Its public slug, where it has one. */
|
|
284
|
+
slug?: string | null;
|
|
285
|
+
/** The site-wide Google Maps browser key the canvas's map blocks draw with. */
|
|
286
|
+
mapsApiKey?: string | null;
|
|
287
|
+
/** The site-wide Google Maps cloud style. */
|
|
288
|
+
mapsMapId?: string | null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
/** The part of the site record the editor writes. */
|
|
292
|
+
export type SiteRecordPatch = { mapsApiKey?: string };
|
|
293
|
+
|
|
294
|
+
export interface WebsiteSite {
|
|
295
|
+
get(scope?: SiteScope): Promise<SiteRecord>;
|
|
296
|
+
/** Save the site-wide Maps key. Without it that field is read-only. */
|
|
297
|
+
update?(patch: SiteRecordPatch, scope?: SiteScope): Promise<SiteRecord>;
|
|
298
|
+
/** RESERVED for the multilingual overlay: the languages the website is
|
|
299
|
+
* written in. 0.4.0 does not call it. */
|
|
300
|
+
languages?(): Promise<{ base: string; others: string[] }>;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export interface WebsiteMedia {
|
|
304
|
+
/**
|
|
305
|
+
* Turn a stored media reference into a URL the browser can load, or answer
|
|
306
|
+
* null to leave the reference as it is. Synchronous — it runs while a section
|
|
307
|
+
* renders. `width` is the size a thumbnail is drawn at, when the caller has one.
|
|
308
|
+
*/
|
|
309
|
+
resolve(ref: string, options?: { width?: number }): string | null;
|
|
310
|
+
/** RESERVED: upload a file dropped on a media field. 0.4.0 hands a dropped
|
|
311
|
+
* file to the host's media panel or picker and does not call it. */
|
|
312
|
+
upload?(file: File): Promise<HostPickedMedia>;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** One node of a page document: a section or a block, with its own props. */
|
|
316
|
+
export type SectionNode = { type: string; props: { id: string; [key: string]: unknown } };
|
|
317
|
+
|
|
318
|
+
/** A stored section template. `node` is null for a card nobody has designed. */
|
|
319
|
+
export interface SectionTemplateRecord {
|
|
320
|
+
id: string;
|
|
321
|
+
name: string;
|
|
322
|
+
component_type: string;
|
|
323
|
+
node: SectionNode | null;
|
|
324
|
+
created_at: string;
|
|
325
|
+
updated_at: string;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/** A section template with a design. */
|
|
329
|
+
export interface SectionTemplate extends SectionTemplateRecord {
|
|
330
|
+
node: SectionNode;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/** Reusable single sections, edited on their own and dropped into any page. */
|
|
334
|
+
export interface WebsiteSectionTemplates {
|
|
335
|
+
/** The designed templates, newest first. */
|
|
336
|
+
list(): Promise<SectionTemplate[]>;
|
|
337
|
+
/** The record at `id`, designed or not; null when there is none. */
|
|
338
|
+
get(id: string): Promise<SectionTemplateRecord | null>;
|
|
339
|
+
/** The template at `id` with its design, seeding `name` + `node` when it has none. */
|
|
340
|
+
ensure(id: string, name: string, node: SectionNode): Promise<SectionTemplate>;
|
|
341
|
+
save(name: string, node: SectionNode): Promise<SectionTemplate>;
|
|
342
|
+
update(id: string, node: SectionNode): Promise<SectionTemplate>;
|
|
343
|
+
rename(id: string, name: string): Promise<SectionTemplateRecord>;
|
|
344
|
+
remove(id: string): Promise<void>;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** Website templates: blueprints whose pages the same editor edits. */
|
|
348
|
+
export interface WebsiteTemplates {
|
|
349
|
+
/** The template's name (the canvas header's wordmark while it is edited). */
|
|
350
|
+
get(id: string): Promise<{ id: string; name: string }>;
|
|
351
|
+
/** The template's pages. They never publish and carry no lock. */
|
|
352
|
+
pages(templateId: string): WebsitePages;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/** Where the top bar's Preview link goes. */
|
|
356
|
+
export interface WebsitePreview {
|
|
357
|
+
/** The public address of the page with this slug. */
|
|
358
|
+
href(slug: string): string;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Everything the editor asks a server for.
|
|
363
|
+
*
|
|
364
|
+
* Other groups the editor still reads from the EFFICIENT client directly — the
|
|
365
|
+
* site's theme, header, social links and live chat, menus, blog, forms and the
|
|
366
|
+
* catalog — join this interface as optional members when their call sites move.
|
|
367
|
+
* Until then a backend other than EFFICIENT's gets the page editor without them.
|
|
368
|
+
*/
|
|
369
|
+
export interface WebsiteBackend {
|
|
370
|
+
pages: WebsitePages;
|
|
371
|
+
lock?: WebsiteEditLock;
|
|
372
|
+
site: WebsiteSite;
|
|
373
|
+
media: WebsiteMedia;
|
|
374
|
+
sectionTemplates?: WebsiteSectionTemplates;
|
|
375
|
+
templates?: WebsiteTemplates;
|
|
376
|
+
preview?: WebsitePreview;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
/** Why a backend call failed, in the editor's own terms. */
|
|
380
|
+
export type WebsiteBackendErrorKind =
|
|
381
|
+
/** The session is not (or no longer) authorised. The editor tells its host
|
|
382
|
+
* (`storefront-editor:auth-expired`) and treats the call as failed. */
|
|
383
|
+
| "unauthorized"
|
|
384
|
+
/** A save was refused: the stored page moved on since this copy was loaded.
|
|
385
|
+
* Carries `conflict`. */
|
|
386
|
+
| "changed-elsewhere"
|
|
387
|
+
/** Somebody else holds the page (a claim), or took it from this tab (a save).
|
|
388
|
+
* Carries `holder`. */
|
|
389
|
+
| "lock-taken"
|
|
390
|
+
/** There is no such page, site or template. */
|
|
391
|
+
| "not-found"
|
|
392
|
+
/** The backend understood the request and refused it as wrong. */
|
|
393
|
+
| "invalid"
|
|
394
|
+
/** No answer: offline, timed out, the backend itself failing. */
|
|
395
|
+
| "unavailable";
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* The one error a backend rejects with. `message` is shown to the operator
|
|
399
|
+
* where the editor has nowhere better to read from (a document that would not
|
|
400
|
+
* load), so write it for a person.
|
|
401
|
+
*/
|
|
402
|
+
export class WebsiteBackendError extends Error {
|
|
403
|
+
readonly kind: WebsiteBackendErrorKind;
|
|
404
|
+
/** `changed-elsewhere`: who saved, and whether it was this person's own tab. */
|
|
405
|
+
readonly conflict?: SaveConflict;
|
|
406
|
+
/** `lock-taken`: who holds the page. */
|
|
407
|
+
readonly holder?: LockHolder;
|
|
408
|
+
constructor(
|
|
409
|
+
kind: WebsiteBackendErrorKind,
|
|
410
|
+
message?: string,
|
|
411
|
+
detail?: { conflict?: SaveConflict; holder?: LockHolder; cause?: unknown },
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export interface EfficientBackendOptions {
|
|
416
|
+
/** Pin every site-scoped call to one website. Without it the client follows
|
|
417
|
+
* the editor's open document, as the built-in default does. */
|
|
418
|
+
siteId?: string;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* The EFFICIENT backend, for an embedder that wants to pass it explicitly — to
|
|
423
|
+
* hand over one group of its own: `{ ...createEfficientBackend(), preview }`.
|
|
424
|
+
* Leaving `backend` out of the editor's props is the same thing.
|
|
425
|
+
*/
|
|
426
|
+
export function createEfficientBackend(options?: EfficientBackendOptions): WebsiteBackend;
|
|
427
|
+
|
|
428
|
+
/** The website the editor is showing. */
|
|
429
|
+
export interface WebsiteEditorSite {
|
|
430
|
+
/** `StorefrontSite.slug` — named on every public read. */
|
|
431
|
+
slug: string;
|
|
432
|
+
/** The website's public origin, no trailing slash. */
|
|
433
|
+
origin: string;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// ── Which blocks the editor offers (0.7.0) ───────────────────────────────────
|
|
437
|
+
//
|
|
438
|
+
// Leave `blocks` out of the editor's props and it offers every block it has —
|
|
439
|
+
// exactly as before 0.7.0. Supply it to choose which SETS are offered, to allow
|
|
440
|
+
// or deny single blocks, and to add blocks of your own.
|
|
441
|
+
//
|
|
442
|
+
// • OFFERED, NOT REGISTERED. A block you leave out is in no group of the Add
|
|
443
|
+
// panel, no slot's menu and no drop target. A page that already holds one
|
|
444
|
+
// still draws it, and it can be selected and deleted.
|
|
445
|
+
// • NOT A SECURITY BOUNDARY. Check a saved page on your server with
|
|
446
|
+
// `blockTypesIn` from `efficient-web-builder/contract`.
|
|
447
|
+
// • `sets`, `allow`, `deny`, `host` and `categories` are read once, when the
|
|
448
|
+
// editor mounts — re-mount (a `key`) to change them. `metadata` is live.
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* The set a block travels in.
|
|
452
|
+
*
|
|
453
|
+
* general draws from its own props and what the host hands it; any host
|
|
454
|
+
* may offer it
|
|
455
|
+
* commerce the shop: products, the catalogue, the cart, the checkout
|
|
456
|
+
* service needs one of the EFFICIENT backend's site services (forms,
|
|
457
|
+
* bookings, reviews, the newsletter, the blog, menus)
|
|
458
|
+
*/
|
|
459
|
+
export type WebsiteBlockSet = "general" | "commerce" | "service";
|
|
460
|
+
|
|
461
|
+
/** A group of the Add panel a host places its blocks in. */
|
|
462
|
+
export interface WebsiteBlockCategory {
|
|
463
|
+
/** The group's key. An existing group's key (`content`, `structure`, …) adds
|
|
464
|
+
* the blocks to the end of that group; a new key makes a new group. */
|
|
465
|
+
key: string;
|
|
466
|
+
/** A new group's heading. Not read for an existing group. */
|
|
467
|
+
title: string;
|
|
468
|
+
/** The types in the group, in order. */
|
|
469
|
+
components: string[];
|
|
470
|
+
/** Put a NEW group before the group with this key. Without it — or when no
|
|
471
|
+
* such group is offered — the group is added last. */
|
|
472
|
+
before?: string;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
/** The host's choice of blocks. Every member is optional. */
|
|
476
|
+
export interface WebsiteEditorBlocks {
|
|
477
|
+
/** The sets offered. Default: all three. */
|
|
478
|
+
sets?: readonly WebsiteBlockSet[];
|
|
479
|
+
/** After `sets`: only these block types. A section that is built from other
|
|
480
|
+
* blocks (a Hero from its slides) is offered only when those are too. */
|
|
481
|
+
allow?: readonly string[];
|
|
482
|
+
/** After both: never these. */
|
|
483
|
+
deny?: readonly string[];
|
|
484
|
+
/**
|
|
485
|
+
* The host's own blocks, by type, each written as the engine's
|
|
486
|
+
* `ComponentConfig`. A type the editor already registers is refused (the
|
|
487
|
+
* editor throws when it mounts): name yours so they cannot collide.
|
|
488
|
+
*
|
|
489
|
+
* A host's block is a SECTION: it is added to the page from the Add panel, in
|
|
490
|
+
* the group `categories` names, and it is given what the editor's own
|
|
491
|
+
* sections are given — the Padding, Corners, Width, Height, Anchor, Theme and
|
|
492
|
+
* Motion rows, the settings tabs (its own fields under Content),
|
|
493
|
+
* click-to-edit text and the Layers tree's eye. So untouched it is on the
|
|
494
|
+
* site's first theme, as any section is; and a field of its own under a name
|
|
495
|
+
* the editor adds a row with (`anchor`, `widthMode`, `sectionAccent`,
|
|
496
|
+
* `sectionPaddingTop`, …) is refused when the editor mounts. It is not
|
|
497
|
+
* offered inside a Box.
|
|
498
|
+
*/
|
|
499
|
+
host?: Record<string, ComponentConfig>;
|
|
500
|
+
/** Where the host's blocks stand in the Add panel. A block named in no group
|
|
501
|
+
* is registered and drawn, and offered nowhere. */
|
|
502
|
+
categories?: WebsiteBlockCategory[];
|
|
503
|
+
/**
|
|
504
|
+
* The host's live data. Every block in the canvas receives it as
|
|
505
|
+
* `puck.metadata`. Change the object and the blocks redraw in place; it is
|
|
506
|
+
* never written to the page or to the undo history.
|
|
507
|
+
*/
|
|
508
|
+
metadata?: Record<string, unknown>;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/** The page / website-template editor. Re-mount (a `key`) to change website. */
|
|
512
|
+
export function WebsiteEditor(props: {
|
|
513
|
+
host: EditorHost;
|
|
514
|
+
target: EditorTarget;
|
|
515
|
+
site: WebsiteEditorSite | null;
|
|
516
|
+
/** The host's media library, drawn in the editor's left panel. Without one,
|
|
517
|
+
* fields ask the host to open its picker window (`open-media-picker`). */
|
|
518
|
+
mediaPanel?: MediaPanelComponent | null;
|
|
519
|
+
/** The server the editor talks to. Without one it is EFFICIENT's
|
|
520
|
+
* (`createEfficientBackend`). Read once, when the editor mounts. */
|
|
521
|
+
backend?: WebsiteBackend | null;
|
|
522
|
+
/** Which blocks the editor offers, and the host's own. Without one: every
|
|
523
|
+
* block, as before 0.7.0. */
|
|
524
|
+
blocks?: WebsiteEditorBlocks | null;
|
|
525
|
+
}): ReactElement;
|
|
526
|
+
|
|
527
|
+
/** The standalone one-section editor (a section template). */
|
|
528
|
+
export function WebsiteSectionEditor(props: {
|
|
529
|
+
host: EditorHost;
|
|
530
|
+
id: string;
|
|
531
|
+
initialType?: string;
|
|
532
|
+
initialName?: string;
|
|
533
|
+
site: WebsiteEditorSite | null;
|
|
534
|
+
/** The host's media library — see `WebsiteEditor`. */
|
|
535
|
+
mediaPanel?: MediaPanelComponent | null;
|
|
536
|
+
/** The server the editor talks to — see `WebsiteEditor`. Needs
|
|
537
|
+
* `sectionTemplates`. */
|
|
538
|
+
backend?: WebsiteBackend | null;
|
|
539
|
+
/** Which blocks the editor offers — see `WebsiteEditor`. */
|
|
540
|
+
blocks?: WebsiteEditorBlocks | null;
|
|
541
|
+
}): ReactElement;
|