@seamlet/astro 0.11.3 → 0.12.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.
@@ -1,28 +1,49 @@
1
+ import {
2
+ uploadedPhotoPath
3
+ } from "../chunk-QCVWHHJC.js";
1
4
  import {
2
5
  isUploadedPhoto
3
- } from "../chunk-P2OQOZBC.js";
6
+ } from "../chunk-QL4K4HR6.js";
4
7
 
5
8
  // src/routes/media.ts
6
9
  import { cmsOrigin } from "virtual:seamlet/config";
7
10
  import site from "virtual:seamlet/site";
8
11
  var NOT_FOUND = 404;
12
+ var BAD_GATEWAY = 502;
13
+ var PHOTO_TIMEOUT_MS = 1e4;
9
14
  var GET = async ({ params }) => {
10
15
  const fileName = params["file"];
11
16
  const isOurs = params["site"] === site.id && isUploadedPhoto(fileName);
12
17
  if (!isOurs) {
13
18
  return new Response(null, { status: NOT_FOUND });
14
19
  }
15
- const response = await fetch(
16
- new URL(`/media/${site.id}/${fileName}`, cmsOrigin)
17
- );
20
+ const url = new URL(uploadedPhotoPath(site.id, fileName), cmsOrigin);
21
+ const response = await photoFromCms(url);
22
+ if (response === void 0) {
23
+ return new Response(null, { status: BAD_GATEWAY });
24
+ }
25
+ if (!response.ok) {
26
+ const status = response.status === NOT_FOUND ? NOT_FOUND : BAD_GATEWAY;
27
+ return new Response(null, { status });
28
+ }
18
29
  return new Response(response.body, {
19
- status: response.status,
20
30
  headers: {
21
31
  "Content-Type": response.headers.get("Content-Type") ?? "image/webp",
22
32
  "Cache-Control": response.headers.get("Cache-Control") ?? "no-cache"
23
33
  }
24
34
  });
25
35
  };
36
+ async function photoFromCms(url) {
37
+ try {
38
+ return await fetch(url, { signal: AbortSignal.timeout(PHOTO_TIMEOUT_MS) });
39
+ } catch (error) {
40
+ console.warn(
41
+ `@seamlet/astro: the photo ${url.href} could not be taken from the CMS`,
42
+ error
43
+ );
44
+ return void 0;
45
+ }
46
+ }
26
47
  export {
27
48
  GET
28
49
  };
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  isOwnPhotoSrc
3
- } from "../chunk-3BS5CE26.js";
3
+ } from "../chunk-W2LIOR2C.js";
4
+ import "../chunk-QCVWHHJC.js";
4
5
 
5
6
  // src/routes/own-photos.ts
6
7
  import site from "virtual:seamlet/site";
