@seamlet/astro 0.0.0-stage → 0.1.1

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.
@@ -0,0 +1,346 @@
1
+ // src/schema/fields.ts
2
+ function defineSchema(fields) {
3
+ return fields;
4
+ }
5
+ function text(options) {
6
+ return { kind: "text", ...options };
7
+ }
8
+ function number(options) {
9
+ return { kind: "number", ...options };
10
+ }
11
+ function date(options) {
12
+ return { kind: "date", ...options };
13
+ }
14
+ function image(options) {
15
+ return { kind: "image", ...options };
16
+ }
17
+ function link(options) {
18
+ return { kind: "link", ...options };
19
+ }
20
+ function group(options) {
21
+ return { kind: "group", ...options };
22
+ }
23
+ function collection(options) {
24
+ return { kind: "collection", ...options };
25
+ }
26
+
27
+ // src/schema/content.ts
28
+ function defaultValue(field) {
29
+ switch (field.kind) {
30
+ case "text":
31
+ return "";
32
+ case "number":
33
+ case "date":
34
+ case "image":
35
+ case "link":
36
+ return null;
37
+ case "group":
38
+ return defaultContent(field.fields);
39
+ case "collection":
40
+ return [];
41
+ }
42
+ }
43
+ function defaultContent(fields) {
44
+ return Object.fromEntries(
45
+ Object.entries(fields).map(([name, field]) => [name, defaultValue(field)])
46
+ );
47
+ }
48
+
49
+ // src/schema/link-addresses.ts
50
+ var PHONE_PREFIX = "tel:";
51
+ var EMAIL_PREFIX = "mailto:";
52
+ var WEB_PREFIX = "https://";
53
+ var SITE_ORIGIN = "https://site.invalid";
54
+ var FIRST_PRINTABLE_CODE = 32;
55
+ var DELETE_CODE = 127;
56
+ var BACKSLASH_CODE = 92;
57
+ var WEB_ADDRESS = /^https?:\/\/[^\s/?#]+[^\s]*$/iu;
58
+ var ANY_SCHEME = /^[a-z][a-z\d+.-]*:(?!\d)/iu;
59
+ var PHONE_NUMBER = /^\+?[\d\s().-]*\d[\d\s().-]*$/u;
60
+ var NOT_PHONE_DIGIT = /[^\d+]/gu;
61
+ var EMAIL_ADDRESS = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u;
62
+ function linkTargetOf(href) {
63
+ const address = href;
64
+ const hasSpacesAround = address !== address.trim();
65
+ if (hasSpacesAround) {
66
+ return void 0;
67
+ }
68
+ if (isSitePath(address)) {
69
+ return { kind: "page", path: address };
70
+ }
71
+ if (WEB_ADDRESS.test(address)) {
72
+ return { kind: "web", url: address };
73
+ }
74
+ const phone = withoutPrefix(address, PHONE_PREFIX);
75
+ if (phone !== void 0 && PHONE_NUMBER.test(phone)) {
76
+ return { kind: "phone", number: phone };
77
+ }
78
+ const email = withoutPrefix(address, EMAIL_PREFIX);
79
+ if (email !== void 0 && EMAIL_ADDRESS.test(email)) {
80
+ return { kind: "email", address: email };
81
+ }
82
+ return void 0;
83
+ }
84
+ function hrefOf(target) {
85
+ switch (target.kind) {
86
+ case "page":
87
+ return target.path.trim();
88
+ case "web":
89
+ return webAddressOf(target.url.trim());
90
+ case "phone":
91
+ return `${PHONE_PREFIX}${target.number.replace(NOT_PHONE_DIGIT, "")}`;
92
+ case "email":
93
+ return `${EMAIL_PREFIX}${target.address.trim()}`;
94
+ }
95
+ }
96
+ function webAddressOf(typed) {
97
+ const hasScheme = ANY_SCHEME.test(typed);
98
+ return hasScheme || typed === "" ? typed : `${WEB_PREFIX}${typed}`;
99
+ }
100
+ function isSitePath(address) {
101
+ if (!address.startsWith("/") || hasAmbiguousCharacter(address)) {
102
+ return false;
103
+ }
104
+ return new URL(address, SITE_ORIGIN).origin === SITE_ORIGIN;
105
+ }
106
+ function hasAmbiguousCharacter(address) {
107
+ const codes = Array.from(
108
+ { length: address.length },
109
+ (_, index) => address.charCodeAt(index)
110
+ );
111
+ return codes.some(
112
+ (code) => code === BACKSLASH_CODE || code < FIRST_PRINTABLE_CODE || code === DELETE_CODE
113
+ );
114
+ }
115
+ function withoutPrefix(address, prefix) {
116
+ const hasPrefix = address.toLowerCase().startsWith(prefix);
117
+ return hasPrefix ? address.slice(prefix.length) : void 0;
118
+ }
119
+
120
+ // src/schema/parse-content.ts
121
+ var DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/u;
122
+ var GRAPHEMES = new Intl.Segmenter("ru-RU", { granularity: "grapheme" });
123
+ function formatFieldPath(path) {
124
+ return path.join(".");
125
+ }
126
+ function countCharacters(text2) {
127
+ return [...GRAPHEMES.segment(text2)].length;
128
+ }
129
+ function parseContent(schema, input) {
130
+ const parsed = parseFields(schema, input, []);
131
+ return { content: parsed.value, issues: parsed.issues };
132
+ }
133
+ function parseFields(fields, input, path) {
134
+ if (!isRecord(input)) {
135
+ return invalidType(defaultContent(fields), path);
136
+ }
137
+ const parsedFields = Object.entries(fields).map(([name, field]) => {
138
+ const fieldInput = Object.hasOwn(input, name) ? input[name] : void 0;
139
+ return [name, parseField(field, fieldInput, [...path, name])];
140
+ });
141
+ const content = Object.fromEntries(
142
+ parsedFields.map(([name, parsed]) => [name, parsed.value])
143
+ );
144
+ const issues = parsedFields.flatMap(([, parsed]) => parsed.issues);
145
+ return { value: content, issues };
146
+ }
147
+ function parseField(field, input, path) {
148
+ if (input === void 0) {
149
+ return valid(defaultValue(field));
150
+ }
151
+ switch (field.kind) {
152
+ case "text":
153
+ return parseText(field, input, path);
154
+ case "number":
155
+ return parseNumber(field, input, path);
156
+ case "date":
157
+ return parseDate(field, input, path);
158
+ case "image":
159
+ return parseImage(field, input, path);
160
+ case "link":
161
+ return parseLink(field, input, path);
162
+ case "group":
163
+ return parseFields(field.fields, input, path);
164
+ case "collection":
165
+ return parseCollection(field, input, path);
166
+ }
167
+ }
168
+ function parseText(field, input, path) {
169
+ if (typeof input !== "string") {
170
+ return invalidType(defaultValue(field), path);
171
+ }
172
+ const isTooLong = field.maxLength !== void 0 && countCharacters(input) > field.maxLength;
173
+ if (isTooLong) {
174
+ return {
175
+ value: input,
176
+ issues: [{ path, code: "too-long", maxLength: field.maxLength }]
177
+ };
178
+ }
179
+ return valid(input);
180
+ }
181
+ function parseNumber(field, input, path) {
182
+ if (input === null) {
183
+ return valid(null);
184
+ }
185
+ if (typeof input !== "number" || !Number.isFinite(input)) {
186
+ return invalidType(defaultValue(field), path);
187
+ }
188
+ if (field.min !== void 0 && input < field.min) {
189
+ return {
190
+ value: input,
191
+ issues: [{ path, code: "below-min", min: field.min }]
192
+ };
193
+ }
194
+ if (field.max !== void 0 && input > field.max) {
195
+ return {
196
+ value: input,
197
+ issues: [{ path, code: "above-max", max: field.max }]
198
+ };
199
+ }
200
+ return valid(input);
201
+ }
202
+ function parseDate(field, input, path) {
203
+ if (input === null) {
204
+ return valid(null);
205
+ }
206
+ if (typeof input !== "string") {
207
+ return invalidType(defaultValue(field), path);
208
+ }
209
+ if (!isCalendarDate(input)) {
210
+ return {
211
+ value: defaultValue(field),
212
+ issues: [{ path, code: "invalid-date" }]
213
+ };
214
+ }
215
+ return valid(input);
216
+ }
217
+ function parseImage(field, input, path) {
218
+ if (input === null) {
219
+ return valid(null);
220
+ }
221
+ const src = isRecord(input) ? input["src"] : void 0;
222
+ const alt = isRecord(input) ? input["alt"] : void 0;
223
+ if (typeof src !== "string" || typeof alt !== "string") {
224
+ return invalidType(defaultValue(field), path);
225
+ }
226
+ const image2 = { src, alt };
227
+ if (src.trim() === "") {
228
+ return { value: image2, issues: [{ path, code: "missing-image-src" }] };
229
+ }
230
+ return valid(image2);
231
+ }
232
+ function parseLink(field, input, path) {
233
+ if (input === null) {
234
+ return valid(null);
235
+ }
236
+ if (!isRecord(input)) {
237
+ return invalidType(defaultValue(field), path);
238
+ }
239
+ const { text: text2, href } = input;
240
+ if (typeof text2 !== "string" || typeof href !== "string") {
241
+ return invalidType(defaultValue(field), path);
242
+ }
243
+ const link2 = { text: text2, href };
244
+ if (href.trim() === "") {
245
+ return { value: link2, issues: [{ path, code: "missing-link-href" }] };
246
+ }
247
+ if (linkTargetOf(href) === void 0) {
248
+ return { value: link2, issues: [{ path, code: "unsupported-link-href" }] };
249
+ }
250
+ return valid(link2);
251
+ }
252
+ function parseCollection(field, input, path) {
253
+ if (!Array.isArray(input)) {
254
+ return invalidType(defaultValue(field), path);
255
+ }
256
+ const records = input;
257
+ const parsedRecords = records.map(
258
+ (record, index) => parseFields(field.item, record, [...path, index])
259
+ );
260
+ return {
261
+ value: parsedRecords.map((parsed) => parsed.value),
262
+ issues: parsedRecords.flatMap((parsed) => parsed.issues)
263
+ };
264
+ }
265
+ function isCalendarDate(value) {
266
+ if (!DATE_PATTERN.test(value)) {
267
+ return false;
268
+ }
269
+ const time = Date.parse(`${value}T00:00:00Z`);
270
+ return !Number.isNaN(time) && new Date(time).toISOString().startsWith(value);
271
+ }
272
+ function isRecord(value) {
273
+ return typeof value === "object" && value !== null && !Array.isArray(value);
274
+ }
275
+ function valid(value) {
276
+ return { value, issues: [] };
277
+ }
278
+ function invalidType(fallback, path) {
279
+ return { value: fallback, issues: [{ path, code: "invalid-type" }] };
280
+ }
281
+
282
+ // src/schema/field-markers.ts
283
+ var FIELD_PATH_ATTRIBUTE = "data-cms-path";
284
+ function editable() {
285
+ return (...path) => ({ [FIELD_PATH_ATTRIBUTE]: formatFieldPath(path) });
286
+ }
287
+
288
+ // src/schema/field-paths.ts
289
+ var RECORD_INDEX_PATTERN = /^\d+$/u;
290
+ function resolveFieldPath(schema, text2) {
291
+ return resolveInFields(schema, text2.split("."), []);
292
+ }
293
+ function resolveInFields(fields, segments, parentPath) {
294
+ const [name, ...rest] = segments;
295
+ const field = name !== void 0 && Object.hasOwn(fields, name) ? fields[name] : void 0;
296
+ if (name === void 0 || field === void 0) {
297
+ return void 0;
298
+ }
299
+ const path = [...parentPath, name];
300
+ if (rest.length === 0) {
301
+ return { kind: "field", path, field };
302
+ }
303
+ return resolveInsideField(field, rest, path);
304
+ }
305
+ function resolveInsideField(field, segments, path) {
306
+ if (field.kind === "group") {
307
+ return resolveInFields(field.fields, segments, path);
308
+ }
309
+ if (field.kind === "collection") {
310
+ return resolveInCollection(field, segments, path);
311
+ }
312
+ return void 0;
313
+ }
314
+ function resolveInCollection(collection2, segments, collectionPath) {
315
+ const [indexText, ...rest] = segments;
316
+ if (indexText === void 0 || !RECORD_INDEX_PATTERN.test(indexText)) {
317
+ return void 0;
318
+ }
319
+ const index = Number(indexText);
320
+ const recordPath = [...collectionPath, index];
321
+ if (rest.length === 0) {
322
+ return { kind: "record", path: recordPath, collection: collection2, index };
323
+ }
324
+ return resolveInFields(collection2.item, rest, recordPath);
325
+ }
326
+
327
+ export {
328
+ defineSchema,
329
+ text,
330
+ number,
331
+ date,
332
+ image,
333
+ link,
334
+ group,
335
+ collection,
336
+ defaultValue,
337
+ defaultContent,
338
+ linkTargetOf,
339
+ hrefOf,
340
+ formatFieldPath,
341
+ countCharacters,
342
+ parseContent,
343
+ FIELD_PATH_ATTRIBUTE,
344
+ editable,
345
+ resolveFieldPath
346
+ };
@@ -0,0 +1,160 @@
1
+ import {
2
+ formatFieldPath,
3
+ resolveFieldPath
4
+ } from "./chunk-EC42AIQC.js";
5
+
6
+ // src/bridge/editor-shortcuts.ts
7
+ function shortcutOf(event) {
8
+ const hasCommandKey = event.metaKey || event.ctrlKey;
9
+ if (!hasCommandKey || event.altKey) {
10
+ return void 0;
11
+ }
12
+ switch (event.code) {
13
+ case "KeyS":
14
+ return "save";
15
+ case "KeyZ":
16
+ return event.shiftKey ? "redo" : "undo";
17
+ case "KeyY":
18
+ return "redo";
19
+ default:
20
+ return void 0;
21
+ }
22
+ }
23
+
24
+ // src/bridge/image-fields.ts
25
+ function imageFieldAt(schema, path) {
26
+ const target = path === void 0 ? void 0 : resolveFieldPath(schema, path);
27
+ const isImage = target?.kind === "field" && target.field.kind === "image";
28
+ return isImage ? { path: target.path, field: target.field } : void 0;
29
+ }
30
+
31
+ // src/bridge/messages.ts
32
+ var PROTOCOL_VERSION = 1;
33
+ var EDITOR_KEYS = /* @__PURE__ */ new Set([
34
+ "save",
35
+ "undo",
36
+ "redo",
37
+ "escape"
38
+ ]);
39
+ var TYPING_OUTCOMES = /* @__PURE__ */ new Set([
40
+ "kept",
41
+ "reverted"
42
+ ]);
43
+ function readPageMessage(data) {
44
+ if (!isRecord(data)) return void 0;
45
+ return isPageMessage(data) ? data : void 0;
46
+ }
47
+ function readEditorMessage(data) {
48
+ if (!isRecord(data)) return void 0;
49
+ return isEditorMessage(data) ? data : void 0;
50
+ }
51
+ function isPageMessage(message) {
52
+ switch (message["type"]) {
53
+ case "ready":
54
+ return isNumber(message["protocol"]);
55
+ case "file-drag-end":
56
+ case "rendered":
57
+ case "render-failed":
58
+ return true;
59
+ case "layout":
60
+ return isLayout(message["layout"]);
61
+ case "hover":
62
+ case "file-drag":
63
+ return isOptional(message["path"], isString);
64
+ case "click":
65
+ return isOptional(message["target"], isClickTarget);
66
+ case "link-click":
67
+ return isString(message["href"]);
68
+ case "key":
69
+ return isString(message["key"]) && EDITOR_KEYS.has(message["key"]);
70
+ case "file-drop":
71
+ return isString(message["path"]) && message["file"] instanceof File;
72
+ case "typing-started":
73
+ case "typing-refused":
74
+ return isString(message["path"]);
75
+ case "typed":
76
+ return isString(message["path"]) && isString(message["text"]);
77
+ case "typing-ended":
78
+ return isString(message["path"]) && isString(message["outcome"]) && TYPING_OUTCOMES.has(message["outcome"]);
79
+ default:
80
+ return false;
81
+ }
82
+ }
83
+ function isEditorMessage(message) {
84
+ switch (message["type"]) {
85
+ case "connect":
86
+ return isRecord(message["schema"]);
87
+ case "render":
88
+ return isRecord(message["content"]);
89
+ case "start-typing":
90
+ return isString(message["path"]) && isString(message["text"]) && typeof message["isSingleLine"] === "boolean" && isPoint(message["point"]);
91
+ case "stop-typing":
92
+ return true;
93
+ case "scroll-to":
94
+ return isFieldPath(message["path"]);
95
+ default:
96
+ return false;
97
+ }
98
+ }
99
+ function isLayout(value) {
100
+ if (!isRecord(value)) return false;
101
+ const markers = value["markers"];
102
+ return Array.isArray(markers) && markers.every(isMarkerBox) && isViewport(value["viewport"]) && isOptional(value["link"], isLinkBox);
103
+ }
104
+ function isMarkerBox(value) {
105
+ return isRecord(value) && isString(value["path"]) && isBox(value["box"]);
106
+ }
107
+ function isLinkBox(value) {
108
+ return isRecord(value) && isString(value["href"]) && isBox(value["box"]);
109
+ }
110
+ function isClickTarget(value) {
111
+ return isRecord(value) && isString(value["path"]) && isPoint(value["point"]);
112
+ }
113
+ function isBox(value) {
114
+ return isRecord(value) && isNumber(value["top"]) && isNumber(value["left"]) && isNumber(value["width"]) && isNumber(value["height"]);
115
+ }
116
+ function isViewport(value) {
117
+ return isRecord(value) && isNumber(value["width"]) && isNumber(value["height"]);
118
+ }
119
+ function isPoint(value) {
120
+ return isRecord(value) && isNumber(value["x"]) && isNumber(value["y"]);
121
+ }
122
+ function isFieldPath(value) {
123
+ return Array.isArray(value) && value.every((segment) => isString(segment) || isNumber(segment));
124
+ }
125
+ function isOptional(value, isPresent) {
126
+ return value === void 0 || isPresent(value);
127
+ }
128
+ function isRecord(value) {
129
+ return typeof value === "object" && value !== null && !Array.isArray(value);
130
+ }
131
+ function isString(value) {
132
+ return typeof value === "string";
133
+ }
134
+ function isNumber(value) {
135
+ return typeof value === "number" && Number.isFinite(value);
136
+ }
137
+
138
+ // src/bridge/file-drags.ts
139
+ function carriesFiles(event) {
140
+ return event.dataTransfer?.types.includes("Files") === true;
141
+ }
142
+
143
+ // src/bridge/marked-paths.ts
144
+ function closestMarkedPath(path, isMarked) {
145
+ for (let length = path.length; length > 0; length -= 1) {
146
+ const candidate = formatFieldPath(path.slice(0, length));
147
+ if (isMarked(candidate)) return candidate;
148
+ }
149
+ return void 0;
150
+ }
151
+
152
+ export {
153
+ shortcutOf,
154
+ imageFieldAt,
155
+ PROTOCOL_VERSION,
156
+ readPageMessage,
157
+ readEditorMessage,
158
+ carriesFiles,
159
+ closestMarkedPath
160
+ };
@@ -0,0 +1,55 @@
1
+ // src/preview-requests.ts
2
+ var MAX_DRAFT_BYTES = 1024 * 1024;
3
+ var BAD_REQUEST = 400;
4
+ var PAYLOAD_TOO_LARGE = 413;
5
+ var UNSUPPORTED_MEDIA_TYPE = 415;
6
+ var UPLOADED_PHOTO = /^[A-Za-z0-9_-]+\.webp$/u;
7
+ async function readDraft(request) {
8
+ const isJson = request.headers.get("Content-Type")?.startsWith("application/json") === true;
9
+ if (!isJson) return refused(UNSUPPORTED_MEDIA_TYPE);
10
+ const body = await readLimited(request);
11
+ if (body === void 0) return refused(PAYLOAD_TOO_LARGE);
12
+ try {
13
+ return { isRead: true, draft: JSON.parse(body) };
14
+ } catch {
15
+ return refused(BAD_REQUEST);
16
+ }
17
+ }
18
+ function isUploadedPhoto(fileName) {
19
+ return fileName !== void 0 && UPLOADED_PHOTO.test(fileName);
20
+ }
21
+ async function readLimited(request) {
22
+ if (request.body === null) return "";
23
+ const reader = request.body.getReader();
24
+ const chunks = [];
25
+ let size = 0;
26
+ for (; ; ) {
27
+ const { done, value } = await reader.read();
28
+ if (done) break;
29
+ size += value.byteLength;
30
+ if (size > MAX_DRAFT_BYTES) {
31
+ await reader.cancel();
32
+ return void 0;
33
+ }
34
+ chunks.push(value);
35
+ }
36
+ return new TextDecoder().decode(concatenated(chunks, size));
37
+ }
38
+ function concatenated(chunks, size) {
39
+ const bytes = new Uint8Array(size);
40
+ let offset = 0;
41
+ for (const chunk of chunks) {
42
+ bytes.set(chunk, offset);
43
+ offset += chunk.byteLength;
44
+ }
45
+ return bytes;
46
+ }
47
+ function refused(status) {
48
+ return { isRead: false, response: new Response(null, { status }) };
49
+ }
50
+
51
+ export {
52
+ MAX_DRAFT_BYTES,
53
+ readDraft,
54
+ isUploadedPhoto
55
+ };
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/client.js ADDED
@@ -0,0 +1,11 @@
1
+ // src/client.ts
2
+ import { cmsOrigin } from "virtual:seamlet/config";
3
+ var PREVIEW_PATH = "/preview";
4
+ var isFramed = window.parent !== window;
5
+ var { pathname } = window.location;
6
+ var isPreview = pathname === PREVIEW_PATH || pathname.startsWith(`${PREVIEW_PATH}/`);
7
+ if (isFramed && isPreview) {
8
+ void import("./bridge.js").then(({ connectToEditor }) => {
9
+ connectToEditor({ editorOrigin: cmsOrigin });
10
+ });
11
+ }
@@ -0,0 +1,90 @@
1
+ interface TextField {
2
+ readonly kind: "text";
3
+ readonly label: string;
4
+ /** A textarea instead of a single line. */
5
+ readonly multiline?: boolean;
6
+ /** In characters as people see them (see countCharacters), not UTF-16 units. */
7
+ readonly maxLength?: number;
8
+ }
9
+ interface NumberField {
10
+ readonly kind: "number";
11
+ readonly label: string;
12
+ readonly min?: number;
13
+ readonly max?: number;
14
+ }
15
+ /** A calendar date without time, stored as `YYYY-MM-DD`. */
16
+ interface DateField {
17
+ readonly kind: "date";
18
+ readonly label: string;
19
+ }
20
+ /** An image address with its alternative text. */
21
+ interface ImageField {
22
+ readonly kind: "image";
23
+ readonly label: string;
24
+ }
25
+ /**
26
+ * A link: its text and where it leads (a page of the site, another site, a phone or an email).
27
+ * The site decides how it looks; a button «Записаться» stays a button.
28
+ */
29
+ interface LinkField {
30
+ readonly kind: "link";
31
+ readonly label: string;
32
+ }
33
+ /** Named fields shown and stored together, for example the first screen of a page. */
34
+ interface GroupField<F extends Fields = Fields> {
35
+ readonly kind: "group";
36
+ readonly label: string;
37
+ readonly fields: F;
38
+ }
39
+ /** A list of records of the same shape, for example the items of a menu. */
40
+ interface CollectionField<F extends Fields = Fields> {
41
+ readonly kind: "collection";
42
+ readonly label: string;
43
+ readonly item: F;
44
+ /** The field of a record that names it in the editor: a text field, or a link by its text. */
45
+ readonly itemTitle?: keyof F & string;
46
+ }
47
+ type Field = TextField | NumberField | DateField | ImageField | LinkField | GroupField | CollectionField;
48
+ type FieldKind = Field["kind"];
49
+ /** Field names to fields; the order of the keys is the order of the form. */
50
+ interface Fields {
51
+ readonly [name: string]: Field;
52
+ }
53
+ type FieldOptions<F extends Field> = Omit<F, "kind">;
54
+ /** The root of a site's schema. Only checks the shape and keeps the literal types. */
55
+ declare function defineSchema<F extends Fields>(fields: F): F;
56
+ declare function text(options: FieldOptions<TextField>): TextField;
57
+ declare function number(options: FieldOptions<NumberField>): NumberField;
58
+ declare function date(options: FieldOptions<DateField>): DateField;
59
+ declare function image(options: FieldOptions<ImageField>): ImageField;
60
+ declare function link(options: FieldOptions<LinkField>): LinkField;
61
+ declare function group<F extends Fields>(options: FieldOptions<GroupField<F>>): GroupField<F>;
62
+ declare function collection<F extends Fields>(options: FieldOptions<CollectionField<F>>): CollectionField<F>;
63
+
64
+ interface ImageValue {
65
+ readonly src: string;
66
+ readonly alt: string;
67
+ }
68
+ interface LinkValue {
69
+ readonly text: string;
70
+ /** A path of the site (`/menu`), a web address, `tel:` or `mailto:`; see linkTargetOf. */
71
+ readonly href: string;
72
+ }
73
+ /**
74
+ * The value of each field. A text can be empty (""), the other scalar fields can be unset (null):
75
+ * content is filled in by people, so a site must be ready for a missing price or date.
76
+ */
77
+ type ValueOf<F extends Field> = F extends TextField ? string : F extends NumberField ? number | null : F extends DateField ? string | null : F extends ImageField ? ImageValue | null : F extends LinkField ? LinkValue | null : F extends GroupField<infer Group> ? ContentOf<Group> : F extends CollectionField<infer Item> ? readonly ContentOf<Item>[] : never;
78
+ /** The content a schema describes: `ContentOf<typeof schema>` is the type a site renders. */
79
+ type ContentOf<F extends Fields> = {
80
+ readonly [Name in keyof F]: ValueOf<F[Name]>;
81
+ };
82
+ /** Content of any schema, for code that works with every site, such as the editor and the store. */
83
+ interface Content {
84
+ readonly [name: string]: FieldValue;
85
+ }
86
+ type FieldValue = string | number | null | ImageValue | LinkValue | Content | readonly Content[];
87
+ declare function defaultValue(field: Field): FieldValue;
88
+ declare function defaultContent(fields: Fields): Content;
89
+
90
+ export { type ContentOf as C, type DateField as D, type Fields as F, type GroupField as G, type ImageField as I, type LinkValue as L, type NumberField as N, type TextField as T, type ValueOf as V, type ImageValue as a, type Field as b, type CollectionField as c, type Content as d, type FieldKind as e, type FieldValue as f, type LinkField as g, collection as h, date as i, defaultContent as j, defaultValue as k, defineSchema as l, group as m, image as n, link as o, number as p, text as t };
@@ -0,0 +1,11 @@
1
+ import { AstroIntegration } from 'astro';
2
+
3
+ interface SeamletOptions {
4
+ /** The address of the CMS, such as `https://cms.example.com`. */
5
+ readonly cms: string;
6
+ /** The module of the site, relative to the project: `defineSeamletSite(…)` as its default export. */
7
+ readonly site: string;
8
+ }
9
+ declare function seamlet(options: SeamletOptions): AstroIntegration;
10
+
11
+ export { type SeamletOptions, seamlet as default };