@seamlet/astro 0.1.0 → 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/README.md CHANGED
@@ -76,6 +76,55 @@ Connects an [Astro](https://astro.build) site to the Seamlet CMS ([source](https
76
76
 
77
77
  The site's pages take their published content with `site.loadPublishedContent()`: at build time for a static site, on request otherwise.
78
78
 
79
+ ## Fields
80
+
81
+ | Builder | Holds | Empty |
82
+ | ----------------------------------------- | ----------------------------------------------------------------------- | ---------------- |
83
+ | `text({ label, multiline?, maxLength? })` | a string | `""` |
84
+ | `number({ label, min?, max? })` | a number | `null` |
85
+ | `date({ label })` | `YYYY-MM-DD` | `null` |
86
+ | `time({ label })` | `HH:MM`, 00:00 to 23:59 | `null` |
87
+ | `image({ label })` | `{ src, alt }` | `null` |
88
+ | `link({ label })` | `{ text, href }`: a page of the site, another site, a phone or an email | `null` |
89
+ | `url({ label })` | a web address without a text: a booking widget, a map | `null` |
90
+ | `toggle({ label })` | `true` or `false` | `false` |
91
+ | `choice({ label, options })` | the `value` of one option | `null` |
92
+ | `choices({ label, options })` | the `value`s of any number of options | `[]` |
93
+ | `group({ label, fields })` | named fields shown together | each field empty |
94
+ | `collection({ label, item, itemTitle? })` | a list of records of the same fields | `[]` |
95
+
96
+ Options are `{ value, label }`: the site reads `value`, the editor shows `label`, so a label can change without touching the site. The content has their exact type:
97
+
98
+ ```ts
99
+ const level = choice({
100
+ label: "Нагрузка",
101
+ options: [
102
+ { value: "soft", label: "Мягко" },
103
+ { value: "active", label: "Активно" },
104
+ ],
105
+ });
106
+ // ContentOf gives "soft" | "active" | null
107
+ ```
108
+
109
+ To let the owner hide a record without removing it, give the record a toggle and leave hidden records out:
110
+
111
+ ```ts
112
+ item: { name: text({ label: "Название" }), isHidden: toggle({ label: "Скрыть с сайта" }) }
113
+ // content.practices.filter((practice) => !practice.isHidden)
114
+ ```
115
+
116
+ ## What the page hides
117
+
118
+ A field is edited where it is seen, so the editor opens what hides it when a person chooses it in the list of fields. A native `<details>` needs nothing: a click on its summary opens it in the editor too. A part the site hides and shows by itself (its own accordion, a step of a quiz, a dialog, a tab) gets the attribute `data-cms-reveal` (`REVEAL_ATTRIBUTE`). When a field inside it is chosen, the bridge sets `data-cms-revealed` on it and sends it the event `cms:reveal`, which bubbles; the site shows the part with CSS or with its own script:
119
+
120
+ ```css
121
+ [data-cms-reveal][data-cms-revealed] {
122
+ display: block;
123
+ }
124
+ ```
125
+
126
+ What was opened stays open while the page is rendered anew after every edit.
127
+
79
128
  ## What the integration adds
80
129
 
81
130
  - `/preview` and `/preview/<page>`: the preview the CMS's editor frames. `GET` shows the published content; `POST` with the content as JSON shows that content without saving it, which is how the editor shows every edit at once. Only the CMS may frame it (`Content-Security-Policy: frame-ancestors`), and it is never indexed or cached.
package/dist/bridge.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { F as Fields } from './content-DKVM1CUQ.js';
2
- import { P as PageMessage, E as EditorMessage } from './messages-DIMwt3Kn.js';
3
- import './parse-content-DF2dL2FG.js';
1
+ import { F as Fields } from './content-CD5TXi4R.js';
2
+ import { P as PageMessage, E as EditorMessage } from './messages-BQSbv5YE.js';
3
+ import './parse-content-CVoASb5i.js';
4
4
 
5
5
  interface BridgeOptions {
6
6
  /** The origin of the CMS whose editor may frame the page, such as `https://cms.example.com`. */
package/dist/bridge.js CHANGED
@@ -5,10 +5,13 @@ import {
5
5
  imageFieldAt,
6
6
  readEditorMessage,
7
7
  shortcutOf
8
- } from "./chunk-HRAL4X6W.js";
8
+ } from "./chunk-LADJH7RU.js";
9
9
  import {
10
- FIELD_PATH_ATTRIBUTE
11
- } from "./chunk-EC42AIQC.js";
10
+ FIELD_PATH_ATTRIBUTE,
11
+ REVEALED_ATTRIBUTE,
12
+ REVEAL_ATTRIBUTE,
13
+ REVEAL_EVENT
14
+ } from "./chunk-2MRY5Z6U.js";
12
15
 