package/dist/schema.d.ts CHANGED
@@ -1,8 +1,6 @@
1
- import { b as ImageValue, L as LinkValue, c as Field, d as CollectionField, F as Fields, a as Content, e as RecordFields, C as ContentOf, G as GroupField, f as LinkField, T as TextField, U as UrlField, g as ChoicesField, h as TimeField, I as ImageField } from './content-6k-E7NWf.js';
2
- export { i as ChoiceField, j as ChoiceOption, D as DateField, k as FieldKind, l as FieldValue, m as ImageFocus, N as NumberField, O as OptionValue, n as RecordOf, o as ReferenceField, p as ReferencesField, q as RichBlock, r as RichLine, s as RichSpan, R as RichText, t as RichTextField, u as ToggleField, V as ValueOf, v as asOneParagraph, w as choice, x as choices, y as collection, z as date, A as defaultContent, B as defaultValue, E as defineSchema, H as group, J as image, K as link, M as normalizedLine, P as number, Q as plainTextOf, S as readRichText, W as reference, X as references, Y as richText, Z as richTextOf, _ as text, $ as time, a0 as toggle, a1 as url } from './content-6k-E7NWf.js';
3
- import { F as FieldPath } from './parse-content-C9ldnmvj.js';
4
- export { C as ContentIssue, P as ParsedContent, c as countCharacters, f as formatFieldPath, p as parseContent } from './parse-content-C9ldnmvj.js';
5
- export { a as SITE_DESCRIPTION_PATH, S as SiteDescription, b as SitePageDescription, i as isSearchTextsGroup, r as readSiteDescription } from './site-description-4Jx6Nq6X.js';
1
+ import { d as ImageValue, L as LinkValue, b as FieldPath, e as Field, f as CollectionField, F as Fields, a as Content, g as RecordFields, C as ContentOf, G as GroupField, h as LinkField, T as TextField, U as UrlField, i as ChoicesField, j as TimeField, I as ImageField } from './parse-content-Eg8yRq_t.js';
2
+ export { k as ChoiceField, l as ChoiceOption, c as ContentIssue, D as DateField, m as FieldKind, n as FieldValue, o as ImageFocus, N as NumberField, O as OptionValue, P as ParsedContent, p as RecordOf, q as ReferenceField, r as ReferencesField, s as RichBlock, t as RichLine, u as RichSpan, R as RichText, v as RichTextField, w as ToggleField, V as ValueOf, x as asOneParagraph, y as choice, z as choices, A as collection, B as countCharacters, E as date, H as defaultContent, J as defaultValue, K as defineSchema, M as formatFieldPath, Q as group, S as image, W as linesOf, X as link, Y as normalizedLine, Z as number, _ as parseContent, $ as plainTextOf, a0 as readRichText, a1 as reference, a2 as references, a3 as richText, a4 as richTextOf, a5 as text, a6 as time, a7 as toggle, a8 as url } from './parse-content-Eg8yRq_t.js';
3
+ export { F as FieldsReading, H as HOME_PAGE_PATH, P as PageProblemReason, a as SITE_DESCRIPTION_PATH, b as SchemaProblem, c as SchemaProblemReason, S as SiteDescription, d as SiteDescriptionProblem, e as SiteDescriptionReading, f as SitePageDescription, g as checkFields, h as checkSiteDescription, i as isSearchTextsGroup, r as readFields, j as readSiteDescription } from './site-description-Cg5KNhhG.js';
6
4
 
7
5
  declare const FIELD_PATH_ATTRIBUTE = "data-cms-path";
