@seamlet/astro 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -16
- package/components/RichLine.astro +37 -0
- package/components/RichText.astro +42 -0
- package/dist/bridge.d.ts +3 -3
- package/dist/bridge.js +2 -2
- package/dist/{chunk-XCBYYKWR.js → chunk-4YLS3VPT.js} +1 -1
- package/dist/{chunk-GLXC5OLC.js → chunk-ZUJEM3P7.js} +127 -0
- package/dist/{content-C_WpiWBm.d.ts → content-DeBXwSmg.d.ts} +48 -4
- package/dist/{messages-CBf5diaN.d.ts → messages-ClePO8N_.d.ts} +2 -2
- package/dist/{parse-content-9RIWhb6z.d.ts → parse-content-DqToE6I6.d.ts} +1 -1
- package/dist/protocol.d.ts +3 -3
- package/dist/protocol.js +2 -2
- package/dist/schema.d.ts +4 -4
- package/dist/schema.js +13 -1
- package/dist/site.d.ts +1 -1
- package/dist/site.js +1 -1
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -78,22 +78,23 @@ The site's pages take their published content with `site.loadPublishedContent()`
|
|
|
78
78
|
|
|
79
79
|
## Fields
|
|
80
80
|
|
|
81
|
-
| Builder
|
|
82
|
-
|
|
|
83
|
-
| `text({ label, multiline?, maxLength? })`
|
|
84
|
-
| `
|
|
85
|
-
| `
|
|
86
|
-
| `
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
89
|
-
| `
|
|
90
|
-
| `
|
|
91
|
-
| `
|
|
92
|
-
| `
|
|
93
|
-
| `
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
81
|
+
| Builder | Holds | Empty |
|
|
82
|
+
| ------------------------------------------ | ----------------------------------------------------------------------- | ---------------- |
|
|
83
|
+
| `text({ label, multiline?, maxLength? })` | a string | `""` |
|
|
84
|
+
| `richText({ label, inline?, maxLength? })` | paragraphs and lists of pieces, bold, italic or links (below) | `[]` |
|
|
85
|
+
| `number({ label, min?, max? })` | a number | `null` |
|
|
86
|
+
| `date({ label })` | `YYYY-MM-DD` | `null` |
|
|
87
|
+
| `time({ label })` | `HH:MM`, 00:00 to 23:59 | `null` |
|
|
88
|
+
| `image({ label })` | `{ src, alt }` | `null` |
|
|
89
|
+
| `link({ label })` | `{ text, href }`: a page of the site, another site, a phone or an email | `null` |
|
|
90
|
+
| `url({ label })` | a web address without a text: a booking widget, a map | `null` |
|
|
91
|
+
| `toggle({ label })` | `true` or `false` | `false` |
|
|
92
|
+
| `choice({ label, options })` | the `value` of one option | `null` |
|
|
93
|
+
| `choices({ label, options })` | the `value`s of any number of options | `[]` |
|
|
94
|
+
| `reference({ label, collection })` | the `id` of a record of a collection of the schema | `null` |
|
|
95
|
+
| `references({ label, collection })` | the `id`s of any number of such records | `[]` |
|
|
96
|
+
| `group({ label, fields })` | named fields shown together | each field empty |
|
|
97
|
+
| `collection({ label, item, itemTitle? })` | a list of records of the same fields | `[]` |
|
|
97
98
|
|
|
98
99
|
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:
|
|
99
100
|
|
|
@@ -141,6 +142,28 @@ export default defineSeamletSite({
|
|
|
141
142
|
});
|
|
142
143
|
```
|
|
143
144
|
|
|
145
|
+
A text with a link in a sentence, bold words or a list is one `richText` field, not a text cut around the link. Its value is data, never HTML: paragraphs (`{ kind: "paragraph", spans }`) and lists (`{ kind: "list", items }`) of pieces `{ text, bold?, italic?, href? }`, where `href` leads where a `link` may. `inline` keeps it one paragraph without lists, for a text inside an element that holds one line. A saved plain string is read as paragraphs, so a `text` field turned into `richText` keeps its words. Render it with the component of the package, which puts out paragraphs and lists (or, with `inline`, the words alone) and never raw HTML; style its elements from around it:
|
|
146
|
+
|
|
147
|
+
```astro
|
|
148
|
+
---
|
|
149
|
+
import RichText from "@seamlet/astro/components/RichText.astro";
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
<div class="about" {...edit("about")}>
|
|
153
|
+
<RichText value={content.about} />
|
|
154
|
+
</div>
|
|
155
|
+
<p {...edit("note")}>
|
|
156
|
+
<RichText value={content.note} inline />
|
|
157
|
+
</p>
|
|
158
|
+
<style>
|
|
159
|
+
.about :global(a) {
|
|
160
|
+
color: inherit;
|
|
161
|
+
}
|
|
162
|
+
</style>
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`plainTextOf` gives its words without marks, for a page title or a description.
|
|
166
|
+
|
|
144
167
|
## Presets
|
|
145
168
|
|
|
146
169
|
Sets of fields almost every site has, edited the same way everywhere; they are plain groups and collections, rendered as the site likes:
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
// The pieces of one line of formatted text with their marks.
|
|
3
|
+
import { linkTargetOf, type RichLine } from "@seamlet/astro/schema";
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
readonly spans: RichLine;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const LINE_BREAK = "\n";
|
|
10
|
+
|
|
11
|
+
const { spans } = Astro.props;
|
|
12
|
+
const pieces = spans.map((span) => ({
|
|
13
|
+
...span,
|
|
14
|
+
lines: span.text.split(LINE_BREAK),
|
|
15
|
+
// Content is checked when it is saved; a site never renders an address a link may not have.
|
|
16
|
+
safeHref:
|
|
17
|
+
span.href !== undefined && linkTargetOf(span.href) !== undefined
|
|
18
|
+
? span.href
|
|
19
|
+
: undefined,
|
|
20
|
+
}));
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
{pieces.map((piece) => {
|
|
24
|
+
const words = piece.lines.map((line, index) => (
|
|
25
|
+
<>
|
|
26
|
+
{index > 0 && <br />}
|
|
27
|
+
{line}
|
|
28
|
+
</>
|
|
29
|
+
));
|
|
30
|
+
const italic = piece.italic === true ? <em>{words}</em> : words;
|
|
31
|
+
const bold = piece.bold === true ? <strong>{italic}</strong> : italic;
|
|
32
|
+
return piece.safeHref === undefined ? (
|
|
33
|
+
bold
|
|
34
|
+
) : (
|
|
35
|
+
<a href={piece.safeHref}>{bold}</a>
|
|
36
|
+
);
|
|
37
|
+
})}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
// Formatted text of the CMS as the site's own elements: paragraphs and lists, or, with `inline`,
|
|
3
|
+
// the words alone for an element that holds one line (`<p>`, `<h2>`). Nothing is rendered as raw
|
|
4
|
+
// HTML, and a link leads only where linkTargetOf allows. The site styles the elements from
|
|
5
|
+
// around it: `.about :global(a)`.
|
|
6
|
+
import type { RichText } from "@seamlet/astro/schema";
|
|
7
|
+
import RichLine from "./RichLine.astro";
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
readonly value: RichText;
|
|
11
|
+
/** Only the words, without paragraphs and lists; its lines are joined with line breaks. */
|
|
12
|
+
readonly inline?: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const { value, inline = false } = Astro.props;
|
|
16
|
+
const lines = value.flatMap((block) =>
|
|
17
|
+
block.kind === "paragraph" ? [block.spans] : block.items,
|
|
18
|
+
);
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
{inline
|
|
22
|
+
? lines.map((line, index) => (
|
|
23
|
+
<>
|
|
24
|
+
{index > 0 && <br />}
|
|
25
|
+
<RichLine spans={line} />
|
|
26
|
+
</>
|
|
27
|
+
))
|
|
28
|
+
: value.map((block) =>
|
|
29
|
+
block.kind === "paragraph" ? (
|
|
30
|
+
<p>
|
|
31
|
+
<RichLine spans={block.spans} />
|
|
32
|
+
</p>
|
|
33
|
+
) : (
|
|
34
|
+
<ul>
|
|
35
|
+
{block.items.map((item) => (
|
|
36
|
+
<li>
|
|
37
|
+
<RichLine spans={item} />
|
|
38
|
+
</li>
|
|
39
|
+
))}
|
|
40
|
+
</ul>
|
|
41
|
+
),
|
|
42
|
+
)}
|
package/dist/bridge.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { F as Fields } from './content-
|
|
2
|
-
import { P as PageMessage, E as EditorMessage } from './messages-
|
|
3
|
-
import './parse-content-
|
|
1
|
+
import { F as Fields } from './content-DeBXwSmg.js';
|
|
2
|
+
import { P as PageMessage, E as EditorMessage } from './messages-ClePO8N_.js';
|
|
3
|
+
import './parse-content-DqToE6I6.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,13 +5,13 @@ import {
|
|
|
5
5
|
imageFieldAt,
|
|
6
6
|
readEditorMessage,
|
|
7
7
|
shortcutOf
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-4YLS3VPT.js";
|
|
9
9
|
import {
|
|
10
10
|
FIELD_PATH_ATTRIBUTE,
|
|
11
11
|
REVEALED_ATTRIBUTE,
|
|
12
12
|
REVEAL_ATTRIBUTE,
|
|
13
13
|
REVEAL_EVENT
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-ZUJEM3P7.js";
|
|
15
15
|
|
|
16
16
|
// src/bridge/page-markers.ts
|
|
17
17
|
var MARKER_SELECTOR = `[${FIELD_PATH_ATTRIBUTE}]`;
|
|
@@ -5,6 +5,9 @@ function defineSchema(fields) {
|
|
|
5
5
|
function text(options) {
|
|
6
6
|
return { kind: "text", ...options };
|
|
7
7
|
}
|
|
8
|
+
function richText(options) {
|
|
9
|
+
return { kind: "richText", ...options };
|
|
10
|
+
}
|
|
8
11
|
function number(options) {
|
|
9
12
|
return { kind: "number", ...options };
|
|
10
13
|
}
|
|
@@ -54,6 +57,7 @@ function defaultValue(field) {
|
|
|
54
57
|
return false;
|
|
55
58
|
case "choices":
|
|
56
59
|
case "references":
|
|
60
|
+
case "richText":
|
|
57
61
|
return [];
|
|
58
62
|
case "number":
|
|
59
63
|
case "date":
|
|
@@ -147,6 +151,102 @@ function withoutPrefix(address, prefix) {
|
|
|
147
151
|
return hasPrefix ? address.slice(prefix.length) : void 0;
|
|
148
152
|
}
|
|
149
153
|
|
|
154
|
+
// src/schema/rich-text.ts
|
|
155
|
+
var PARAGRAPH_BREAK = /\n\s*\n/u;
|
|
156
|
+
var PARAGRAPH_SEPARATOR = "\n\n";
|
|
157
|
+
var LINE_SEPARATOR = "\n";
|
|
158
|
+
var SPACE = " ";
|
|
159
|
+
function richTextOf(plain) {
|
|
160
|
+
return plain.split(PARAGRAPH_BREAK).map((paragraph) => paragraph.trim()).filter((paragraph) => paragraph !== "").map((paragraph) => ({ kind: "paragraph", spans: [{ text: paragraph }] }));
|
|
161
|
+
}
|
|
162
|
+
function plainTextOf(text2) {
|
|
163
|
+
return text2.map(plainBlockOf).join(PARAGRAPH_SEPARATOR);
|
|
164
|
+
}
|
|
165
|
+
function linkAddressesOf(text2) {
|
|
166
|
+
return linesOf(text2).flatMap(
|
|
167
|
+
(line) => line.flatMap((span) => span.href === void 0 ? [] : [span.href])
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
function asOneParagraph(text2) {
|
|
171
|
+
const lines = linesOf(text2).filter((line) => line.length > 0);
|
|
172
|
+
if (lines.length === 0) return [];
|
|
173
|
+
const spans = lines.flatMap(
|
|
174
|
+
(line, index) => index === 0 ? line : [{ text: SPACE }, ...line]
|
|
175
|
+
);
|
|
176
|
+
return [{ kind: "paragraph", spans: normalizedLine(spans) }];
|
|
177
|
+
}
|
|
178
|
+
function readRichText(input) {
|
|
179
|
+
if (!Array.isArray(input)) return void 0;
|
|
180
|
+
const blocks = input.map(readBlock);
|
|
181
|
+
const isEveryBlockRead = blocks.every((block) => block !== void 0);
|
|
182
|
+
if (!isEveryBlockRead) return void 0;
|
|
183
|
+
return blocks.filter(hasWords);
|
|
184
|
+
}
|
|
185
|
+
function normalizedLine(spans) {
|
|
186
|
+
return spans.filter((span) => span.text !== "").reduce((joined, span) => {
|
|
187
|
+
const previous = joined.at(-1);
|
|
188
|
+
if (previous !== void 0 && haveSameMarks(previous, span)) {
|
|
189
|
+
return [
|
|
190
|
+
...joined.slice(0, -1),
|
|
191
|
+
{ ...previous, text: previous.text + span.text }
|
|
192
|
+
];
|
|
193
|
+
}
|
|
194
|
+
return [...joined, span];
|
|
195
|
+
}, []);
|
|
196
|
+
}
|
|
197
|
+
function readBlock(input) {
|
|
198
|
+
if (!isObject(input)) return void 0;
|
|
199
|
+
if (input["kind"] === "paragraph") {
|
|
200
|
+
const spans = readLine(input["spans"]);
|
|
201
|
+
return spans === void 0 ? void 0 : { kind: "paragraph", spans };
|
|
202
|
+
}
|
|
203
|
+
if (input["kind"] === "list" && Array.isArray(input["items"])) {
|
|
204
|
+
const items = input["items"].map(readLine);
|
|
205
|
+
const isEveryItemRead = items.every((item) => item !== void 0);
|
|
206
|
+
if (!isEveryItemRead) return void 0;
|
|
207
|
+
return { kind: "list", items: items.filter((item) => item.length > 0) };
|
|
208
|
+
}
|
|
209
|
+
return void 0;
|
|
210
|
+
}
|
|
211
|
+
function readLine(input) {
|
|
212
|
+
if (!Array.isArray(input)) return void 0;
|
|
213
|
+
const spans = input.map(readSpan);
|
|
214
|
+
const isEverySpanRead = spans.every((span) => span !== void 0);
|
|
215
|
+
return isEverySpanRead ? normalizedLine(spans) : void 0;
|
|
216
|
+
}
|
|
217
|
+
function readSpan(input) {
|
|
218
|
+
if (!isObject(input)) return void 0;
|
|
219
|
+
const { text: text2, bold, italic, href } = input;
|
|
220
|
+
const hasValidMarks = typeof text2 === "string" && (bold === void 0 || bold === true) && (italic === void 0 || italic === true) && (href === void 0 || typeof href === "string");
|
|
221
|
+
if (!hasValidMarks) return void 0;
|
|
222
|
+
return {
|
|
223
|
+
text: text2,
|
|
224
|
+
...bold === true ? { bold } : {},
|
|
225
|
+
...italic === true ? { italic } : {},
|
|
226
|
+
...typeof href === "string" ? { href } : {}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function haveSameMarks(first, second) {
|
|
230
|
+
return first.bold === second.bold && first.italic === second.italic && first.href === second.href;
|
|
231
|
+
}
|
|
232
|
+
function hasWords(block) {
|
|
233
|
+
return block.kind === "paragraph" ? block.spans.length > 0 : block.items.length > 0;
|
|
234
|
+
}
|
|
235
|
+
function linesOf(text2) {
|
|
236
|
+
return text2.flatMap(
|
|
237
|
+
(block) => block.kind === "paragraph" ? [block.spans] : block.items
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
function plainBlockOf(block) {
|
|
241
|
+
return block.kind === "paragraph" ? plainLineOf(block.spans) : block.items.map(plainLineOf).join(LINE_SEPARATOR);
|
|
242
|
+
}
|
|
243
|
+
function plainLineOf(line) {
|
|
244
|
+
return line.map((span) => span.text).join("");
|
|
245
|
+
}
|
|
246
|
+
function isObject(value) {
|
|
247
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
248
|
+
}
|
|
249
|
+
|
|
150
250
|
// src/schema/record-ids.ts
|
|
151
251
|
var RECORD_ID_KEY = "id";
|
|
152
252
|
var RECORD_ID_PATTERN = /^[A-Za-z0-9_-]{1,64}$/u;
|
|
@@ -240,6 +340,7 @@ function issuesInField(field, value, path, idsOf) {
|
|
|
240
340
|
(record, index) => issuesInFields(field.item, record, [...path, index], idsOf)
|
|
241
341
|
);
|
|
242
342
|
case "text":
|
|
343
|
+
case "richText":
|
|
243
344
|
case "number":
|
|
244
345
|
case "date":
|
|
245
346
|
case "image":
|
|
@@ -312,6 +413,8 @@ function parseField(field, input, path) {
|
|
|
312
413
|
switch (field.kind) {
|
|
313
414
|
case "text":
|
|
314
415
|
return parseText(field, input, path);
|
|
416
|
+
case "richText":
|
|
417
|
+
return parseRichText(field, input, path);
|
|
315
418
|
case "number":
|
|
316
419
|
return parseNumber(field, input, path);
|
|
317
420
|
case "date":
|
|
@@ -353,6 +456,24 @@ function parseText(field, input, path) {
|
|
|
353
456
|
}
|
|
354
457
|
return valid(input);
|
|
355
458
|
}
|
|
459
|
+
function parseRichText(field, input, path) {
|
|
460
|
+
const read = typeof input === "string" ? richTextOf(input) : readRichText(input);
|
|
461
|
+
if (read === void 0) {
|
|
462
|
+
return invalidType(defaultValue(field), path);
|
|
463
|
+
}
|
|
464
|
+
const text2 = field.inline === true ? asOneParagraph(read) : read;
|
|
465
|
+
const hasUnsupportedLink = linkAddressesOf(text2).some(
|
|
466
|
+
(href) => linkTargetOf(href) === void 0
|
|
467
|
+
);
|
|
468
|
+
const linkIssues = hasUnsupportedLink ? [{ path, code: "unsupported-link-href" }] : [];
|
|
469
|
+
const lengthIssues = tooLongIssues(field.maxLength, plainTextOf(text2), path);
|
|
470
|
+
return { value: text2, issues: [...linkIssues, ...lengthIssues] };
|
|
471
|
+
}
|
|
472
|
+
function tooLongIssues(maxLength, text2, path) {
|
|
473
|
+
if (maxLength === void 0) return [];
|
|
474
|
+
const isTooLong = countCharacters(text2) > maxLength;
|
|
475
|
+
return isTooLong ? [{ path, code: "too-long", maxLength }] : [];
|
|
476
|
+
}
|
|
356
477
|
function parseNumber(field, input, path) {
|
|
357
478
|
if (input === null) {
|
|
358
479
|
return valid(null);
|
|
@@ -668,6 +789,7 @@ function openingHoursField(options = {}) {
|
|
|
668
789
|
export {
|
|
669
790
|
defineSchema,
|
|
670
791
|
text,
|
|
792
|
+
richText,
|
|
671
793
|
number,
|
|
672
794
|
date,
|
|
673
795
|
image,
|
|
@@ -685,6 +807,11 @@ export {
|
|
|
685
807
|
defaultContent,
|
|
686
808
|
linkTargetOf,
|
|
687
809
|
hrefOf,
|
|
810
|
+
richTextOf,
|
|
811
|
+
plainTextOf,
|
|
812
|
+
asOneParagraph,
|
|
813
|
+
readRichText,
|
|
814
|
+
normalizedLine,
|
|
688
815
|
RECORD_ID_KEY,
|
|
689
816
|
isRecordId,
|
|
690
817
|
newRecord,
|
|
@@ -6,6 +6,18 @@ interface TextField {
|
|
|
6
6
|
/** In characters as people see them (see countCharacters), not UTF-16 units. */
|
|
7
7
|
readonly maxLength?: number;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* A text with paragraphs, lists, bold, italic and links inside it: a paragraph with a link in a
|
|
11
|
+
* sentence stays one field. `inline` keeps it one paragraph without lists, for a text inside an
|
|
12
|
+
* element that holds one line of words.
|
|
13
|
+
*/
|
|
14
|
+
interface RichTextField {
|
|
15
|
+
readonly kind: "richText";
|
|
16
|
+
readonly label: string;
|
|
17
|
+
readonly inline?: boolean;
|
|
18
|
+
/** In characters of its words as people see them, without the marks. */
|
|
19
|
+
readonly maxLength?: number;
|
|
20
|
+
}
|
|
9
21
|
interface NumberField {
|
|
10
22
|
readonly kind: "number";
|
|
11
23
|
readonly label: string;
|
|
@@ -96,7 +108,7 @@ interface CollectionField<F extends RecordFields = RecordFields> {
|
|
|
96
108
|
/** The field of a record that names it in the editor: a text field, or a link by its text. */
|
|
97
109
|
readonly itemTitle?: keyof F & string;
|
|
98
110
|
}
|
|
99
|
-
type Field = TextField | NumberField | DateField | ImageField | LinkField | ToggleField | ChoiceField | ChoicesField | UrlField | TimeField | ReferenceField | ReferencesField | GroupField | CollectionField;
|
|
111
|
+
type Field = TextField | RichTextField | NumberField | DateField | ImageField | LinkField | ToggleField | ChoiceField | ChoicesField | UrlField | TimeField | ReferenceField | ReferencesField | GroupField | CollectionField;
|
|
100
112
|
type FieldKind = Field["kind"];
|
|
101
113
|
/** Field names to fields; the order of the keys is the order of the form. */
|
|
102
114
|
interface Fields {
|
|
@@ -110,6 +122,7 @@ type FieldOptions<F extends Field> = Omit<F, "kind">;
|
|
|
110
122
|
/** The root of a site's schema. Only checks the shape and keeps the literal types. */
|
|
111
123
|
declare function defineSchema<F extends Fields>(fields: F): F;
|
|
112
124
|
declare function text(options: FieldOptions<TextField>): TextField;
|
|
125
|
+
declare function richText(options: FieldOptions<RichTextField>): RichTextField;
|
|
113
126
|
declare function number(options: FieldOptions<NumberField>): NumberField;
|
|
114
127
|
declare function date(options: FieldOptions<DateField>): DateField;
|
|
115
128
|
declare function image(options: FieldOptions<ImageField>): ImageField;
|
|
@@ -125,6 +138,37 @@ declare function references(options: FieldOptions<ReferencesField>): ReferencesF
|
|
|
125
138
|
declare function group<F extends Fields>(options: FieldOptions<GroupField<F>>): GroupField<F>;
|
|
126
139
|
declare function collection<F extends RecordFields>(options: FieldOptions<CollectionField<F>>): CollectionField<F>;
|
|
127
140
|
|
|
141
|
+
/** A piece of a text with its marks; a link is a piece with an address. */
|
|
142
|
+
interface RichSpan {
|
|
143
|
+
readonly text: string;
|
|
144
|
+
readonly bold?: true;
|
|
145
|
+
readonly italic?: true;
|
|
146
|
+
/** A path of the site, a web address, `tel:` or `mailto:`; see linkTargetOf. */
|
|
147
|
+
readonly href?: string;
|
|
148
|
+
}
|
|
149
|
+
type RichLine = readonly RichSpan[];
|
|
150
|
+
type RichBlock = {
|
|
151
|
+
readonly kind: "paragraph";
|
|
152
|
+
readonly spans: RichLine;
|
|
153
|
+
} | {
|
|
154
|
+
readonly kind: "list";
|
|
155
|
+
readonly items: readonly RichLine[];
|
|
156
|
+
};
|
|
157
|
+
type RichText = readonly RichBlock[];
|
|
158
|
+
/** A plain text as paragraphs: how a text field becomes formatted text and keeps its words. */
|
|
159
|
+
declare function richTextOf(plain: string): RichText;
|
|
160
|
+
/** The words of a text without its marks: for a length, a search, a summary. */
|
|
161
|
+
declare function plainTextOf(text: RichText): string;
|
|
162
|
+
/** One paragraph of all the text: a text that stands inside a sentence-level element. */
|
|
163
|
+
declare function asOneParagraph(text: RichText): RichText;
|
|
164
|
+
/**
|
|
165
|
+
* Formatted text read from any input, or undefined when it is not one: the same text gives the
|
|
166
|
+
* same value, with empty pieces left out and neighbors with the same marks joined.
|
|
167
|
+
*/
|
|
168
|
+
declare function readRichText(input: unknown): RichText | undefined;
|
|
169
|
+
/** The same line with empty pieces left out and neighbors with the same marks joined. */
|
|
170
|
+
declare function normalizedLine(spans: readonly RichSpan[]): RichLine;
|
|
171
|
+
|
|
128
172
|
/** The point of a photo that must stay in view when a site crops it: 0 to 1 from the left and the top. */
|
|
129
173
|
interface ImageFocus {
|
|
130
174
|
readonly x: number;
|
|
@@ -145,7 +189,7 @@ interface LinkValue {
|
|
|
145
189
|
* The value of each field. A text can be empty (""), the other scalar fields can be unset (null):
|
|
146
190
|
* content is filled in by people, so a site must be ready for a missing price or date.
|
|
147
191
|
*/
|
|
148
|
-
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 ReferenceField ? string | null : F extends ReferencesField ? readonly string[] : F extends GroupField<infer Group> ? ContentOf<Group> : F extends CollectionField<infer Item> ? readonly RecordOf<Item>[] : never;
|
|
192
|
+
type ValueOf<F extends Field> = F extends TextField ? string : F extends RichTextField ? RichText : 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 ReferenceField ? string | null : F extends ReferencesField ? readonly string[] : F extends GroupField<infer Group> ? ContentOf<Group> : F extends CollectionField<infer Item> ? readonly RecordOf<Item>[] : never;
|
|
149
193
|
/**
|
|
150
194
|
* A record of a collection: its fields and its id. The id is missing only in content no editor has
|
|
151
195
|
* saved yet, such as a site's initial content (see withRecordIds).
|
|
@@ -163,8 +207,8 @@ type ContentOf<F extends Fields> = {
|
|
|
163
207
|
interface Content {
|
|
164
208
|
readonly [name: string]: FieldValue;
|
|
165
209
|
}
|
|
166
|
-
type FieldValue = string | number | boolean | null | readonly string[] | ImageValue | LinkValue | Content | readonly Content[];
|
|
210
|
+
type FieldValue = string | number | boolean | null | readonly string[] | ImageValue | LinkValue | RichText | Content | readonly Content[];
|
|
167
211
|
declare function defaultValue(field: Field): FieldValue;
|
|
168
212
|
declare function defaultContent(fields: Fields): Content;
|
|
169
213
|
|
|
170
|
-
export {
|
|
214
|
+
export { time as $, defaultContent as A, defaultValue as B, type ContentOf as C, type DateField as D, defineSchema as E, type Fields as F, type GroupField as G, group as H, type ImageField as I, image as J, link as K, type LinkValue as L, normalizedLine as M, type NumberField as N, type OptionValue as O, number as P, plainTextOf as Q, type RecordFields as R, readRichText as S, type TextField as T, type UrlField as U, type ValueOf as V, reference as W, references as X, richText as Y, richTextOf as Z, text as _, type Content as a, toggle as a0, url as a1, type ImageValue as b, type Field as c, type CollectionField as d, type LinkField as e, type ChoicesField as f, type TimeField as g, type ChoiceField as h, type ChoiceOption as i, type FieldKind as j, type FieldValue as k, type ImageFocus as l, type RecordOf as m, type ReferenceField as n, type ReferencesField as o, type RichBlock as p, type RichLine as q, type RichSpan as r, type RichText as s, type RichTextField as t, type ToggleField as u, asOneParagraph as v, choice as w, choices as x, collection as y, date as z };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { F as Fields, a as Content } from './content-
|
|
2
|
-
import { F as FieldPath } from './parse-content-
|
|
1
|
+
import { F as Fields, a as Content } from './content-DeBXwSmg.js';
|
|
2
|
+
import { F as FieldPath } from './parse-content-DqToE6I6.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-
|
|
1
|
+
import { F as Fields, C as ContentOf } from './content-DeBXwSmg.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)[];
|
package/dist/protocol.d.ts
CHANGED
|
@@ -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-
|
|
2
|
-
import { F as Fields, I as ImageField } from './content-
|
|
3
|
-
import { F as FieldPath } from './parse-content-
|
|
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-ClePO8N_.js';
|
|
2
|
+
import { F as Fields, I as ImageField } from './content-DeBXwSmg.js';
|
|
3
|
+
import { F as FieldPath } from './parse-content-DqToE6I6.js';
|
|
4
4
|
|
|
5
5
|
declare function carriesFiles(event: DragEvent): boolean;
|
|
6
6
|
|
package/dist/protocol.js
CHANGED
package/dist/schema.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { b as ImageValue, L as LinkValue, c as Field, d as CollectionField, F as Fields, a as Content, R as RecordFields, C as ContentOf, G as GroupField, e as LinkField, T as TextField, U as UrlField, f as ChoicesField, g as TimeField, I as ImageField } from './content-
|
|
2
|
-
export { h as ChoiceField, i as ChoiceOption, D as DateField, j as FieldKind, k as FieldValue, l as ImageFocus, N as NumberField, O as OptionValue, m as RecordOf, n as ReferenceField, o as ReferencesField, p as ToggleField, V as ValueOf,
|
|
3
|
-
import { F as FieldPath } from './parse-content-
|
|
4
|
-
export { C as ContentIssue, P as ParsedContent, c as countCharacters, f as formatFieldPath, p as parseContent } from './parse-content-
|
|
1
|
+
import { b as ImageValue, L as LinkValue, c as Field, d as CollectionField, F as Fields, a as Content, R as RecordFields, C as ContentOf, G as GroupField, e as LinkField, T as TextField, U as UrlField, f as ChoicesField, g as TimeField, I as ImageField } from './content-DeBXwSmg.js';
|
|
2
|
+
export { h as ChoiceField, i as ChoiceOption, D as DateField, j as FieldKind, k as FieldValue, l as ImageFocus, N as NumberField, O as OptionValue, m as RecordOf, n as ReferenceField, o as ReferencesField, p as RichBlock, q as RichLine, r as RichSpan, s 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-DeBXwSmg.js';
|
|
3
|
+
import { F as FieldPath } from './parse-content-DqToE6I6.js';
|
|
4
|
+
export { C as ContentIssue, P as ParsedContent, c as countCharacters, f as formatFieldPath, p as parseContent } from './parse-content-DqToE6I6.js';
|
|
5
5
|
|
|
6
6
|
declare const FIELD_PATH_ATTRIBUTE = "data-cms-path";
|
|
7
7
|
interface FieldMarker {
|
package/dist/schema.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
REVEAL_ATTRIBUTE,
|
|
6
6
|
REVEAL_EVENT,
|
|
7
7
|
WEEKDAYS,
|
|
8
|
+
asOneParagraph,
|
|
8
9
|
choice,
|
|
9
10
|
choices,
|
|
10
11
|
collection,
|
|
@@ -26,19 +27,24 @@ import {
|
|
|
26
27
|
link,
|
|
27
28
|
linkTargetOf,
|
|
28
29
|
newRecord,
|
|
30
|
+
normalizedLine,
|
|
29
31
|
number,
|
|
30
32
|
openingHoursField,
|
|
31
33
|
parseContent,
|
|
34
|
+
plainTextOf,
|
|
35
|
+
readRichText,
|
|
32
36
|
reference,
|
|
33
37
|
references,
|
|
34
38
|
resolveFieldPath,
|
|
39
|
+
richText,
|
|
40
|
+
richTextOf,
|
|
35
41
|
seoFields,
|
|
36
42
|
text,
|
|
37
43
|
time,
|
|
38
44
|
toggle,
|
|
39
45
|
url,
|
|
40
46
|
withRecordIds
|
|
41
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-ZUJEM3P7.js";
|
|
42
48
|
export {
|
|
43
49
|
FIELD_PATH_ATTRIBUTE,
|
|
44
50
|
RECORD_ID_KEY,
|
|
@@ -46,6 +52,7 @@ export {
|
|
|
46
52
|
REVEAL_ATTRIBUTE,
|
|
47
53
|
REVEAL_EVENT,
|
|
48
54
|
WEEKDAYS,
|
|
55
|
+
asOneParagraph,
|
|
49
56
|
choice,
|
|
50
57
|
choices,
|
|
51
58
|
collection,
|
|
@@ -67,12 +74,17 @@ export {
|
|
|
67
74
|
link,
|
|
68
75
|
linkTargetOf,
|
|
69
76
|
newRecord,
|
|
77
|
+
normalizedLine,
|
|
70
78
|
number,
|
|
71
79
|
openingHoursField,
|
|
72
80
|
parseContent,
|
|
81
|
+
plainTextOf,
|
|
82
|
+
readRichText,
|
|
73
83
|
reference,
|
|
74
84
|
references,
|
|
75
85
|
resolveFieldPath,
|
|
86
|
+
richText,
|
|
87
|
+
richTextOf,
|
|
76
88
|
seoFields,
|
|
77
89
|
text,
|
|
78
90
|
time,
|
package/dist/site.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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-
|
|
3
|
+
import { F as Fields, C as ContentOf } from './content-DeBXwSmg.js';
|
|
4
4
|
|
|
5
5
|
interface SeamletSiteDefinition<F extends Fields> {
|
|
6
6
|
/** The site's address in the CMS: `/sites/<id>`. */
|
package/dist/site.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seamlet/astro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.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",
|
|
@@ -46,11 +46,13 @@
|
|
|
46
46
|
"./routes/own-photos": {
|
|
47
47
|
"types": "./dist/routes/own-photos.d.ts",
|
|
48
48
|
"default": "./dist/routes/own-photos.js"
|
|
49
|
-
}
|
|
49
|
+
},
|
|
50
|
+
"./components/RichText.astro": "./components/RichText.astro"
|
|
50
51
|
},
|
|
51
52
|
"files": [
|
|
52
53
|
"dist",
|
|
53
|
-
"routes"
|
|
54
|
+
"routes",
|
|
55
|
+
"components"
|
|
54
56
|
],
|
|
55
57
|
"scripts": {
|
|
56
58
|
"build": "tsup"
|