13
16
  // src/bridge/page-markers.ts
14
17
  var MARKER_SELECTOR = `[${FIELD_PATH_ATTRIBUTE}]`;
@@ -52,8 +55,73 @@ function isHtmlElement(value) {
52
55
  return asElement(value)?.namespaceURI === HTML_NAMESPACE;
53
56
  }
54
57
  function asElement(value) {
55
- const isElement = typeof value === "object" && value !== null && "nodeType" in value && value.nodeType === Node.ELEMENT_NODE;
56
- return isElement ? value : void 0;
58
+ const isElement2 = typeof value === "object" && value !== null && "nodeType" in value && value.nodeType === Node.ELEMENT_NODE;
59
+ return isElement2 ? value : void 0;
60
+ }
61
+
62
+ // src/bridge/page-reveals.ts
63
+ var DETAILS_SELECTOR = "details";
64
+ var REVEAL_SELECTOR = `[${REVEAL_ATTRIBUTE}]`;
65
+ function revealAround(element) {
66
+ for (let part = element; part !== null; ) {
67
+ if (isDetails(part)) part.open = true;
68
+ if (part.hasAttribute(REVEAL_ATTRIBUTE)) reveal(part);
69
+ part = part.parentElement;
70
+ }
71
+ }
72
+ function toggleDetailsAt(target, isOnField) {
73
+ const details = detailsOfSummaryAround(target);
74
+ if (details === void 0) return;
75
+ if (!details.open) {
76
+ details.open = true;
77
+ return;
78
+ }
79
+ if (!isOnField) details.open = false;
80
+ }
81
+ function revealedPartsOf(document2) {
82
+ const details = [...document2.querySelectorAll(DETAILS_SELECTOR)];
83
+ const parts = [...document2.querySelectorAll(REVEAL_SELECTOR)];
84
+ return {
85
+ openDetails: indexesWhere(details, (part) => isDetails(part) && part.open),
86
+ revealed: indexesWhere(
87
+ parts,
88
+ (part) => part.hasAttribute(REVEALED_ATTRIBUTE)
89
+ )
90
+ };
91
+ }
92
+ function restoreRevealedParts(document2, revealed) {
93
+ const details = [...document2.querySelectorAll(DETAILS_SELECTOR)];
94
+ const parts = [...document2.querySelectorAll(REVEAL_SELECTOR)];
95
+ for (const index of revealed.openDetails) {
96
+ const part = details[index];
97
+ if (part !== void 0 && isDetails(part)) part.open = true;
98
+ }
99
+ for (const index of revealed.revealed) {
100
+ const part = parts[index];
101
+ if (part !== void 0) reveal(part);
102
+ }
103
+ }
104
+ function reveal(part) {
105
+ if (part.hasAttribute(REVEALED_ATTRIBUTE)) return;
106
+ part.setAttribute(REVEALED_ATTRIBUTE, "");
107
+ part.dispatchEvent(new CustomEvent(REVEAL_EVENT, { bubbles: true }));
108
+ }
109
+ function detailsOfSummaryAround(target) {
110
+ const summary = isElement(target) ? target.closest("summary") : null;
111
+ const details = summary?.parentElement;
112
+ const isSummaryOfDetails = details !== null && details !== void 0 && isDetails(details);
113
+ return isSummaryOfDetails ? details : void 0;
114
+ }
115
+ function indexesWhere(elements, isPicked) {
116
+ return elements.flatMap(
117
+ (element, index) => isPicked(element) ? [index] : []
118
+ );
119
+ }
120
+ function isDetails(element) {
121
+ return element.localName === "details";
122
+ }
123
+ function isElement(value) {
124
+ return value !== null && "nodeType" in value && value.nodeType === Node.ELEMENT_NODE;
57
125
  }