8
6
  interface FieldMarker {
@@ -91,6 +89,12 @@ type LinkKind = LinkTarget["kind"];
91
89
  * is checked exactly as it is stored and rendered: spaces around it make it no address at all.
92
90
  */
93
91
  declare function linkTargetOf(href: string): LinkTarget | undefined;
92
+ /**
93
+ * The address as it is when a link of a site may lead there, or undefined when it may not: what a
94
+ * site puts into `href` of content it renders, since content that is not saved yet (the editor's
95
+ * draft in the preview) may hold any address.
96
+ */
97
+ declare function safeHref(href: string): string | undefined;
94
98
  /**
95
99
  * The address of a link from what a person typed: a phone or an email without `tel:` or
96
100
  * `mailto:`, a site without `https://`.
@@ -172,8 +176,9 @@ declare function openingHoursField(options?: {
172
176
  note: TextField;
173
177
  }>;
174
178
 
175
- /** The schema the input describes, or undefined when it is not one. */
176
- declare function readFields(input: unknown): Fields | undefined;
179
+ /** An object of named values: not null, not a list. */
180
+ type JsonObject = Readonly<Record<string, unknown>>;
181
+ declare function isJsonObject(value: unknown): value is JsonObject;
177
182
 
178
183
  /** The file at the root of a site's repository. */
179
184
  declare const SITE_CONFIG_FILE = "seamlet.json";
@@ -198,4 +203,4 @@ declare function isSiteId(value: unknown): value is string;
198
203
  /** The config the input gives (the parsed `seamlet.json`); fields it does not know are left out. */
199
204
  declare function readSiteConfig(input: unknown): SiteConfigReading;
200
205
 
201
- export { ChoicesField, CollectionField, type CollectionRecords, Content, ContentOf, type ContentPath, FIELD_PATH_ATTRIBUTE, Field, type FieldMarker, FieldPath, type FieldTarget, Fields, GroupField, ImageField, ImageValue, LinkField, type LinkKind, type LinkTarget, LinkValue, RECORD_ID_KEY, REVEALED_ATTRIBUTE, REVEAL_ATTRIBUTE, REVEAL_EVENT, RecordFields, SITE_CONFIG_FILE, type SiteConfig, type SiteConfigField, type SiteConfigReading, TextField, TimeField, UrlField, WEEKDAYS, collectionAt, contactsFields, editable, findRecord, hrefOf, imagePosition, isRecordId, isSiteId, linkTargetOf, newRecord, openingHoursField, readFields, readSiteConfig, resolveFieldPath, seoFields, withRecordIds };
206
+ export { ChoicesField, CollectionField, type CollectionRecords, Content, ContentOf, type ContentPath, FIELD_PATH_ATTRIBUTE, Field, type FieldMarker, FieldPath, type FieldTarget, Fields, GroupField, ImageField, ImageValue, type JsonObject, LinkField, type LinkKind, type LinkTarget, LinkValue, RECORD_ID_KEY, REVEALED_ATTRIBUTE, REVEAL_ATTRIBUTE, REVEAL_EVENT, RecordFields, SITE_CONFIG_FILE, type SiteConfig, type SiteConfigField, type SiteConfigReading, TextField, TimeField, UrlField, WEEKDAYS, collectionAt, contactsFields, editable, findRecord, hrefOf, imagePosition, isJsonObject, isRecordId, isSiteId, linkTargetOf, newRecord, openingHoursField, readSiteConfig, resolveFieldPath, safeHref, seoFields, withRecordIds };
package/dist/schema.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  FIELD_PATH_ATTRIBUTE,
3
+ HOME_PAGE_PATH,
3
4
  RECORD_ID_KEY,
4
5
  REVEALED_ATTRIBUTE,
5
6
  REVEAL_ATTRIBUTE,
@@ -8,6 +9,8 @@ import {
8
9
  SITE_DESCRIPTION_PATH,
9
10
  WEEKDAYS,
10
11
  asOneParagraph,
12
+ checkFields,
13
+ checkSiteDescription,
11
14
  choice,
12
15
  choices,
13
16
  collection,
@@ -25,9 +28,11 @@ import {
25
28
  hrefOf,
26
29
  image,
27
30
  imagePosition,
31
+ isJsonObject,
28
32
  isRecordId,
29
33
  isSearchTextsGroup,
30
34
  isSiteId,
35
+ linesOf,
31
36
  link,
32
37
  linkTargetOf,
33
38
  newRecord,
@@ -45,15 +50,17 @@ import {
45
50
  resolveFieldPath,
46
51
  richText,
47
52
  richTextOf,
53
+ safeHref,
48
54
  seoFields,
49
55
  text,
50
56
  time,
51
57
  toggle,
52
58
  url,
53
59
  withRecordIds
54
- } from "./chunk-DRDPKFHZ.js";
60
+ } from "./chunk-ZS32CW5F.js";
55
61
  export {
56
62
  FIELD_PATH_ATTRIBUTE,
63
+ HOME_PAGE_PATH,
57
64
  RECORD_ID_KEY,
58
65
  REVEALED_ATTRIBUTE,
59
66
  REVEAL_ATTRIBUTE,
@@ -62,6 +69,8 @@ export {
62
69
  SITE_DESCRIPTION_PATH,
63
70
  WEEKDAYS,
64
71
  asOneParagraph,
72
+ checkFields,
73
+ checkSiteDescription,
65
74
  choice,
66
75
  choices,
67
76
  collection,
@@ -79,9 +88,11 @@ export {
79
88
  hrefOf,
80
89
  image,
81
90
  imagePosition,
91
+ isJsonObject,
82
92
  isRecordId,
83
93
  isSearchTextsGroup,
84
94
  isSiteId,
95
+ linesOf,
85
96
  link,
86
97
  linkTargetOf,
87
98
  newRecord,
@@ -99,6 +110,7 @@ export {
99
110
  resolveFieldPath,
100
111
  richText,
101
112
  richTextOf,
113
+ safeHref,
102
114
  seoFields,
103
115
  text,
104
116
  time,
@@ -0,0 +1,112 @@
1
+ import { F as Fields, a as Content, c as ContentIssue } from './parse-content-Eg8yRq_t.js';
2
+
3
+ /** Why a schema cannot be read. */
4
+ type SchemaProblemReason =
5
+ /** Not an object of fields, or a field that is not an object. */
6
+ "not-fields"
7
+ /** A name that is empty, holds the separator of paths (`.`) or is a number, which a path reads as a record. */
8
+ | "invalid-name" | "invalid-label" | "unknown-kind"
9
+ /** An option of the field (`maxLength`, `inline`…) is there but not of its kind. */
10
+ | "invalid-option" | "invalid-choice-options"
11
+ /** A record has a field named `id`, which is the record's own. */
12
+ | "record-id-field"
13
+ /** `itemTitle` names no text or link field of the record. */
14
+ | "invalid-item-title"
15
+ /** A reference names a collection the schema does not have. */
16
+ | "unknown-collection";
17
+ interface SchemaProblem {
18
+ /** The names of the fields down to the field with the problem; empty for the schema itself. */
19
+ readonly path: readonly string[];
20
+ readonly reason: SchemaProblemReason;
21
+ }
22
+ type FieldsReading = {
23
+ readonly status: "read";
24
+ readonly fields: Fields;
25
+ } | {
26
+ readonly status: "invalid";
27
+ readonly problem: SchemaProblem;
28
+ };
29
+ /** The schema the input describes, or undefined when it is not one. */
30
+ declare function readFields(input: unknown): Fields | undefined;
31
+ /** The schema the input describes, or the first problem that keeps it from being one. */
32
+ declare function checkFields(input: unknown): FieldsReading;
33
+
34
+ /** Where a site hands out its description, on the origin of its preview. */
35
+ declare const SITE_DESCRIPTION_PATH = "/seamlet/site.json";
36
+ /** A page of a site, which the site renders itself. */
37
+ interface SitePageDescription {
38
+ /** The address of the page on the site: `/` for the first page, `/menu` for others. */
39
+ readonly path: string;
40
+ /** How the editor names the page in its list of pages. */
41
+ readonly title: string;
42
+ /**
43
+ * Where the content holds what search engines and link previews show for the page
44
+ * (`seo.menu`): a group of a `title` and a `description` text.
45
+ */
46
+ readonly searchTexts?: string;
47
+ }
48
+ interface SiteDescription {
49
+ /** The address of the site in the CMS (`/sites/<id>`). */
50
+ readonly id: string;
51
+ /** How the CMS names the site. */
52
+ readonly title: string;
53
+ readonly schema: Fields;
54
+ /** The content shown and edited until the first save. */
55
+ readonly initialContent: Content;
56
+ /** The pages of the site; the first is the home page at `/`. */
57
+ readonly pages: readonly [SitePageDescription, ...SitePageDescription[]];
58
+ }
59
+ /** Why a site's description cannot be read: what to fix in the site. */
60
+ type SiteDescriptionProblem = {
61
+ readonly code: "not-a-description";
62
+ } | {
63
+ readonly code: "invalid-id";
64
+ } | {
65
+ readonly code: "invalid-title";
66
+ } | {
67
+ readonly code: "invalid-schema";
68
+ readonly problem: SchemaProblem;
69
+ }
70
+ /** The initial content is not content the CMS could save. */
71
+ | {
72
+ readonly code: "invalid-initial-content";
73
+ readonly issues: readonly ContentIssue[];
74
+ } | {
75
+ readonly code: "no-pages";
76
+ }
77
+ /** The page at this place of the list is not a page, or its path or title is not one. */
78
+ | {
79
+ readonly code: "invalid-page";
80
+ readonly index: number;
81
+ readonly reason: PageProblemReason;
82
+ } | {
83
+ readonly code: "home-page-not-first";
84
+ } | {
85
+ readonly code: "repeated-page-path";
86
+ readonly path: string;
87
+ };
88
+ type PageProblemReason = "not-a-page"
89
+ /** Not `/`, or not lower-case segments without a trailing slash, such as `/menu`. */
90
+ | "invalid-path" | "invalid-title"
91
+ /** Not where the content holds a group of a `title` and a `description` text. */
92
+ | "invalid-search-texts";
93
+ type SiteDescriptionReading = {
94
+ readonly status: "read";
95
+ readonly description: SiteDescription;
96
+ } | {
97
+ readonly status: "invalid";
98
+ readonly problems: readonly SiteDescriptionProblem[];
99
+ };
100
+ /** The first page of every site. */
101
+ declare const HOME_PAGE_PATH = "/";
102
+ /**
103
+ * The description the input gives, or undefined when it is not a whole and sound one: a known
104
+ * schema, an initial content with nothing to fix, the home page first, each page once.
105
+ */
106
+ declare function readSiteDescription(input: unknown): SiteDescription | undefined;
107
+ /** The description the input gives, or every problem that keeps it from being whole and sound. */
108
+ declare function checkSiteDescription(input: unknown): SiteDescriptionReading;
109
+ /** Whether the path names a group of a `title` and a `description` text. */
110
+ declare function isSearchTextsGroup(schema: Fields, path: string): boolean;
111
+
112
+ export { type FieldsReading as F, HOME_PAGE_PATH as H, type PageProblemReason as P, type SiteDescription as S, SITE_DESCRIPTION_PATH as a, type SchemaProblem as b, type SchemaProblemReason as c, type SiteDescriptionProblem as d, type SiteDescriptionReading as e, type SitePageDescription as f, checkFields as g, checkSiteDescription as h, isSearchTextsGroup as i, readSiteDescription as j, readFields as r };
package/dist/site.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { ImageMetadata } from 'astro';
2
2
  import { AstroComponentFactory } from 'astro/runtime/server/index.js';
3
- import { F as Fields, C as ContentOf } from './content-6k-E7NWf.js';
4
- import { S as SiteDescription } from './site-description-4Jx6Nq6X.js';
3
+ import { F as Fields, C as ContentOf } from './parse-content-Eg8yRq_t.js';
4
+ import { S as SiteDescription } from './site-description-Cg5KNhhG.js';
5
5
 
6
6
  /** A page of the site: how the editor names it, and the component that renders it. */
7
7
  interface SeamletPage {
package/dist/site.js CHANGED
@@ -1,13 +1,34 @@
1
1
  import {
2
+ HOME_PAGE_PATH,
3
+ checkSiteDescription,
4
+ formatFieldPath,
2
5
  parseContent
3
- } from "./chunk-DRDPKFHZ.js";
6
+ } from "./chunk-ZS32CW5F.js";
7
+ import {
8
+ SITE_MODULE_FILE
9
+ } from "./chunk-QCVWHHJC.js";
4
10
 
5
11
  // src/site.ts
6
12
  import { cmsOrigin, siteId, siteTitle } from "virtual:seamlet/config";
7
13
 
8
14
  // src/published-answers.ts
9
15
  var NOT_FOUND = 404;
16
+ var PUBLISHED_CONTENT_TIMEOUT_MS = 3e4;
10
17
  var UNKNOWN_SITE = "unknown-site";
18
+ async function fetchPublishedAnswer(url) {
19
+ let response;
20
+ try {
21
+ response = await fetch(url, {
22
+ signal: AbortSignal.timeout(PUBLISHED_CONTENT_TIMEOUT_MS)
23
+ });
24
+ } catch (error) {
25
+ throw new Error(
26
+ `Loading the published content from ${url.href} failed: the CMS did not answer`,
27
+ { cause: error }
28
+ );
29
+ }
30
+ return readPublishedAnswer(response, url);
31
+ }
11
32
  async function readPublishedAnswer(response, url) {
12
33
  if (response.ok)
13
34
  return { status: "published", content: await response.json() };
@@ -35,21 +56,74 @@ function describeSite(identity, definition) {
35
56
  ...page.searchTexts === void 0 ? {} : { searchTexts: page.searchTexts }
36
57
  })
37
58
  );
38
- const [home, ...rest] = pages;
39
- if (home === void 0) {
40
- throw new Error(`The site "${identity.id}" has no pages`);
41
- }
42
- return {
59
+ const description = {
43
60
  id: identity.id,
44
61
  title: identity.title,
45
62
  schema: definition.schema,
46
63
  initialContent: definition.initialContent,
47
- pages: [home, ...rest]
64
+ pages
48
65
  };
66
+ const asSent = JSON.parse(JSON.stringify(description));
67
+ const reading = checkSiteDescription(asSent);
68
+ if (reading.status === "invalid") {
69
+ throw new Error(refusalMessage(identity.id, reading.problems));
70
+ }
71
+ return reading.description;
72
+ }
73
+ function refusalMessage(id, problems) {
74
+ const lines = problems.map((problem) => ` - ${problemText(problem)}`);
75
+ return [
76
+ `@seamlet/astro: the CMS would refuse the site "${id}" (${SITE_MODULE_FILE}):`,
77
+ ...lines
78
+ ].join("\n");
79
+ }
80
+ function problemText(problem) {
81
+ switch (problem.code) {
82
+ case "not-a-description":
83
+ return "the site describes nothing";
84
+ case "invalid-id":
85
+ return "its id in seamlet.json is not one";
86
+ case "invalid-title":
87
+ return "its title in seamlet.json is empty";
88
+ case "invalid-schema": {
89
+ const path = formatFieldPath(problem.problem.path);
90
+ const where = path === "" ? "the schema" : `the field "${path}"`;
91
+ return `${where}: ${SCHEMA_PROBLEM_TEXTS[problem.problem.reason]}`;
92
+ }
93
+ case "invalid-initial-content":
94
+ return `the initial content: ${problem.issues.map(issueText).join("; ")}`;
95
+ case "no-pages":
96
+ return "it has no pages";
97
+ case "invalid-page":
98
+ return `page ${String(problem.index + 1)} of pages: ${PAGE_PROBLEM_TEXTS[problem.reason]}`;
99
+ case "home-page-not-first":
100
+ return 'the first of the pages is not the home page "/"';
101
+ case "repeated-page-path":
102
+ return `the page "${problem.path}" is named twice`;
103
+ }
104
+ }
105
+ var SCHEMA_PROBLEM_TEXTS = {
106
+ "not-fields": "not fields of the schema language",
107
+ "invalid-name": "a field's name must not be empty, hold a dot or be a number",
108
+ "invalid-label": "its label is not a text",
109
+ "unknown-kind": "a kind of field the schema language does not have",
110
+ "invalid-option": "an option of the field is not of its kind",
111
+ "invalid-choice-options": "its options are not a list of { value, label }",
112
+ "record-id-field": "a record has its own id: name the field otherwise",
113
+ "invalid-item-title": "itemTitle names no text or link field of the record",
114
+ "unknown-collection": "it refers to a collection the schema does not have"
115
+ };
116
+ var PAGE_PROBLEM_TEXTS = {
117
+ "not-a-page": "not a page",
118
+ "invalid-path": 'its address is not "/" or lower-case segments without a trailing slash, such as "/menu"',
119
+ "invalid-title": "its title is empty",
120
+ "invalid-search-texts": "searchTexts names no group of a title and a description text"
121
+ };
122
+ function issueText(issue) {
123
+ return `"${formatFieldPath(issue.path)}" is ${issue.code}`;
49
124
  }
50
125
 
51
126
  // src/site.ts
52
- var HOME_PAGE_PATH = "/";
53
127
  function defineSeamletSite(definition) {
54
128
  const pages = new Map(Object.entries(definition.pages));
55
129
  return {
@@ -63,7 +137,7 @@ function defineSeamletSite(definition) {
63
137
  }
64
138
  async function loadPublishedContent(schema) {
65
139
  const url = new URL(`/api/sites/${siteId}/published`, cmsOrigin);
66
- const answer = await readPublishedAnswer(await fetch(url), url);
140
+ const answer = await fetchPublishedAnswer(url);
67
141
  const content = answer.status === "published" ? answer.content : await initialContentOfSite();
68
142
  return parseContent(schema, content).content;
69
143
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seamlet/astro",
3
- "version": "0.11.3",
3
+ "version": "0.12.1",
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",
@@ -4,29 +4,26 @@
4
4
  // content; POST shows the content in its JSON body, never saved: the bridge renders the editor's
5
5
  // draft this way.
6
6
  import { parseContent } from "@seamlet/astro/schema";
7
- import { readDraft } from "@seamlet/astro/preview-requests";
7
+ import { previewHeadersOf, readDraft } from "@seamlet/astro/preview-requests";
8
8
  import { cmsOrigin } from "virtual:seamlet/config";
9
9
  import site from "virtual:seamlet/site";
10
10
 
11
11
  const NOT_FOUND = 404;
12
12
 
13
+ const headers = previewHeadersOf(cmsOrigin);
14
+ for (const [name, value] of Object.entries(headers)) {
15
+ Astro.response.headers.set(name, value);
16
+ }
17
+
13
18
  const Page = site.pageAt(Astro.params.page);
14
19
  if (Page === undefined) {
15
- return new Response(null, { status: NOT_FOUND });
20
+ return new Response(null, { status: NOT_FOUND, headers });
16
21
  }
17
22
 
18
- // Only the CMS's editor frames the preview, and nobody indexes or keeps it.
19
- Astro.response.headers.set(
20
- "Content-Security-Policy",
21
- `frame-ancestors ${cmsOrigin}`,
22
- );
23
- Astro.response.headers.set("X-Robots-Tag", "noindex");
24
- Astro.response.headers.set("Cache-Control", "no-store");
25
-
26
23
  const reading =
27
24
  Astro.request.method === "POST" ? await readDraft(Astro.request) : undefined;
28
25
  if (reading !== undefined && !reading.isRead) {
29
- return reading.response;
26
+ return new Response(null, { status: reading.response.status, headers });
30
27
  }
31
28
  const content =
32
29
  reading === undefined
@@ -1,69 +0,0 @@
1
- import { F as Fields, C as ContentOf } from './content-6k-E7NWf.js';
2
-
3
- /** Where a value lives in the content: field names and, inside a collection, record indexes. */
4
- type FieldPath = readonly (string | number)[];
5
- type ContentIssue = {
6
- readonly path: FieldPath;
7
- readonly code: "invalid-type";
8
- } | {
9
- readonly path: FieldPath;
10
- readonly code: "invalid-date";
11
- } | {
12
- readonly path: FieldPath;
13
- readonly code: "invalid-time";
14
- } | {
15
- readonly path: FieldPath;
16
- readonly code: "too-long";
17
- readonly maxLength: number;
18
- } | {
19
- readonly path: FieldPath;
20
- readonly code: "below-min";
21
- readonly min: number;
22
- } | {
23
- readonly path: FieldPath;
24
- readonly code: "above-max";
25
- readonly max: number;
26
- } | {
27
- readonly path: FieldPath;
28
- readonly code: "missing-image-src";
29
- } | {
30
- readonly path: FieldPath;
31
- readonly code: "missing-link-href";
32
- } | {
33
- readonly path: FieldPath;
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;
42
- } | {
43
- readonly path: FieldPath;
44
- readonly code: "missing-record";
45
- readonly id: string;
46
- };
47
- interface ParsedContent<C> {
48
- readonly content: C;
49
- readonly issues: readonly ContentIssue[];
50
- }
51
- declare function formatFieldPath(path: FieldPath): string;
52
- /**
53
- * The length that `maxLength` limits: characters as people see them (grapheme clusters), so
54
- * "й" and "👍🏽" are one character each, whatever number of code units they take.
55
- */
56
- declare function countCharacters(text: string): number;
57
- /**
58
- * Brings any input to the shape of the schema, so the result is always content of that schema:
59
- * - a field missing from the input (the schema grew after the content was saved) gets its default;
60
- * - a value of the wrong type or format gets the default too, and an issue;
61
- * - a value of the right type that breaks a rule of its field (too long, below the minimum) is
62
- * kept, with an issue: nothing a person typed is lost;
63
- * - keys the schema does not know are dropped; a record keeps its id (see withRecordIds);
64
- * - a reference to a record its collection does not have (removed since) is kept, with an issue.
65
- * Content with issues is valid to show and edit, but not to save.
66
- */
67
- declare function parseContent<F extends Fields>(schema: F, input: unknown): ParsedContent<ContentOf<F>>;
68
-
69
- export { type ContentIssue as C, type FieldPath as F, type ParsedContent as P, countCharacters as c, formatFieldPath as f, parseContent as p };
@@ -1,36 +0,0 @@
1
- import { F as Fields, a as Content } from './content-6k-E7NWf.js';
2
-
3
- /** Where a site hands out its description, on the origin of its preview. */
4
- declare const SITE_DESCRIPTION_PATH = "/seamlet/site.json";
5
- /** A page of a site, which the site renders itself. */
6
- interface SitePageDescription {
7
- /** The address of the page on the site: `/` for the first page, `/menu` for others. */
8
- readonly path: string;
9
- /** How the editor names the page in its list of pages. */
10
- readonly title: string;
11
- /**
12
- * Where the content holds what search engines and link previews show for the page
13
- * (`seo.menu`): a group of a `title` and a `description` text.
14
- */
15
- readonly searchTexts?: string;
16
- }
17
- interface SiteDescription {
18
- /** The address of the site in the CMS (`/sites/<id>`). */
19
- readonly id: string;
20
- /** How the CMS names the site. */
21
- readonly title: string;
22
- readonly schema: Fields;
23
- /** The content shown and edited until the first save. */
24
- readonly initialContent: Content;
25
- /** The pages of the site; the first is the home page at `/`. */
26
- readonly pages: readonly [SitePageDescription, ...SitePageDescription[]];
27
- }
28
- /**
29
- * The description the input gives, or undefined when it is not a whole and sound one: a known
30
- * schema, an initial content with nothing to fix, the home page first, each page once.
31
- */
32
- declare function readSiteDescription(input: unknown): SiteDescription | undefined;
33
- /** Whether the path names a group of a `title` and a `description` text. */
34
- declare function isSearchTextsGroup(schema: Fields, path: string): boolean;
35
-
36
- export { type SiteDescription as S, SITE_DESCRIPTION_PATH as a, type SitePageDescription as b, isSearchTextsGroup as i, readSiteDescription as r };