@seamlet/astro 1.0.0 → 1.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/CHANGELOG.md +14 -0
- package/README.md +113 -2
- package/dist/bridge.d.ts +2 -2
- package/dist/bridge.js +3 -2
- package/dist/chunk-HFPZVDST.js +78 -0
- package/dist/{chunk-QL4K4HR6.js → chunk-OCA2WYK3.js} +41 -0
- package/dist/{chunk-ZS32CW5F.js → chunk-UL2C2IB2.js} +392 -6
- package/dist/{chunk-SSECNMAD.js → chunk-Y4R4YV5O.js} +1 -1
- package/dist/cli.js +4 -4
- package/dist/index.js +8 -7
- package/dist/{messages-CvZhLRSE.d.ts → messages-C2OjqFmy.d.ts} +1 -1
- package/dist/{parse-content-Eg8yRq_t.d.ts → parse-content-Bnn6Wa-l.d.ts} +25 -4
- package/dist/preview-requests.d.ts +15 -1
- package/dist/preview-requests.js +7 -1
- package/dist/protocol.d.ts +3 -3
- package/dist/protocol.js +2 -2
- package/dist/routes/media.js +3 -1
- package/dist/schema.d.ts +53 -22
- package/dist/schema.js +35 -1
- package/dist/site-description-BnHrODQX.d.ts +258 -0
- package/dist/site.d.ts +54 -4
- package/dist/site.js +55 -9
- package/package.json +1 -1
- package/routes/preview.astro +23 -9
- package/dist/site-description-Cg5KNhhG.d.ts +0 -112
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.2.0
|
|
4
|
+
|
|
5
|
+
- `productFields()`: the fields of a product of a catalog, for the record of a collection, with its address, photos, a short and a whole description, the price with a word about it, characteristics as pairs of a name and a value, the switch that hides it and what search engines show.
|
|
6
|
+
- `recordPages(…, { shows })` leaves the pages of some records off the site: `site.pageOf` and `site.staticPaths` leave them out, `pathOf` gives them no address, and the preview shows them (`site.previewPageOf`), so the owner prepares a hidden record in the editor.
|
|
7
|
+
|
|
8
|
+
## 1.1.0
|
|
9
|
+
|
|
10
|
+
- Pages of records: `recordPages(schema, { collection, path: "/menu/[record]" })` gives every record of a collection a page, rendered by one component with `content`, `record` and `index`; `defineSeamletSite` takes them as `recordPages`, the site's description tells the CMS about them, and the preview shows them, a record the editor has just added too.
|
|
11
|
+
- `slug({ label })`: the address of a record's page, checked as an address (`invalid-slug`) and as one of a kind in its collection (`taken-slug`); `slugOf` spells it from Russian words. A record without a slug has its page at its id.
|
|
12
|
+
- `site.pageOf(rest, content)` and `site.staticPaths(content)` render every page of a site from one `[...page]` route, the pages of records too. `site.pageAt` renders the site's own pages only, and is deprecated.
|
|
13
|
+
- The preview answers an address of no page at once, without asking the CMS or reading the draft.
|
|
14
|
+
|
|
15
|
+
A site with a `slug` or pages of records needs a CMS that reads descriptions of 1.1 (cms.seamlet.ru from its release 1.1.0): an older one refuses the kind `slug`. Code with an exhaustive `switch` over `Field`, `ContentIssue`, `SchemaProblemReason` or `SiteDescriptionProblem` gets their new members to handle.
|
|
16
|
+
|
|
3
17
|
## 1.0.0
|
|
4
18
|
|
|
5
19
|
The first stable version. From here the package follows semver: what a site writes against it keeps building until the next major version.
|
package/README.md
CHANGED
|
@@ -67,7 +67,7 @@ npx astro add @seamlet/astro node # the integration, and an ad
|
|
|
67
67
|
<h1 {...edit("title")}>{content.title}</h1>
|
|
68
68
|
```
|
|
69
69
|
|
|
70
|
-
4. Describe the site for the CMS in `src/seamlet.ts`: its schema, the content it starts with, and its pages, each with the name the editor gives it and the component that renders it (it takes the content as its `content` prop). The home page `/` comes first; `searchTexts` names where the content holds a page's title and description for search engines, a group of a `title` and a `description` text (`seoFields`).
|
|
70
|
+
4. Describe the site for the CMS in `src/seamlet.ts`: its schema, the content it starts with, and its pages, each with the name the editor gives it and the component that renders it (it takes the content as its `content` prop), and the pages of records if it has them (see «Pages of records»). The home page `/` comes first; `searchTexts` names where the content holds a page's title and description for search engines, a group of a `title` and a `description` text (`seoFields`).
|
|
71
71
|
|
|
72
72
|
```ts
|
|
73
73
|
// src/seamlet.ts
|
|
@@ -98,6 +98,23 @@ npx astro add @seamlet/astro node # the integration, and an ad
|
|
|
98
98
|
});
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
+
A site renders all its pages, the pages of records too (below), from one route `src/pages/[...page].astro`: `site.staticPaths(content)` lists every page for a static build, and `site.pageOf(rest, content)` finds the one asked for on request; each gives the component and the props to render it with.
|
|
102
|
+
|
|
103
|
+
```astro
|
|
104
|
+
---
|
|
105
|
+
// src/pages/[...page].astro, built as static pages
|
|
106
|
+
import site from "../seamlet";
|
|
107
|
+
|
|
108
|
+
export async function getStaticPaths() {
|
|
109
|
+
return site.staticPaths(await site.loadPublishedContent());
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const { component: Page, props } = Astro.props;
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
<Page {...props} />
|
|
116
|
+
```
|
|
117
|
+
|
|
101
118
|
The site's pages take their published content with `site.loadPublishedContent()`: at build time for a static site, on request otherwise. Until the first publication, and while the CMS does not know the site yet (a site connected from its repository is built before that), it is the site's initial content; any other refusal of the CMS stops the build. A module that needs the content without importing every page takes it with `loadPublishedContent(schema)` of `@seamlet/astro/site`.
|
|
102
119
|
|
|
103
120
|
## Fields
|
|
@@ -109,6 +126,7 @@ The site's pages take their published content with `site.loadPublishedContent()`
|
|
|
109
126
|
| `number({ label, min?, max? })` | a number | `null` |
|
|
110
127
|
| `date({ label })` | `YYYY-MM-DD` | `null` |
|
|
111
128
|
| `time({ label })` | `HH:MM`, 00:00 to 23:59 | `null` |
|
|
129
|
+
| `slug({ label })` | the address of a record's page, `latte` in `/menu/latte` (below) | `""` |
|
|
112
130
|
| `image({ label })` | `{ src, alt }` | `null` |
|
|
113
131
|
| `link({ label })` | `{ text, href }`: a page of the site, another site, a phone or an email | `null` |
|
|
114
132
|
| `url({ label })` | a web address without a text: a booking widget, a map | `null` |
|
|
@@ -212,6 +230,33 @@ Sets of fields almost every site has, edited the same way everywhere; they are p
|
|
|
212
230
|
|
|
213
231
|
Each takes `{ label }` to name it otherwise.
|
|
214
232
|
|
|
233
|
+
`productFields()` is the fields of a product of a catalog, for the record of a collection: `name`, `slug` (the address of its page), `photos` (records of one `photo`, the first is the main one), `summary` (one line for a card), `description`, `price` with `priceNote` («от», «за м²»), `characteristics` (records of a `name` and a `value`, so the owner adds a property without code: «Площадь» — «36 м²»), `isHidden` and `seo`. A site adds its own fields beside them, a category for one:
|
|
234
|
+
|
|
235
|
+
```ts
|
|
236
|
+
export const schema = defineSchema({
|
|
237
|
+
categories: collection({
|
|
238
|
+
label: "Категории",
|
|
239
|
+
itemTitle: "name",
|
|
240
|
+
item: { name: text({ label: "Название" }) },
|
|
241
|
+
}),
|
|
242
|
+
products: collection({
|
|
243
|
+
label: "Товары",
|
|
244
|
+
itemTitle: "name",
|
|
245
|
+
item: {
|
|
246
|
+
...productFields(),
|
|
247
|
+
category: reference({ label: "Категория", collection: "categories" }),
|
|
248
|
+
},
|
|
249
|
+
}),
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
export const productPages = recordPages(schema, {
|
|
253
|
+
collection: "products",
|
|
254
|
+
path: "/catalog/[record]",
|
|
255
|
+
searchTexts: "seo",
|
|
256
|
+
shows: (product) => !product.isHidden,
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
215
260
|
## Records and references
|
|
216
261
|
|
|
217
262
|
Every record of a collection has an `id` of its own, which never changes when records move, so a record has no field named `id`. The CMS gives ids to records that have none (`withRecordIds`), so a site's initial content may leave them out, unless something refers to those records.
|
|
@@ -239,6 +284,72 @@ const schema = defineSchema({
|
|
|
239
284
|
|
|
240
285
|
A reference to a record removed since is kept, with the issue `missing-record`: the content is not saved until the person picks another one or clears it.
|
|
241
286
|
|
|
287
|
+
## Pages of records
|
|
288
|
+
|
|
289
|
+
A collection can have a page for each of its records: a product, a dish, an article. The site writes one component, and the owner who adds a record in the CMS gets its page, which the CMS's editor opens and edits like any other page (a CMS that reads descriptions of version 1.1, as cms.seamlet.ru does from its release 1.1.0). `recordPages` names the collection and the address of its pages, `[record]` standing for the record:
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
// src/schema.ts
|
|
293
|
+
import { recordPages, slug } from "@seamlet/astro/schema";
|
|
294
|
+
|
|
295
|
+
export const schema = defineSchema({
|
|
296
|
+
menu: collection({
|
|
297
|
+
label: "Меню",
|
|
298
|
+
itemTitle: "name",
|
|
299
|
+
item: {
|
|
300
|
+
name: text({ label: "Название" }),
|
|
301
|
+
price: number({ label: "Цена, ₽", min: 0 }),
|
|
302
|
+
slug: slug({ label: "Адрес страницы" }),
|
|
303
|
+
},
|
|
304
|
+
}),
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
export const drinkPages = recordPages(schema, {
|
|
308
|
+
collection: "menu",
|
|
309
|
+
path: "/menu/[record]",
|
|
310
|
+
});
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
```ts
|
|
314
|
+
// src/seamlet.ts
|
|
315
|
+
export default defineSeamletSite({
|
|
316
|
+
schema,
|
|
317
|
+
initialContent,
|
|
318
|
+
pages: {
|
|
319
|
+
"/": { title: "Главная", component: HomePage },
|
|
320
|
+
"/menu": { title: "Меню", component: MenuPage },
|
|
321
|
+
},
|
|
322
|
+
recordPages: [{ pages: drinkPages, component: DrinkPage }],
|
|
323
|
+
});
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
The component takes the content, the record and its place in its collection as `content`, `record` and `index`, so its markers point at the record's fields:
|
|
327
|
+
|
|
328
|
+
```astro
|
|
329
|
+
---
|
|
330
|
+
// src/components/DrinkPage.astro
|
|
331
|
+
import { editable } from "@seamlet/astro/schema";
|
|
332
|
+
import type { Content } from "../schema";
|
|
333
|
+
|
|
334
|
+
interface Props {
|
|
335
|
+
content: Content;
|
|
336
|
+
record: Content["menu"][number];
|
|
337
|
+
index: number;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const { record, index } = Astro.props;
|
|
341
|
+
const edit = editable<Content>();
|
|
342
|
+
---
|
|
343
|
+
|
|
344
|
+
<h1 {...edit("menu", index, "name")}>{record.name}</h1>
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
`shows` leaves the pages of some records off the site, a hidden product's for one: `site.pageOf` and `site.staticPaths` leave them out and `pathOf` gives them no address, while the preview shows them, so the owner prepares a product before showing it.
|
|
348
|
+
|
|
349
|
+
`drinkPages.pathOf(drink)` gives the address of a record's page for a link to it: `/menu/latte`. The address is the record's `slug` when it has one, a field the owner fills in (lower-case Latin letters, digits and hyphens; the CMS spells it from the record's name), and its id otherwise, so a record has its page from the moment it is added, and keeps it when it is renamed or moved. A slug another record of the collection has, as its slug or as its id, is the issue `taken-slug`; a slug with other characters, `invalid-slug`. A collection is `catalog.products` through groups, its pages have one segment more than a page of the site (`/catalog/[record]`, never `/[record]`), a record has one page, and no page of the site may stand among the pages of records (`/menu/specials` beside `/menu/[record]`). `searchTexts` names a group of the record with a `title` and a `description` text for its page.
|
|
350
|
+
|
|
351
|
+
Every record of the initial content needs an address of its own, a slug or an id, since its page is built before the CMS saves anything. The preview shows a page of a record the owner has just added, so the editor opens it before it is saved or published: under the pages of records, an address that could be an id and names no record shows a blank record, which the editor's draft replaces at once.
|
|
352
|
+
|
|
242
353
|
## What the page hides
|
|
243
354
|
|
|
244
355
|
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. Neither do tabs, filters and toggles made of radio buttons and checkboxes and shown with CSS: a click on one or its label flips it in the editor, and the page keeps it flipped after every edit. 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:
|
|
@@ -253,7 +364,7 @@ What was opened stays open while the page is rendered anew after every edit: a p
|
|
|
253
364
|
|
|
254
365
|
## What the integration adds
|
|
255
366
|
|
|
256
|
-
- `/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; its refusals (
|
|
367
|
+
- `/preview` and `/preview/<page>`: the preview the CMS's editor frames, the pages of records too (at their address or at the record's id, a page the site does not show too). `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; its refusals (an address of no page, a record a draft does not have, a draft that is not JSON or is over 1 MB) say the same.
|
|
257
368
|
- The bridge on the preview: it tells the editor where the marked elements are and what a person does on the page, and types texts in place, formatted ones too.
|
|
258
369
|
- `/seamlet/site.json`: what the site tells the CMS about itself (`SITE_DESCRIPTION_PATH`): its id and name from `seamlet.json`, its schema, the content it starts with and its pages. The CMS reads it with `readSiteDescription` whenever the site is opened in its editor.
|
|
259
370
|
- `/seamlet-photos/<address>`: the site's own photo at an address its content gives it, found by `photos` of `defineSeamletSite`, for the CMS's editor.
|
package/dist/bridge.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as Fields } from './parse-content-
|
|
2
|
-
import { P as PageMessage, E as EditorMessage } from './messages-
|
|
1
|
+
import { F as Fields } from './parse-content-Bnn6Wa-l.js';
|
|
2
|
+
import { P as PageMessage, E as EditorMessage } from './messages-C2OjqFmy.js';
|
|
3
3
|
|
|
4
4
|
interface BridgeOptions {
|
|
5
5
|
/** The origin of the CMS whose editor may frame the page, such as `https://cms.example.com`. */
|
package/dist/bridge.js
CHANGED
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
shortcutOf,
|
|
19
19
|
toggleList,
|
|
20
20
|
toggleMark
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-Y4R4YV5O.js";
|
|
22
22
|
import {
|
|
23
23
|
FIELD_PATH_ATTRIBUTE,
|
|
24
24
|
REVEALED_ATTRIBUTE,
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
readRichText,
|
|
29
29
|
resolveFieldPath,
|
|
30
30
|
safeHref
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-UL2C2IB2.js";
|
|
32
32
|
|
|
33
33
|
// src/bridge/typed-fields.ts
|
|
34
34
|
function typedKindAt(schema, path) {
|
|
@@ -42,6 +42,7 @@ function typedKindAt(schema, path) {
|
|
|
42
42
|
case "number":
|
|
43
43
|
case "date":
|
|
44
44
|
case "time":
|
|
45
|
+
case "slug":
|
|
45
46
|
case "image":
|
|
46
47
|
case "link":
|
|
47
48
|
case "url":
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HOME_PAGE_PATH,
|
|
3
|
+
PAGE_PATH_SEPARATOR,
|
|
4
|
+
collectionAt,
|
|
5
|
+
recordAddressOf,
|
|
6
|
+
recordAt,
|
|
7
|
+
recordPageAt,
|
|
8
|
+
recordPagePath
|
|
9
|
+
} from "./chunk-UL2C2IB2.js";
|
|
10
|
+
|
|
11
|
+
// src/site-pages.ts
|
|
12
|
+
function pagePathOf(rest) {
|
|
13
|
+
return rest === void 0 ? HOME_PAGE_PATH : `${PAGE_PATH_SEPARATOR}${rest}`;
|
|
14
|
+
}
|
|
15
|
+
function pageParameterOf(path) {
|
|
16
|
+
return path === HOME_PAGE_PATH ? void 0 : path.slice(PAGE_PATH_SEPARATOR.length);
|
|
17
|
+
}
|
|
18
|
+
var SHOWN_ON_SITE = (recordPage, record) => recordPage.pages.isShown?.(record) ?? true;
|
|
19
|
+
var EVERY_RECORD = () => true;
|
|
20
|
+
function renderedPageAt(site, path, content) {
|
|
21
|
+
return ownPageAt(site, path, content) ?? renderedRecordPageAt(site, { path, content, isShown: SHOWN_ON_SITE });
|
|
22
|
+
}
|
|
23
|
+
function previewedPageAt(site, path, content) {
|
|
24
|
+
return ownPageAt(site, path, content) ?? renderedRecordPageAt(site, { path, content, isShown: EVERY_RECORD });
|
|
25
|
+
}
|
|
26
|
+
function staticPagesOf(site, content) {
|
|
27
|
+
const ownPages = Object.entries(site.pages).map(
|
|
28
|
+
([path, page]) => staticPage(path, { component: page.component, props: { content } })
|
|
29
|
+
);
|
|
30
|
+
const recordPages = site.recordPages.flatMap(
|
|
31
|
+
(recordPage) => staticRecordPagesOf(site.schema, recordPage, content)
|
|
32
|
+
);
|
|
33
|
+
return [...ownPages, ...recordPages];
|
|
34
|
+
}
|
|
35
|
+
function ownPageAt(site, path, content) {
|
|
36
|
+
const page = Object.hasOwn(site.pages, path) ? site.pages[path] : void 0;
|
|
37
|
+
return page === void 0 ? void 0 : { component: page.component, props: { content } };
|
|
38
|
+
}
|
|
39
|
+
function renderedRecordPageAt(site, request) {
|
|
40
|
+
const { path, content, isShown } = request;
|
|
41
|
+
const described = site.recordPages.map((recordPage2) => ({
|
|
42
|
+
...recordPage2.pages,
|
|
43
|
+
recordPage: recordPage2
|
|
44
|
+
}));
|
|
45
|
+
const found = recordPageAt(described, path);
|
|
46
|
+
if (found === void 0) return void 0;
|
|
47
|
+
const records = collectionAt(site.schema, content, found.page.collection);
|
|
48
|
+
if (records === void 0) return void 0;
|
|
49
|
+
const placed = recordAt(records.collection, records.records, found.address);
|
|
50
|
+
const { recordPage } = found.page;
|
|
51
|
+
if (placed === void 0 || !isShown(recordPage, placed.record)) {
|
|
52
|
+
return void 0;
|
|
53
|
+
}
|
|
54
|
+
const props = { content, record: placed.record, index: placed.index };
|
|
55
|
+
return { component: recordPage.component, props };
|
|
56
|
+
}
|
|
57
|
+
function staticRecordPagesOf(schema, recordPage, content) {
|
|
58
|
+
const found = collectionAt(schema, content, recordPage.pages.collection);
|
|
59
|
+
if (found === void 0) return [];
|
|
60
|
+
return found.records.flatMap((record, index) => {
|
|
61
|
+
const address = recordAddressOf(found.collection, record);
|
|
62
|
+
const hasPage = address !== void 0 && SHOWN_ON_SITE(recordPage, record);
|
|
63
|
+
if (!hasPage) return [];
|
|
64
|
+
const path = recordPagePath(recordPage.pages.path, address);
|
|
65
|
+
const props = { content, record, index };
|
|
66
|
+
return [staticPage(path, { component: recordPage.component, props })];
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function staticPage(path, page) {
|
|
70
|
+
return { params: { page: pageParameterOf(path) }, props: page };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export {
|
|
74
|
+
pagePathOf,
|
|
75
|
+
renderedPageAt,
|
|
76
|
+
previewedPageAt,
|
|
77
|
+
staticPagesOf
|
|
78
|
+
};
|
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
pagePathOf
|
|
3
|
+
} from "./chunk-HFPZVDST.js";
|
|
4
|
+
import {
|
|
5
|
+
FIELD_PATH_SEPARATOR,
|
|
6
|
+
collectionAt,
|
|
7
|
+
isContentObject,
|
|
8
|
+
isRecordId,
|
|
9
|
+
newRecord,
|
|
10
|
+
recordAt,
|
|
11
|
+
recordPageAt
|
|
12
|
+
} from "./chunk-UL2C2IB2.js";
|
|
13
|
+
|
|
1
14
|
// src/preview-requests.ts
|
|
2
15
|
var MAX_DRAFT_BYTES = 1024 * 1024;
|
|
3
16
|
var BAD_REQUEST = 400;
|
|
@@ -22,6 +35,25 @@ async function readDraft(request) {
|
|
|
22
35
|
return refused(BAD_REQUEST);
|
|
23
36
|
}
|
|
24
37
|
}
|
|
38
|
+
function isPageAddress(description, rest) {
|
|
39
|
+
const path = pagePathOf(rest);
|
|
40
|
+
const isOwnPage = description.pages.some((page) => page.path === path);
|
|
41
|
+
const recordPage = recordPageAt(description.recordPages ?? [], path);
|
|
42
|
+
return isOwnPage || recordPage !== void 0;
|
|
43
|
+
}
|
|
44
|
+
function previewContentAt(description, content, rest) {
|
|
45
|
+
const recordPages = description.recordPages ?? [];
|
|
46
|
+
const found = recordPageAt(recordPages, pagePathOf(rest));
|
|
47
|
+
if (found === void 0 || !isRecordId(found.address)) return content;
|
|
48
|
+
const { collection: path } = found.page;
|
|
49
|
+
const records = collectionAt(description.schema, content, path);
|
|
50
|
+
if (records === void 0) return content;
|
|
51
|
+
const isPublished = recordAt(records.collection, records.records, found.address) !== void 0;
|
|
52
|
+
if (isPublished) return content;
|
|
53
|
+
const blank = newRecord(records.collection, found.address);
|
|
54
|
+
const names = path.split(FIELD_PATH_SEPARATOR);
|
|
55
|
+
return withRecordsAt(content, names, [...records.records, blank]);
|
|
56
|
+
}
|
|
25
57
|
function isUploadedPhoto(fileName) {
|
|
26
58
|
return fileName !== void 0 && UPLOADED_PHOTO.test(fileName);
|
|
27
59
|
}
|
|
@@ -51,6 +83,13 @@ function concatenated(chunks, size) {
|
|
|
51
83
|
}
|
|
52
84
|
return bytes;
|
|
53
85
|
}
|
|
86
|
+
function withRecordsAt(content, names, records) {
|
|
87
|
+
const [name, ...rest] = names;
|
|
88
|
+
if (name === void 0) return content;
|
|
89
|
+
if (rest.length === 0) return { ...content, [name]: records };
|
|
90
|
+
const group = content[name];
|
|
91
|
+
return isContentObject(group) ? { ...content, [name]: withRecordsAt(group, rest, records) } : content;
|
|
92
|
+
}
|
|
54
93
|
function refused(status) {
|
|
55
94
|
return { isRead: false, response: new Response(null, { status }) };
|
|
56
95
|
}
|
|
@@ -59,5 +98,7 @@ export {
|
|
|
59
98
|
MAX_DRAFT_BYTES,
|
|
60
99
|
previewHeadersOf,
|
|
61
100
|
readDraft,
|
|
101
|
+
isPageAddress,
|
|
102
|
+
previewContentAt,
|
|
62
103
|
isUploadedPhoto
|
|
63
104
|
};
|