58
126
 
59
127
  // src/bridge/page-events.ts
@@ -79,6 +147,7 @@ function listenToPage(document2, listeners) {
79
147
  return;
80
148
  }
81
149
  const marked = markedElementAround(event.target);
150
+ toggleDetailsAt(event.target, marked !== void 0);
82
151
  listeners.onClick(
83
152
  marked === void 0 ? void 0 : { ...marked, point: { x: event.clientX, y: event.clientY } }
84
153
  );
@@ -123,6 +192,8 @@ function listenToPage(document2, listeners) {
123
192
  ["drop", dropFile],
124
193
  ["dragleave", leaveWithFile],
125
194
  ["scroll", listeners.onLayoutChange],
195
+ // An opened or closed `<details>` moves what is below it; toggle does not bubble.
196
+ ["toggle", listeners.onLayoutChange],
126
197
  // Images load after the page is shown and move what is below them; load does not bubble.
127
198
  ["load", listeners.onLayoutChange]
128
199
  ];
@@ -255,8 +326,10 @@ async function fetchRenderedPage(url, content, signal) {
255
326
  }
256
327
  function replacePageBody(document2, html) {
257
328
  const rendered = new DOMParser().parseFromString(html, "text/html");
329
+ const revealed = revealedPartsOf(document2);
258
330
  document2.title = rendered.title;
259
331
  document2.body.replaceWith(document2.importNode(rendered.body, true));
332
+ restoreRevealedParts(document2, revealed);
260
333
  }
261
334
 
262
335
  // src/bridge/element-text.ts
@@ -510,10 +583,7 @@ function connectPage(document2, schema, send) {
510
583
  typing.stop();
511
584
  break;
512
585
  case "scroll-to":
513
- findClosestMarkedElement(document2, message.path)?.scrollIntoView({
514
- block: "nearest",
515
- behavior: "smooth"
516
- });
586
+ scrollToField(document2, message.path);
517
587
  break;
518
588
  }
519
589
  };
@@ -527,6 +597,12 @@ function connectPage(document2, schema, send) {
527
597
  }
528
598
  };
529
599
  }
600
+ function scrollToField(document2, path) {
601
+ const element = findClosestMarkedElement(document2, path);
602
+ if (element === void 0) return;
603
+ revealAround(element);
604
+ element.scrollIntoView({ block: "nearest", behavior: "smooth" });
605
+ }
530
606
  function editorKeyOf(event) {
531
607
  if (event.key === "Escape") return "escape";
532
608
  return shortcutOf(event);
@@ -17,6 +17,21 @@ function image(options) {
17
17
  function link(options) {
18
18
  return { kind: "link", ...options };
19
19
  }
20
+ function toggle(options) {
21
+ return { kind: "toggle", ...options };
22
+ }
23
+ function choice(options) {
24
+ return { kind: "choice", ...options };
25
+ }
26
+ function choices(options) {
27
+ return { kind: "choices", ...options };
28
+ }
29
+ function url(options) {
30
+ return { kind: "url", ...options };
31
+ }
32
+ function time(options) {
33
+ return { kind: "time", ...options };
34
+ }
20
35
  function group(options) {
21
36
  return { kind: "group", ...options };
22
37
  }
@@ -29,10 +44,17 @@ function defaultValue(field) {
29
44
  switch (field.kind) {
30
45
  case "text":
31
46
  return "";
47
+ case "toggle":
48
+ return false;
49
+ case "choices":
50
+ return [];
32
51
  case "number":
33
52
  case "date":
34
53
  case "image":
35
54
  case "link":
55
+ case "choice":
56
+ case "url":
57
+ case "time":
36
58
  return null;
37
59
  case "group":
38
60
  return defaultContent(field.fields);
@@ -119,6 +141,7 @@ function withoutPrefix(address, prefix) {
119
141
 
120
142
  // src/schema/parse-content.ts
121
143
  var DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/u;
144
+ var TIME_PATTERN = /^(?:[01]\d|2[0-3]):[0-5]\d$/u;
122
145
  var GRAPHEMES = new Intl.Segmenter("ru-RU", { granularity: "grapheme" });
123
146
  function formatFieldPath(path) {
124
147
  return path.join(".");
@@ -159,6 +182,16 @@ function parseField(field, input, path) {
159
182
  return parseImage(field, input, path);
160
183
  case "link":
161
184
  return parseLink(field, input, path);
185
+ case "toggle":
186
+ return parseToggle(field, input, path);
187
+ case "choice":
188
+ return parseChoice(field, input, path);
189
+ case "choices":
190
+ return parseChoices(field, input, path);
191
+ case "url":
192
+ return parseUrl(field, input, path);
193
+ case "time":
194
+ return parseTime(field, input, path);
162
195
  case "group":
163
196
  return parseFields(field.fields, input, path);
164
197
  case "collection":
@@ -249,6 +282,64 @@ function parseLink(field, input, path) {
249
282
  }
250
283
  return valid(link2);
251
284
  }
285
+ function parseToggle(field, input, path) {
286
+ if (typeof input !== "boolean") {
287
+ return invalidType(defaultValue(field), path);
288
+ }
289
+ return valid(input);
290
+ }
291
+ function parseChoice(field, input, path) {
292
+ if (input === null) {
293
+ return valid(null);
294
+ }
295
+ if (typeof input !== "string") {
296
+ return invalidType(defaultValue(field), path);
297
+ }
298
+ const issues = unknownChoiceIssues(field, [input], path);
299
+ return { value: input, issues };
300
+ }
301
+ function parseChoices(field, input, path) {
302
+ const isListOfStrings = Array.isArray(input) && input.every((value) => typeof value === "string");
303
+ if (!isListOfStrings) {
304
+ return invalidType(defaultValue(field), path);
305
+ }
306
+ const picked = [...new Set(input)];
307
+ const issues = unknownChoiceIssues(field, picked, path);
308
+ return { value: picked, issues };
309
+ }
310
+ function unknownChoiceIssues(field, picked, path) {
311
+ const known = new Set(field.options.map((option) => option.value));
312
+ const unknown = picked.filter((value) => !known.has(value));
313
+ return unknown.map((value) => ({ path, code: "unknown-choice", value }));
314
+ }
315
+ function parseUrl(field, input, path) {
316
+ if (input === null) {
317
+ return valid(null);
318
+ }
319
+ if (typeof input !== "string") {
320
+ return invalidType(defaultValue(field), path);
321
+ }
322
+ const isWebAddress = linkTargetOf(input)?.kind === "web";
323
+ if (!isWebAddress) {
324
+ return { value: input, issues: [{ path, code: "unsupported-url" }] };
325
+ }
326
+ return valid(input);
327
+ }
328
+ function parseTime(field, input, path) {
329
+ if (input === null) {
330
+ return valid(null);
331
+ }
332
+ if (typeof input !== "string") {
333
+ return invalidType(defaultValue(field), path);
334
+ }
335
+ if (!TIME_PATTERN.test(input)) {
336
+ return {
337
+ value: defaultValue(field),
338
+ issues: [{ path, code: "invalid-time" }]
339
+ };
340
+ }
341
+ return valid(input);
342
+ }
252
343
  function parseCollection(field, input, path) {
253
344
  if (!Array.isArray(input)) {
254
345
  return invalidType(defaultValue(field), path);
@@ -266,8 +357,8 @@ function isCalendarDate(value) {
266
357
  if (!DATE_PATTERN.test(value)) {
267
358
  return false;
268
359
  }
269
- const time = Date.parse(`${value}T00:00:00Z`);
270
- return !Number.isNaN(time) && new Date(time).toISOString().startsWith(value);
360
+ const time2 = Date.parse(`${value}T00:00:00Z`);
361
+ return !Number.isNaN(time2) && new Date(time2).toISOString().startsWith(value);
271
362
  }
272
363
  function isRecord(value) {
273
364
  return typeof value === "object" && value !== null && !Array.isArray(value);
@@ -324,6 +415,11 @@ function resolveInCollection(collection2, segments, collectionPath) {
324
415
  return resolveInFields(collection2.item, rest, recordPath);
325
416
  }
326
417
 
418
+ // src/schema/reveal-markers.ts
419
+ var REVEAL_ATTRIBUTE = "data-cms-reveal";
420
+ var REVEALED_ATTRIBUTE = "data-cms-revealed";
421
+ var REVEAL_EVENT = "cms:reveal";
422
+
327
423
  export {
328
424
  defineSchema,
329
425
  text,
@@ -331,6 +427,11 @@ export {
331
427
  date,
332
428
  image,
333
429
  link,
430
+ toggle,
431
+ choice,
432
+ choices,
433
+ url,
434
+ time,
334
435
  group,
335
436
  collection,
336
437
  defaultValue,
@@ -342,5 +443,8 @@ export {
342
443
  parseContent,
343
444
  FIELD_PATH_ATTRIBUTE,
344
445
  editable,
345
- resolveFieldPath
446
+ resolveFieldPath,
447
+ REVEAL_ATTRIBUTE,
448
+ REVEALED_ATTRIBUTE,
449
+ REVEAL_EVENT
346
450
  };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  formatFieldPath,
3
3
  resolveFieldPath
4
- } from "./chunk-EC42AIQC.js";
4
+ } from "./chunk-2MRY5Z6U.js";
5
5
 
6
6
  // src/bridge/editor-shortcuts.ts
7
7
  function shortcutOf(event) {
@@ -30,6 +30,40 @@ interface LinkField {
30
30
  readonly kind: "link";
31
31
  readonly label: string;
32
32
  }
33
+ /** Yes or no, for example «Можно новичкам» or «Скрыть с сайта». */
34
+ interface ToggleField {
35
+ readonly kind: "toggle";
36
+ readonly label: string;
37
+ }
38
+ /** One of the options a person picks from, as the site names it and as the editor shows it. */
39
+ interface ChoiceOption {
40
+ /** What the content stores and the site reads; never shown to people. */
41
+ readonly value: string;
42
+ /** What the editor shows. */
43
+ readonly label: string;
44
+ }
45
+ /** One option of a fixed list, or none: a level, a category, a badge. */
46
+ interface ChoiceField<O extends readonly ChoiceOption[] = readonly ChoiceOption[]> {
47
+ readonly kind: "choice";
48
+ readonly label: string;
49
+ readonly options: O;
50
+ }
51
+ /** Any number of options of a fixed list: tags, the days a class runs. */
52
+ interface ChoicesField<O extends readonly ChoiceOption[] = readonly ChoiceOption[]> {
53
+ readonly kind: "choices";
54
+ readonly label: string;
55
+ readonly options: O;
56
+ }
57
+ /** A web address without a text: a booking widget, a map. Only `http://` and `https://`. */
58
+ interface UrlField {
59
+ readonly kind: "url";
60
+ readonly label: string;
61
+ }
62
+ /** A time of day without a date, stored as `HH:MM` (24 hours). */
63
+ interface TimeField {
64
+ readonly kind: "time";
65
+ readonly label: string;
66
+ }
33
67
  /** Named fields shown and stored together, for example the first screen of a page. */
34
68
  interface GroupField<F extends Fields = Fields> {
35
69
  readonly kind: "group";
@@ -44,7 +78,7 @@ interface CollectionField<F extends Fields = Fields> {
44
78
  /** The field of a record that names it in the editor: a text field, or a link by its text. */
45
79
  readonly itemTitle?: keyof F & string;
46
80
  }
47
- type Field = TextField | NumberField | DateField | ImageField | LinkField | GroupField | CollectionField;
81
+ type Field = TextField | NumberField | DateField | ImageField | LinkField | ToggleField | ChoiceField | ChoicesField | UrlField | TimeField | GroupField | CollectionField;
48
82
  type FieldKind = Field["kind"];
49
83
  /** Field names to fields; the order of the keys is the order of the form. */
50
84
  interface Fields {
@@ -58,6 +92,12 @@ declare function number(options: FieldOptions<NumberField>): NumberField;
58
92
  declare function date(options: FieldOptions<DateField>): DateField;
59
93
  declare function image(options: FieldOptions<ImageField>): ImageField;
60
94
  declare function link(options: FieldOptions<LinkField>): LinkField;
95
+ declare function toggle(options: FieldOptions<ToggleField>): ToggleField;
96
+ /** `const` keeps the values of the options literal, so the content has their exact type. */
97
+ declare function choice<const O extends readonly ChoiceOption[]>(options: FieldOptions<ChoiceField<O>>): ChoiceField<O>;
98
+ declare function choices<const O extends readonly ChoiceOption[]>(options: FieldOptions<ChoicesField<O>>): ChoicesField<O>;
99
+ declare function url(options: FieldOptions<UrlField>): UrlField;
100
+ declare function time(options: FieldOptions<TimeField>): TimeField;
61
101
  declare function group<F extends Fields>(options: FieldOptions<GroupField<F>>): GroupField<F>;
62
102
  declare function collection<F extends Fields>(options: FieldOptions<CollectionField<F>>): CollectionField<F>;
63
103
 
@@ -74,7 +114,9 @@ interface LinkValue {
74
114
  * The value of each field. A text can be empty (""), the other scalar fields can be unset (null):
75
115
  * content is filled in by people, so a site must be ready for a missing price or date.
76
116
  */
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;
117
+ 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 ToggleField ? boolean : F extends ChoiceField<infer Options> ? OptionValue<Options> | null : F extends ChoicesField<infer Options> ? readonly OptionValue<Options>[] : F extends UrlField ? string | null : F extends TimeField ? string | null : F extends GroupField<infer Group> ? ContentOf<Group> : F extends CollectionField<infer Item> ? readonly ContentOf<Item>[] : never;
118
+ /** The values a list of options stores: `"soft" | "active"` for options with those values. */
119
+ type OptionValue<O extends readonly ChoiceOption[]> = O[number]["value"];
78
120
  /** The content a schema describes: `ContentOf<typeof schema>` is the type a site renders. */
79
121
  type ContentOf<F extends Fields> = {
80
122
  readonly [Name in keyof F]: ValueOf<F[Name]>;
@@ -83,8 +125,8 @@ type ContentOf<F extends Fields> = {
83
125
  interface Content {
84
126
  readonly [name: string]: FieldValue;
85
127
  }
86
- type FieldValue = string | number | null | ImageValue | LinkValue | Content | readonly Content[];
128
+ type FieldValue = string | number | boolean | null | readonly string[] | ImageValue | LinkValue | Content | readonly Content[];
87
129
  declare function defaultValue(field: Field): FieldValue;
88
130
  declare function defaultContent(fields: Fields): Content;
89
131
 
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 };
132
+ export { url as A, 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 OptionValue as O, type TextField as T, type UrlField as U, type ValueOf as V, type Content as a, type ImageValue as b, type Field as c, type CollectionField as d, type ChoiceField as e, type ChoiceOption as f, type ChoicesField as g, type FieldKind as h, type FieldValue as i, type LinkField as j, type TimeField as k, type ToggleField as l, choice as m, choices as n, collection as o, date as p, defaultContent as q, defaultValue as r, defineSchema as s, group as t, image as u, link as v, number as w, text as x, time as y, toggle as z };
@@ -1,5 +1,5 @@
1
- import { F as Fields, d as Content } from './content-DKVM1CUQ.js';
2
- import { F as FieldPath } from './parse-content-DF2dL2FG.js';
1
+ import { F as Fields, a as Content } from './content-CD5TXi4R.js';
2
+ import { F as FieldPath } from './parse-content-CVoASb5i.js';
3
3
 
4
4
  interface Box {
5
5
  readonly top: number;
@@ -1,4 +1,4 @@
1
- import { F as Fields, C as ContentOf } from './content-DKVM1CUQ.js';
1
+ import { F as Fields, C as ContentOf } from './content-CD5TXi4R.js';
2
2
 
3
3
  /** Where a value lives in the content: field names and, inside a collection, record indexes. */
4
4
  type FieldPath = readonly (string | number)[];
@@ -8,6 +8,9 @@ type ContentIssue = {
8
8
  } | {
9
9
  readonly path: FieldPath;
10
10
  readonly code: "invalid-date";
11
+ } | {
12
+ readonly path: FieldPath;
13
+ readonly code: "invalid-time";
11
14
  } | {
12
15
  readonly path: FieldPath;
13
16
  readonly code: "too-long";
@@ -29,6 +32,13 @@ type ContentIssue = {
29
32
  } | {
30
33
  readonly path: FieldPath;
31
34
  readonly code: "unsupported-link-href";
35
+ } | {
36
+ readonly path: FieldPath;
37
+ readonly code: "unsupported-url";
38
+ } | {
39
+ readonly path: FieldPath;
40
+ readonly code: "unknown-choice";
41
+ readonly value: string;
32
42
  };
33
43
  interface ParsedContent<C> {
34
44
  readonly content: C;
@@ -1,6 +1,6 @@
1
- export { B as Box, a as EditorKey, E as EditorMessage, I as InlineEditingOutcome, L as LinkBox, M as MarkerBox, b as PROTOCOL_VERSION, c as PageLayout, P as PageMessage, d as Point, S as Shortcut, V as Viewport, r as readEditorMessage, e as readPageMessage, s as shortcutOf } from './messages-DIMwt3Kn.js';
2
- import { F as Fields, I as ImageField } from './content-DKVM1CUQ.js';
3
- import { F as FieldPath } from './parse-content-DF2dL2FG.js';
1
+ export { B as Box, a as EditorKey, E as EditorMessage, I as InlineEditingOutcome, L as LinkBox, M as MarkerBox, b as PROTOCOL_VERSION, c as PageLayout, P as PageMessage, d as Point, S as Shortcut, V as Viewport, r as readEditorMessage, e as readPageMessage, s as shortcutOf } from './messages-BQSbv5YE.js';
2
+ import { F as Fields, I as ImageField } from './content-CD5TXi4R.js';
3
+ import { F as FieldPath } from './parse-content-CVoASb5i.js';
4
4
 
5
5
  declare function carriesFiles(event: DragEvent): boolean;
6
6
 
package/dist/protocol.js CHANGED
@@ -6,8 +6,8 @@ import {
6
6
  readEditorMessage,
7
7
  readPageMessage,
8
8
  shortcutOf
9
- } from "./chunk-HRAL4X6W.js";
10
- import "./chunk-EC42AIQC.js";
9
+ } from "./chunk-LADJH7RU.js";
10
+ import "./chunk-2MRY5Z6U.js";
11
11
  export {
12
12
  PROTOCOL_VERSION,
13
13
  carriesFiles,
package/dist/schema.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { a as ImageValue, L as LinkValue, b as Field, c as CollectionField, F as Fields } from './content-DKVM1CUQ.js';
2
- export { d as Content, C as ContentOf, D as DateField, e as FieldKind, f as FieldValue, G as GroupField, I as ImageField, g as LinkField, N as NumberField, T as TextField, V as ValueOf, h as collection, i as date, j as defaultContent, k as defaultValue, l as defineSchema, m as group, n as image, o as link, p as number, t as text } from './content-DKVM1CUQ.js';
3
- import { F as FieldPath } from './parse-content-DF2dL2FG.js';
4
- export { C as ContentIssue, P as ParsedContent, c as countCharacters, f as formatFieldPath, p as parseContent } from './parse-content-DF2dL2FG.js';
1
+ import { b as ImageValue, L as LinkValue, c as Field, d as CollectionField, F as Fields } from './content-CD5TXi4R.js';
2
+ export { e as ChoiceField, f as ChoiceOption, g as ChoicesField, a as Content, C as ContentOf, D as DateField, h as FieldKind, i as FieldValue, G as GroupField, I as ImageField, j as LinkField, N as NumberField, O as OptionValue, T as TextField, k as TimeField, l as ToggleField, U as UrlField, V as ValueOf, m as choice, n as choices, o as collection, p as date, q as defaultContent, r as defaultValue, s as defineSchema, t as group, u as image, v as link, w as number, x as text, y as time, z as toggle, A as url } from './content-CD5TXi4R.js';
3
+ import { F as FieldPath } from './parse-content-CVoASb5i.js';
4
+ export { C as ContentIssue, P as ParsedContent, c as countCharacters, f as formatFieldPath, p as parseContent } from './parse-content-CVoASb5i.js';
5
5
 
6
6
  declare const FIELD_PATH_ATTRIBUTE = "data-cms-path";
7
7
  interface FieldMarker {
@@ -39,6 +39,13 @@ type FieldTarget = {
39
39
  */
40
40
  declare function resolveFieldPath(schema: Fields, text: string): FieldTarget | undefined;
41
41
 
42
+ /** On the element the site hides and shows itself: `<div data-cms-reveal hidden>`. */
43
+ declare const REVEAL_ATTRIBUTE = "data-cms-reveal";
44
+ /** Set by the bridge on a part it revealed; a site can show it with CSS alone. */
45
+ declare const REVEALED_ATTRIBUTE = "data-cms-revealed";
46
+ /** Sent to a revealed part, and bubbling up from it, for a site that shows it with a script. */
47
+ declare const REVEAL_EVENT = "cms:reveal";
48
+
42
49
  /** Where a link leads, as the editor offers it. */
43
50
  type LinkTarget = {
44
51
  readonly kind: "page";
@@ -65,4 +72,4 @@ declare function linkTargetOf(href: string): LinkTarget | undefined;
65
72
  */
66
73
  declare function hrefOf(target: LinkTarget): string;
67
74
 
68
- export { CollectionField, type ContentPath, FIELD_PATH_ATTRIBUTE, Field, type FieldMarker, FieldPath, type FieldTarget, Fields, ImageValue, type LinkKind, type LinkTarget, LinkValue, editable, hrefOf, linkTargetOf, resolveFieldPath };
75
+ export { CollectionField, type ContentPath, FIELD_PATH_ATTRIBUTE, Field, type FieldMarker, FieldPath, type FieldTarget, Fields, ImageValue, type LinkKind, type LinkTarget, LinkValue, REVEALED_ATTRIBUTE, REVEAL_ATTRIBUTE, REVEAL_EVENT, editable, hrefOf, linkTargetOf, resolveFieldPath };
package/dist/schema.js CHANGED
@@ -1,5 +1,10 @@
1
1
  import {
2
2
  FIELD_PATH_ATTRIBUTE,
3
+ REVEALED_ATTRIBUTE,
4
+ REVEAL_ATTRIBUTE,
5
+ REVEAL_EVENT,
6
+ choice,
7
+ choices,
3
8
  collection,
4
9
  countCharacters,
5
10
  date,
@@ -16,10 +21,18 @@ import {
16
21
  number,
17
22
  parseContent,
18
23
  resolveFieldPath,
19
- text
20
- } from "./chunk-EC42AIQC.js";
24
+ text,
25
+ time,
26
+ toggle,
27
+ url
28
+ } from "./chunk-2MRY5Z6U.js";
21
29
  export {
22
30
  FIELD_PATH_ATTRIBUTE,
31
+ REVEALED_ATTRIBUTE,
32
+ REVEAL_ATTRIBUTE,
33
+ REVEAL_EVENT,
34
+ choice,
35
+ choices,
23
36
  collection,
24
37
  countCharacters,
25
38
  date,
@@ -36,5 +49,8 @@ export {
36
49
  number,
37
50
  parseContent,
38
51
  resolveFieldPath,
39
- text
52
+ text,
53
+ time,
54
+ toggle,
55
+ url
40
56
  };
package/dist/site.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { AstroComponentFactory } from 'astro/runtime/server/index.js';
2
- import { F as Fields, C as ContentOf } from './content-DKVM1CUQ.js';
2
+ import { F as Fields, C as ContentOf } from './content-CD5TXi4R.js';
3
3
 
4
4
  interface SeamletSiteDefinition<F extends Fields> {
5
5
  /** The site's address in the CMS: `/sites/<id>`. */
package/dist/site.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseContent
3
- } from "./chunk-EC42AIQC.js";
3
+ } from "./chunk-2MRY5Z6U.js";
4
4
 
5
5
  // src/site.ts
6
6
  import { cmsOrigin } from "virtual:seamlet/config";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seamlet/astro",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Connects an Astro site to the Seamlet CMS: the schema of its content, the preview the editor frames, and its published content.",
5
5
  "license": "MIT",
6
6
  "type": "module",