@seamlet/astro 0.11.2 → 0.12.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 +18 -6
- package/components/RichLine.astro +2 -5
- package/components/RichText.astro +2 -4
- package/dist/bridge.d.ts +2 -3
- package/dist/bridge.js +598 -374
- package/dist/{chunk-DRDPKFHZ.js → chunk-7GYJS7ZT.js} +386 -203
- package/dist/{chunk-2E7VDSUK.js → chunk-MW3BE5O6.js} +34 -43
- package/dist/chunk-QCVWHHJC.js +14 -0
- package/dist/{chunk-P2OQOZBC.js → chunk-QL4K4HR6.js} +8 -0
- package/dist/{chunk-3BS5CE26.js → chunk-W2LIOR2C.js} +6 -2
- package/dist/cli.js +96 -37
- package/dist/client.js +4 -1
- package/dist/index.js +52 -19
- package/dist/{messages-Dx0jNy3N.d.ts → messages-CvZhLRSE.d.ts} +29 -52
- package/dist/{content-6k-E7NWf.d.ts → parse-content-Eg8yRq_t.d.ts} +69 -1
- package/dist/preview-requests.d.ts +6 -1
- package/dist/preview-requests.js +3 -1
- package/dist/protocol.d.ts +31 -5
- package/dist/protocol.js +4 -3
- package/dist/routes/media.js +26 -5
- package/dist/routes/own-photos.js +2 -1
- package/dist/schema.d.ts +13 -8
- package/dist/schema.js +13 -1
- package/dist/site-description-Cg5KNhhG.d.ts +112 -0
- package/dist/site.d.ts +2 -2
- package/dist/site.js +83 -9
- package/package.json +1 -1
- package/routes/preview.astro +8 -11
- package/dist/parse-content-C9ldnmvj.d.ts +0 -69
- package/dist/site-description-4Jx6Nq6X.d.ts +0 -36
|
@@ -80,6 +80,73 @@ function defaultContent(fields) {
|
|
|
80
80
|
);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
// src/schema/field-paths.ts
|
|
84
|
+
var RECORD_INDEX_PATTERN = /^\d+$/u;
|
|
85
|
+
var FIELD_PATH_SEPARATOR = ".";
|
|
86
|
+
function isRecordIndex(segment) {
|
|
87
|
+
return RECORD_INDEX_PATTERN.test(segment);
|
|
88
|
+
}
|
|
89
|
+
function resolveFieldPath(schema, text2) {
|
|
90
|
+
return resolveInFields(schema, text2.split(FIELD_PATH_SEPARATOR), []);
|
|
91
|
+
}
|
|
92
|
+
function resolveInFields(fields, segments, parentPath) {
|
|
93
|
+
const [name, ...rest] = segments;
|
|
94
|
+
const field = name !== void 0 && Object.hasOwn(fields, name) ? fields[name] : void 0;
|
|
95
|
+
if (name === void 0 || field === void 0) {
|
|
96
|
+
return void 0;
|
|
97
|
+
}
|
|
98
|
+
const path = [...parentPath, name];
|
|
99
|
+
if (rest.length === 0) {
|
|
100
|
+
return { kind: "field", path, field };
|
|
101
|
+
}
|
|
102
|
+
return resolveInsideField(field, rest, path);
|
|
103
|
+
}
|
|
104
|
+
function resolveInsideField(field, segments, path) {
|
|
105
|
+
switch (field.kind) {
|
|
106
|
+
case "group":
|
|
107
|
+
return resolveInFields(field.fields, segments, path);
|
|
108
|
+
case "collection":
|
|
109
|
+
return resolveInCollection(field, segments, path);
|
|
110
|
+
case "text":
|
|
111
|
+
case "richText":
|
|
112
|
+
case "number":
|
|
113
|
+
case "date":
|
|
114
|
+
case "image":
|
|
115
|
+
case "link":
|
|
116
|
+
case "toggle":
|
|
117
|
+
case "choice":
|
|
118
|
+
case "choices":
|
|
119
|
+
case "url":
|
|
120
|
+
case "time":
|
|
121
|
+
case "reference":
|
|
122
|
+
case "references":
|
|
123
|
+
return void 0;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function resolveInCollection(collection2, segments, collectionPath) {
|
|
127
|
+
const [indexText, ...rest] = segments;
|
|
128
|
+
if (indexText === void 0 || !isRecordIndex(indexText)) {
|
|
129
|
+
return void 0;
|
|
130
|
+
}
|
|
131
|
+
const index = Number(indexText);
|
|
132
|
+
const recordPath = [...collectionPath, index];
|
|
133
|
+
if (rest.length === 0) {
|
|
134
|
+
return { kind: "record", path: recordPath, collection: collection2, index };
|
|
135
|
+
}
|
|
136
|
+
return resolveInFields(collection2.item, rest, recordPath);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// src/schema/json-values.ts
|
|
140
|
+
function isJsonObject(value) {
|
|
141
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
142
|
+
}
|
|
143
|
+
function isContentObject(value) {
|
|
144
|
+
return isJsonObject(value);
|
|
145
|
+
}
|
|
146
|
+
function isText(value) {
|
|
147
|
+
return typeof value === "string" && value.trim() !== "";
|
|
148
|
+
}
|
|
149
|
+
|
|
83
150
|
// src/schema/link-addresses.ts
|
|
84
151
|
var PHONE_PREFIX = "tel:";
|
|
85
152
|
var EMAIL_PREFIX = "mailto:";
|
|
@@ -87,7 +154,7 @@ var WEB_PREFIX = "https://";
|
|
|
87
154
|
var SITE_ORIGIN = "https://site.invalid";
|
|
88
155
|
var FIRST_PRINTABLE_CODE = 32;
|
|
89
156
|
var DELETE_CODE = 127;
|
|
90
|
-
var
|
|
157
|
+
var BACKSLASH = "\\";
|
|
91
158
|
var WEB_ADDRESS = /^https?:\/\/[^\s/?#]+[^\s]*$/iu;
|
|
92
159
|
var ANY_SCHEME = /^[a-z][a-z\d+.-]*:(?!\d)/iu;
|
|
93
160
|
var PHONE_NUMBER = /^\+?[\d\s().-]*\d[\d\s().-]*$/u;
|
|
@@ -96,7 +163,7 @@ var EMAIL_ADDRESS = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u;
|
|
|
96
163
|
function linkTargetOf(href) {
|
|
97
164
|
const address = href;
|
|
98
165
|
const hasSpacesAround = address !== address.trim();
|
|
99
|
-
if (hasSpacesAround) {
|
|
166
|
+
if (hasSpacesAround || hasControlCharacter(address)) {
|
|
100
167
|
return void 0;
|
|
101
168
|
}
|
|
102
169
|
if (isSitePath(address)) {
|
|
@@ -115,6 +182,9 @@ function linkTargetOf(href) {
|
|
|
115
182
|
}
|
|
116
183
|
return void 0;
|
|
117
184
|
}
|
|
185
|
+
function safeHref(href) {
|
|
186
|
+
return linkTargetOf(href) === void 0 ? void 0 : href;
|
|
187
|
+
}
|
|
118
188
|
function hrefOf(target) {
|
|
119
189
|
switch (target.kind) {
|
|
120
190
|
case "page":
|
|
@@ -132,18 +202,18 @@ function webAddressOf(typed) {
|
|
|
132
202
|
return hasScheme || typed === "" ? typed : `${WEB_PREFIX}${typed}`;
|
|
133
203
|
}
|
|
134
204
|
function isSitePath(address) {
|
|
135
|
-
if (!address.startsWith("/") ||
|
|
205
|
+
if (!address.startsWith("/") || address.includes(BACKSLASH)) {
|
|
136
206
|
return false;
|
|
137
207
|
}
|
|
138
208
|
return new URL(address, SITE_ORIGIN).origin === SITE_ORIGIN;
|
|
139
209
|
}
|
|
140
|
-
function
|
|
210
|
+
function hasControlCharacter(address) {
|
|
141
211
|
const codes = Array.from(
|
|
142
212
|
{ length: address.length },
|
|
143
213
|
(_, index) => address.charCodeAt(index)
|
|
144
214
|
);
|
|
145
215
|
return codes.some(
|
|
146
|
-
(code) => code
|
|
216
|
+
(code) => code < FIRST_PRINTABLE_CODE || code === DELETE_CODE
|
|
147
217
|
);
|
|
148
218
|
}
|
|
149
219
|
function withoutPrefix(address, prefix) {
|
|
@@ -195,7 +265,7 @@ function normalizedLine(spans) {
|
|
|
195
265
|
}, []);
|
|
196
266
|
}
|
|
197
267
|
function readBlock(input) {
|
|
198
|
-
if (!
|
|
268
|
+
if (!isJsonObject(input)) return void 0;
|
|
199
269
|
if (input["kind"] === "paragraph") {
|
|
200
270
|
const spans = readLine(input["spans"]);
|
|
201
271
|
return spans === void 0 ? void 0 : { kind: "paragraph", spans };
|
|
@@ -215,7 +285,7 @@ function readLine(input) {
|
|
|
215
285
|
return isEverySpanRead ? normalizedLine(spans) : void 0;
|
|
216
286
|
}
|
|
217
287
|
function readSpan(input) {
|
|
218
|
-
if (!
|
|
288
|
+
if (!isJsonObject(input)) return void 0;
|
|
219
289
|
const { text: text2, bold, italic, href } = input;
|
|
220
290
|
const hasValidMarks = typeof text2 === "string" && (bold === void 0 || bold === true) && (italic === void 0 || italic === true) && (href === void 0 || typeof href === "string");
|
|
221
291
|
if (!hasValidMarks) return void 0;
|
|
@@ -243,9 +313,6 @@ function plainBlockOf(block) {
|
|
|
243
313
|
function plainLineOf(line) {
|
|
244
314
|
return line.map((span) => span.text).join("");
|
|
245
315
|
}
|
|
246
|
-
function isObject(value) {
|
|
247
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
248
|
-
}
|
|
249
316
|
|
|
250
317
|
// src/schema/record-ids.ts
|
|
251
318
|
var RECORD_ID_KEY = "id";
|
|
@@ -259,23 +326,63 @@ function newRecord(collection2, id) {
|
|
|
259
326
|
function withRecordIds(schema, content, newId) {
|
|
260
327
|
return withIdsInFields(schema, content, newId);
|
|
261
328
|
}
|
|
262
|
-
var COLLECTION_PATH_SEPARATOR = ".";
|
|
263
329
|
function collectionAt(schema, content, path) {
|
|
264
|
-
return collectionIn(schema, content, path.split(
|
|
330
|
+
return collectionIn(schema, content, path.split(FIELD_PATH_SEPARATOR));
|
|
265
331
|
}
|
|
266
332
|
function collectionIn(fields, content, names) {
|
|
267
333
|
const [name, ...rest] = names;
|
|
268
334
|
const field = name !== void 0 && Object.hasOwn(fields, name) ? fields[name] : void 0;
|
|
269
335
|
if (name === void 0 || field === void 0) return void 0;
|
|
270
336
|
const value = content[name];
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
337
|
+
const isLast = rest.length === 0;
|
|
338
|
+
switch (field.kind) {
|
|
339
|
+
case "collection": {
|
|
340
|
+
const records = Array.isArray(value) ? value.filter(isContentObject) : [];
|
|
341
|
+
return isLast ? { collection: field, records } : void 0;
|
|
342
|
+
}
|
|
343
|
+
case "group":
|
|
344
|
+
return !isLast && isContentObject(value) ? collectionIn(field.fields, value, rest) : void 0;
|
|
345
|
+
case "text":
|
|
346
|
+
case "richText":
|
|
347
|
+
case "number":
|
|
348
|
+
case "date":
|
|
349
|
+
case "image":
|
|
350
|
+
case "link":
|
|
351
|
+
case "toggle":
|
|
352
|
+
case "choice":
|
|
353
|
+
case "choices":
|
|
354
|
+
case "url":
|
|
355
|
+
case "time":
|
|
356
|
+
case "reference":
|
|
357
|
+
case "references":
|
|
358
|
+
return void 0;
|
|
274
359
|
}
|
|
275
|
-
|
|
276
|
-
|
|
360
|
+
}
|
|
361
|
+
function collectionFieldAt(schema, path) {
|
|
362
|
+
const [name, ...rest] = path.split(FIELD_PATH_SEPARATOR);
|
|
363
|
+
const field = name !== void 0 && Object.hasOwn(schema, name) ? schema[name] : void 0;
|
|
364
|
+
if (field === void 0) return void 0;
|
|
365
|
+
const isLast = rest.length === 0;
|
|
366
|
+
switch (field.kind) {
|
|
367
|
+
case "collection":
|
|
368
|
+
return isLast ? field : void 0;
|
|
369
|
+
case "group":
|
|
370
|
+
return isLast ? void 0 : collectionFieldAt(field.fields, rest.join(FIELD_PATH_SEPARATOR));
|
|
371
|
+
case "text":
|
|
372
|
+
case "richText":
|
|
373
|
+
case "number":
|
|
374
|
+
case "date":
|
|
375
|
+
case "image":
|
|
376
|
+
case "link":
|
|
377
|
+
case "toggle":
|
|
378
|
+
case "choice":
|
|
379
|
+
case "choices":
|
|
380
|
+
case "url":
|
|
381
|
+
case "time":
|
|
382
|
+
case "reference":
|
|
383
|
+
case "references":
|
|
384
|
+
return void 0;
|
|
277
385
|
}
|
|
278
|
-
return void 0;
|
|
279
386
|
}
|
|
280
387
|
function findRecord(records, id) {
|
|
281
388
|
if (id === null) return void 0;
|
|
@@ -289,13 +396,26 @@ function withIdsInFields(fields, content, newId) {
|
|
|
289
396
|
return Object.fromEntries(entries);
|
|
290
397
|
}
|
|
291
398
|
function withIdsIn(field, value, newId) {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
399
|
+
switch (field.kind) {
|
|
400
|
+
case "group":
|
|
401
|
+
return isContentObject(value) ? withIdsInFields(field.fields, value, newId) : value;
|
|
402
|
+
case "collection":
|
|
403
|
+
return Array.isArray(value) ? withIdsInRecords(field, value.filter(isContentObject), newId) : value;
|
|
404
|
+
case "text":
|
|
405
|
+
case "richText":
|
|
406
|
+
case "number":
|
|
407
|
+
case "date":
|
|
408
|
+
case "image":
|
|
409
|
+
case "link":
|
|
410
|
+
case "toggle":
|
|
411
|
+
case "choice":
|
|
412
|
+
case "choices":
|
|
413
|
+
case "url":
|
|
414
|
+
case "time":
|
|
415
|
+
case "reference":
|
|
416
|
+
case "references":
|
|
417
|
+
return value;
|
|
297
418
|
}
|
|
298
|
-
return value;
|
|
299
419
|
}
|
|
300
420
|
function withIdsInRecords(collection2, records, newId) {
|
|
301
421
|
const taken = /* @__PURE__ */ new Set();
|
|
@@ -308,9 +428,6 @@ function withIdsInRecords(collection2, records, newId) {
|
|
|
308
428
|
return { ...fields, [RECORD_ID_KEY]: id };
|
|
309
429
|
});
|
|
310
430
|
}
|
|
311
|
-
function isContent(value) {
|
|
312
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
313
|
-
}
|
|
314
431
|
|
|
315
432
|
// src/schema/reference-checks.ts
|
|
316
433
|
function missingRecordIssues(schema, content) {
|
|
@@ -334,7 +451,7 @@ function issuesInField(field, value, path, idsOf) {
|
|
|
334
451
|
case "references":
|
|
335
452
|
return missingIds(stringsOf(value), field, path, idsOf);
|
|
336
453
|
case "group":
|
|
337
|
-
return
|
|
454
|
+
return isContentObject(value) ? issuesInFields(field.fields, value, path, idsOf) : [];
|
|
338
455
|
case "collection":
|
|
339
456
|
return recordsOf(value).flatMap(
|
|
340
457
|
(record, index) => issuesInFields(field.item, record, [...path, index], idsOf)
|
|
@@ -364,22 +481,19 @@ function recordIdsAt(schema, content, path) {
|
|
|
364
481
|
return new Set(ids.filter((id) => typeof id === "string"));
|
|
365
482
|
}
|
|
366
483
|
function recordsOf(value) {
|
|
367
|
-
return Array.isArray(value) ? value.filter(
|
|
484
|
+
return Array.isArray(value) ? value.filter(isContentObject) : [];
|
|
368
485
|
}
|
|
369
486
|
function stringsOf(value) {
|
|
370
487
|
const list = Array.isArray(value) ? value : [];
|
|
371
488
|
return list.filter((id) => typeof id === "string");
|
|
372
489
|
}
|
|
373
|
-
function isContent2(value) {
|
|
374
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
375
|
-
}
|
|
376
490
|
|
|
377
491
|
// src/schema/parse-content.ts
|
|
378
492
|
var DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/u;
|
|
379
493
|
var TIME_PATTERN = /^(?:[01]\d|2[0-3]):[0-5]\d$/u;
|
|
380
494
|
var GRAPHEMES = new Intl.Segmenter("ru-RU", { granularity: "grapheme" });
|
|
381
495
|
function formatFieldPath(path) {
|
|
382
|
-
return path.join(
|
|
496
|
+
return path.join(FIELD_PATH_SEPARATOR);
|
|
383
497
|
}
|
|
384
498
|
function countCharacters(text2) {
|
|
385
499
|
return [...GRAPHEMES.segment(text2)].length;
|
|
@@ -393,7 +507,7 @@ function parseContent(schema, input) {
|
|
|
393
507
|
};
|
|
394
508
|
}
|
|
395
509
|
function parseFields(fields, input, path) {
|
|
396
|
-
if (!
|
|
510
|
+
if (!isJsonObject(input)) {
|
|
397
511
|
return invalidType(defaultContent(fields), path);
|
|
398
512
|
}
|
|
399
513
|
const parsedFields = Object.entries(fields).map(([name, field]) => {
|
|
@@ -457,11 +571,11 @@ function parseText(field, input, path) {
|
|
|
457
571
|
return valid(input);
|
|
458
572
|
}
|
|
459
573
|
function parseRichText(field, input, path) {
|
|
460
|
-
const
|
|
461
|
-
if (
|
|
574
|
+
const read2 = typeof input === "string" ? richTextOf(input) : readRichText(input);
|
|
575
|
+
if (read2 === void 0) {
|
|
462
576
|
return invalidType(defaultValue(field), path);
|
|
463
577
|
}
|
|
464
|
-
const text2 = field.inline === true ? asOneParagraph(
|
|
578
|
+
const text2 = field.inline === true ? asOneParagraph(read2) : read2;
|
|
465
579
|
const hasUnsupportedLink = linkAddressesOf(text2).some(
|
|
466
580
|
(href) => linkTargetOf(href) === void 0
|
|
467
581
|
);
|
|
@@ -514,12 +628,12 @@ function parseImage(field, input, path) {
|
|
|
514
628
|
if (input === null) {
|
|
515
629
|
return valid(null);
|
|
516
630
|
}
|
|
517
|
-
const src =
|
|
518
|
-
const alt =
|
|
631
|
+
const src = isJsonObject(input) ? input["src"] : void 0;
|
|
632
|
+
const alt = isJsonObject(input) ? input["alt"] : void 0;
|
|
519
633
|
if (typeof src !== "string" || typeof alt !== "string") {
|
|
520
634
|
return invalidType(defaultValue(field), path);
|
|
521
635
|
}
|
|
522
|
-
const focus =
|
|
636
|
+
const focus = isJsonObject(input) ? focusOf(input["focus"]) : void 0;
|
|
523
637
|
const image2 = focus === void 0 ? { src, alt } : { src, alt, focus };
|
|
524
638
|
if (src.trim() === "") {
|
|
525
639
|
return { value: image2, issues: [{ path, code: "missing-image-src" }] };
|
|
@@ -530,7 +644,7 @@ function parseLink(field, input, path) {
|
|
|
530
644
|
if (input === null) {
|
|
531
645
|
return valid(null);
|
|
532
646
|
}
|
|
533
|
-
if (!
|
|
647
|
+
if (!isJsonObject(input)) {
|
|
534
648
|
return invalidType(defaultValue(field), path);
|
|
535
649
|
}
|
|
536
650
|
const { text: text2, href } = input;
|
|
@@ -619,7 +733,7 @@ function parseCollection(field, input, path) {
|
|
|
619
733
|
}
|
|
620
734
|
function parseRecord(field, input, path) {
|
|
621
735
|
const parsed = parseFields(field.item, input, path);
|
|
622
|
-
const id =
|
|
736
|
+
const id = isJsonObject(input) ? input[RECORD_ID_KEY] : void 0;
|
|
623
737
|
if (!isRecordId(id)) return parsed;
|
|
624
738
|
return {
|
|
625
739
|
value: { ...parsed.value, [RECORD_ID_KEY]: id },
|
|
@@ -644,7 +758,7 @@ function parseReferences(field, input, path) {
|
|
|
644
758
|
return valid(ids);
|
|
645
759
|
}
|
|
646
760
|
function focusOf(input) {
|
|
647
|
-
if (!
|
|
761
|
+
if (!isJsonObject(input)) return void 0;
|
|
648
762
|
const { x, y } = input;
|
|
649
763
|
const isInside = isFraction(x) && isFraction(y);
|
|
650
764
|
return isInside ? { x, y } : void 0;
|
|
@@ -659,9 +773,6 @@ function isCalendarDate(value) {
|
|
|
659
773
|
const time2 = Date.parse(`${value}T00:00:00Z`);
|
|
660
774
|
return !Number.isNaN(time2) && new Date(time2).toISOString().startsWith(value);
|
|
661
775
|
}
|
|
662
|
-
function isRecord(value) {
|
|
663
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
664
|
-
}
|
|
665
776
|
function valid(value) {
|
|
666
777
|
return { value, issues: [] };
|
|
667
778
|
}
|
|
@@ -675,45 +786,6 @@ function editable() {
|
|
|
675
786
|
return (...path) => ({ [FIELD_PATH_ATTRIBUTE]: formatFieldPath(path) });
|
|
676
787
|
}
|
|
677
788
|
|
|
678
|
-
// src/schema/field-paths.ts
|
|
679
|
-
var RECORD_INDEX_PATTERN = /^\d+$/u;
|
|
680
|
-
function resolveFieldPath(schema, text2) {
|
|
681
|
-
return resolveInFields(schema, text2.split("."), []);
|
|
682
|
-
}
|
|
683
|
-
function resolveInFields(fields, segments, parentPath) {
|
|
684
|
-
const [name, ...rest] = segments;
|
|
685
|
-
const field = name !== void 0 && Object.hasOwn(fields, name) ? fields[name] : void 0;
|
|
686
|
-
if (name === void 0 || field === void 0) {
|
|
687
|
-
return void 0;
|
|
688
|
-
}
|
|
689
|
-
const path = [...parentPath, name];
|
|
690
|
-
if (rest.length === 0) {
|
|
691
|
-
return { kind: "field", path, field };
|
|
692
|
-
}
|
|
693
|
-
return resolveInsideField(field, rest, path);
|
|
694
|
-
}
|
|
695
|
-
function resolveInsideField(field, segments, path) {
|
|
696
|
-
if (field.kind === "group") {
|
|
697
|
-
return resolveInFields(field.fields, segments, path);
|
|
698
|
-
}
|
|
699
|
-
if (field.kind === "collection") {
|
|
700
|
-
return resolveInCollection(field, segments, path);
|
|
701
|
-
}
|
|
702
|
-
return void 0;
|
|
703
|
-
}
|
|
704
|
-
function resolveInCollection(collection2, segments, collectionPath) {
|
|
705
|
-
const [indexText, ...rest] = segments;
|
|
706
|
-
if (indexText === void 0 || !RECORD_INDEX_PATTERN.test(indexText)) {
|
|
707
|
-
return void 0;
|
|
708
|
-
}
|
|
709
|
-
const index = Number(indexText);
|
|
710
|
-
const recordPath = [...collectionPath, index];
|
|
711
|
-
if (rest.length === 0) {
|
|
712
|
-
return { kind: "record", path: recordPath, collection: collection2, index };
|
|
713
|
-
}
|
|
714
|
-
return resolveInFields(collection2.item, rest, recordPath);
|
|
715
|
-
}
|
|
716
|
-
|
|
717
789
|
// src/schema/reveal-markers.ts
|
|
718
790
|
var REVEAL_ATTRIBUTE = "data-cms-reveal";
|
|
719
791
|
var REVEALED_ATTRIBUTE = "data-cms-revealed";
|
|
@@ -787,125 +859,185 @@ function openingHoursField(options = {}) {
|
|
|
787
859
|
}
|
|
788
860
|
|
|
789
861
|
// src/schema/read-fields.ts
|
|
862
|
+
var TITLE_KINDS = /* @__PURE__ */ new Set([
|
|
863
|
+
"text",
|
|
864
|
+
"link"
|
|
865
|
+
]);
|
|
790
866
|
function readFields(input) {
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
const
|
|
796
|
-
|
|
797
|
-
);
|
|
798
|
-
|
|
799
|
-
|
|
867
|
+
const reading = checkFields(input);
|
|
868
|
+
return reading.status === "read" ? reading.fields : void 0;
|
|
869
|
+
}
|
|
870
|
+
function checkFields(input) {
|
|
871
|
+
const reading = readFieldsAt(input, []);
|
|
872
|
+
if (!reading.isRead) return { status: "invalid", problem: reading.problem };
|
|
873
|
+
const problem = firstUnknownCollection(reading.value, reading.value, []);
|
|
874
|
+
return problem === void 0 ? { status: "read", fields: reading.value } : { status: "invalid", problem };
|
|
875
|
+
}
|
|
876
|
+
function readFieldsAt(input, path) {
|
|
877
|
+
if (!isJsonObject(input)) return refused(path, "not-fields");
|
|
878
|
+
const fields = {};
|
|
879
|
+
for (const [name, value] of Object.entries(input)) {
|
|
880
|
+
const fieldPath = [...path, name];
|
|
881
|
+
if (!isFieldName(name)) return refused(fieldPath, "invalid-name");
|
|
882
|
+
const field = readField(value, fieldPath);
|
|
883
|
+
if (!field.isRead) return field;
|
|
884
|
+
fields[name] = field.value;
|
|
885
|
+
}
|
|
886
|
+
return read(fields);
|
|
800
887
|
}
|
|
801
|
-
function readField(input) {
|
|
802
|
-
if (!
|
|
803
|
-
const label = input
|
|
804
|
-
if (typeof label !== "string") return
|
|
805
|
-
|
|
806
|
-
return
|
|
888
|
+
function readField(input, path) {
|
|
889
|
+
if (!isJsonObject(input)) return refused(path, "not-fields");
|
|
890
|
+
const { kind, label } = input;
|
|
891
|
+
if (typeof label !== "string") return refused(path, "invalid-label");
|
|
892
|
+
if (!isFieldKind(kind)) return refused(path, "unknown-kind");
|
|
893
|
+
return readFieldOfKind({ kind, label, input, path });
|
|
807
894
|
}
|
|
808
|
-
function readFieldOfKind(
|
|
895
|
+
function readFieldOfKind(field) {
|
|
896
|
+
const { kind, label, input, path } = field;
|
|
809
897
|
switch (kind) {
|
|
810
898
|
case "text":
|
|
811
|
-
return {
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
...optional("maxLength", input["maxLength"], isCount)
|
|
816
|
-
};
|
|
899
|
+
return withOptions({ kind, label }, path, [
|
|
900
|
+
optional("multiline", input, isBoolean),
|
|
901
|
+
optional("maxLength", input, isCount)
|
|
902
|
+
]);
|
|
817
903
|
case "richText":
|
|
818
|
-
return {
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
...optional("maxLength", input["maxLength"], isCount)
|
|
823
|
-
};
|
|
904
|
+
return withOptions({ kind, label }, path, [
|
|
905
|
+
optional("inline", input, isBoolean),
|
|
906
|
+
optional("maxLength", input, isCount)
|
|
907
|
+
]);
|
|
824
908
|
case "number":
|
|
825
|
-
return {
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
...optional("max", input["max"], isNumber)
|
|
830
|
-
};
|
|
909
|
+
return withOptions({ kind, label }, path, [
|
|
910
|
+
optional("min", input, isNumber),
|
|
911
|
+
optional("max", input, isNumber)
|
|
912
|
+
]);
|
|
831
913
|
case "date":
|
|
832
914
|
case "image":
|
|
833
915
|
case "link":
|
|
834
916
|
case "toggle":
|
|
835
917
|
case "url":
|
|
836
918
|
case "time":
|
|
837
|
-
return { kind, label };
|
|
919
|
+
return read({ kind, label });
|
|
838
920
|
case "choice":
|
|
839
921
|
case "choices":
|
|
840
|
-
return readChoiceField(kind, label, input
|
|
922
|
+
return readChoiceField({ kind, label, input, path });
|
|
841
923
|
case "reference":
|
|
842
924
|
case "references":
|
|
843
|
-
return readReferenceField(kind, label, input
|
|
925
|
+
return readReferenceField({ kind, label, input, path });
|
|
844
926
|
case "group":
|
|
845
|
-
return readGroupField(label, input
|
|
927
|
+
return readGroupField({ kind, label, input, path });
|
|
846
928
|
case "collection":
|
|
847
|
-
return readCollectionField(label, input);
|
|
929
|
+
return readCollectionField({ kind, label, input, path });
|
|
848
930
|
}
|
|
849
931
|
}
|
|
850
|
-
function readChoiceField(
|
|
851
|
-
|
|
852
|
-
const options = input;
|
|
853
|
-
|
|
854
|
-
|
|
932
|
+
function readChoiceField(field) {
|
|
933
|
+
const { kind, label, input, path } = field;
|
|
934
|
+
const options = input["options"];
|
|
935
|
+
const isOptionList = Array.isArray(options) && options.every(isChoiceOption);
|
|
936
|
+
if (!isOptionList) return refused(path, "invalid-choice-options");
|
|
937
|
+
const knownOptions = options.map(({ value, label: optionLabel }) => ({
|
|
855
938
|
value,
|
|
856
939
|
label: optionLabel
|
|
857
940
|
}));
|
|
858
|
-
return { kind, label, options:
|
|
941
|
+
return read({ kind, label, options: knownOptions });
|
|
942
|
+
}
|
|
943
|
+
function readReferenceField(field) {
|
|
944
|
+
const { kind, label, input, path } = field;
|
|
945
|
+
const collection2 = input["collection"];
|
|
946
|
+
if (typeof collection2 !== "string") {
|
|
947
|
+
return refused(path, "unknown-collection");
|
|
948
|
+
}
|
|
949
|
+
return read({ kind, label, collection: collection2 });
|
|
950
|
+
}
|
|
951
|
+
function readGroupField(field) {
|
|
952
|
+
const { kind, label, input, path } = field;
|
|
953
|
+
const fields = readFieldsAt(input["fields"], path);
|
|
954
|
+
return fields.isRead ? read({ kind, label, fields: fields.value }) : fields;
|
|
955
|
+
}
|
|
956
|
+
function readCollectionField(field) {
|
|
957
|
+
const { kind, label, input, path } = field;
|
|
958
|
+
const item = readFieldsAt(input["item"], path);
|
|
959
|
+
if (!item.isRead) return item;
|
|
960
|
+
if (RECORD_ID_KEY in item.value) {
|
|
961
|
+
return refused([...path, RECORD_ID_KEY], "record-id-field");
|
|
962
|
+
}
|
|
963
|
+
const itemTitle = input["itemTitle"];
|
|
964
|
+
if (itemTitle === void 0) return read({ kind, label, item: item.value });
|
|
965
|
+
const titleField = typeof itemTitle === "string" && Object.hasOwn(item.value, itemTitle) ? item.value[itemTitle] : void 0;
|
|
966
|
+
const canTitleRecord = titleField !== void 0 && TITLE_KINDS.has(titleField.kind);
|
|
967
|
+
if (typeof itemTitle !== "string" || !canTitleRecord) {
|
|
968
|
+
return refused(path, "invalid-item-title");
|
|
969
|
+
}
|
|
970
|
+
return read({ kind, label, item: item.value, itemTitle });
|
|
859
971
|
}
|
|
860
|
-
function
|
|
861
|
-
|
|
972
|
+
function firstUnknownCollection(schema, fields, path) {
|
|
973
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
974
|
+
const problem = unknownCollectionIn(schema, field, [...path, name]);
|
|
975
|
+
if (problem !== void 0) return problem;
|
|
976
|
+
}
|
|
977
|
+
return void 0;
|
|
978
|
+
}
|
|
979
|
+
function unknownCollectionIn(schema, field, path) {
|
|
980
|
+
switch (field.kind) {
|
|
981
|
+
case "reference":
|
|
982
|
+
case "references":
|
|
983
|
+
return referencesCollection(schema, field) ? void 0 : { path, reason: "unknown-collection" };
|
|
984
|
+
case "group":
|
|
985
|
+
return firstUnknownCollection(schema, field.fields, path);
|
|
986
|
+
case "collection":
|
|
987
|
+
return firstUnknownCollection(schema, field.item, path);
|
|
988
|
+
case "text":
|
|
989
|
+
case "richText":
|
|
990
|
+
case "number":
|
|
991
|
+
case "date":
|
|
992
|
+
case "image":
|
|
993
|
+
case "link":
|
|
994
|
+
case "toggle":
|
|
995
|
+
case "choice":
|
|
996
|
+
case "choices":
|
|
997
|
+
case "url":
|
|
998
|
+
case "time":
|
|
999
|
+
return void 0;
|
|
1000
|
+
}
|
|
862
1001
|
}
|
|
863
|
-
function
|
|
864
|
-
|
|
865
|
-
return fields === void 0 ? void 0 : { kind: "group", label, fields };
|
|
1002
|
+
function referencesCollection(schema, field) {
|
|
1003
|
+
return collectionFieldAt(schema, field.collection) !== void 0;
|
|
866
1004
|
}
|
|
867
|
-
function
|
|
868
|
-
const
|
|
869
|
-
if (
|
|
870
|
-
|
|
871
|
-
const isTitleKnown = typeof itemTitle === "string" && itemTitle in item;
|
|
872
|
-
if (itemTitle !== void 0 && !isTitleKnown) return void 0;
|
|
873
|
-
return {
|
|
874
|
-
kind: "collection",
|
|
875
|
-
label,
|
|
876
|
-
item,
|
|
877
|
-
...isTitleKnown ? { itemTitle } : {}
|
|
878
|
-
};
|
|
1005
|
+
function withOptions(field, path, options) {
|
|
1006
|
+
const isEveryOptionRead = options.every((option) => option !== void 0);
|
|
1007
|
+
if (!isEveryOptionRead) return refused(path, "invalid-option");
|
|
1008
|
+
return read(Object.assign({}, field, ...options));
|
|
879
1009
|
}
|
|
880
|
-
function optional(key,
|
|
881
|
-
|
|
1010
|
+
function optional(key, input, isOfKind) {
|
|
1011
|
+
const value = input[key];
|
|
1012
|
+
if (value === void 0) return {};
|
|
1013
|
+
if (!isOfKind(value)) return void 0;
|
|
882
1014
|
return { [key]: value };
|
|
883
1015
|
}
|
|
884
|
-
var FIELD_KINDS =
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
1016
|
+
var FIELD_KINDS = {
|
|
1017
|
+
text: true,
|
|
1018
|
+
richText: true,
|
|
1019
|
+
number: true,
|
|
1020
|
+
date: true,
|
|
1021
|
+
image: true,
|
|
1022
|
+
link: true,
|
|
1023
|
+
toggle: true,
|
|
1024
|
+
choice: true,
|
|
1025
|
+
choices: true,
|
|
1026
|
+
url: true,
|
|
1027
|
+
time: true,
|
|
1028
|
+
reference: true,
|
|
1029
|
+
references: true,
|
|
1030
|
+
group: true,
|
|
1031
|
+
collection: true
|
|
1032
|
+
};
|
|
901
1033
|
function isFieldKind(value) {
|
|
902
|
-
return typeof value === "string" &&
|
|
1034
|
+
return typeof value === "string" && Object.hasOwn(FIELD_KINDS, value);
|
|
903
1035
|
}
|
|
904
|
-
function
|
|
905
|
-
return
|
|
1036
|
+
function isFieldName(name) {
|
|
1037
|
+
return name !== "" && !name.includes(FIELD_PATH_SEPARATOR) && !isRecordIndex(name);
|
|
906
1038
|
}
|
|
907
|
-
function
|
|
908
|
-
return typeof value === "
|
|
1039
|
+
function isChoiceOption(value) {
|
|
1040
|
+
return isJsonObject(value) && typeof value["value"] === "string" && typeof value["label"] === "string";
|
|
909
1041
|
}
|
|
910
1042
|
function isBoolean(value) {
|
|
911
1043
|
return typeof value === "boolean";
|
|
@@ -916,6 +1048,12 @@ function isNumber(value) {
|
|
|
916
1048
|
function isCount(value) {
|
|
917
1049
|
return isNumber(value) && Number.isInteger(value) && value >= 0;
|
|
918
1050
|
}
|
|
1051
|
+
function read(value) {
|
|
1052
|
+
return { isRead: true, value };
|
|
1053
|
+
}
|
|
1054
|
+
function refused(path, reason) {
|
|
1055
|
+
return { isRead: false, problem: { path, reason } };
|
|
1056
|
+
}
|
|
919
1057
|
|
|
920
1058
|
// src/schema/site-config.ts
|
|
921
1059
|
var SITE_CONFIG_FILE = "seamlet.json";
|
|
@@ -927,7 +1065,7 @@ function isSiteId(value) {
|
|
|
927
1065
|
return typeof value === "string" && value.length <= LONGEST_SITE_ID && SITE_ID.test(value);
|
|
928
1066
|
}
|
|
929
1067
|
function readSiteConfig(input) {
|
|
930
|
-
const fields =
|
|
1068
|
+
const fields = isJsonObject(input) ? input : {};
|
|
931
1069
|
const { id, title } = fields;
|
|
932
1070
|
const cms = originOf(fields["cms"]);
|
|
933
1071
|
const hasId = isSiteId(id);
|
|
@@ -949,27 +1087,43 @@ function originOf(value) {
|
|
|
949
1087
|
const url2 = new URL(value);
|
|
950
1088
|
return WEB_PROTOCOLS.has(url2.protocol) ? url2.origin : void 0;
|
|
951
1089
|
}
|
|
952
|
-
function isText(value) {
|
|
953
|
-
return typeof value === "string" && value.trim() !== "";
|
|
954
|
-
}
|
|
955
|
-
function isRecord3(value) {
|
|
956
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
957
|
-
}
|
|
958
1090
|
|
|
959
1091
|
// src/schema/site-description.ts
|
|
960
1092
|
var SITE_DESCRIPTION_PATH = "/seamlet/site.json";
|
|
961
1093
|
var PAGE_PATH = /^(\/[a-z0-9][a-z0-9-]*)+$/u;
|
|
962
1094
|
var HOME_PAGE_PATH = "/";
|
|
963
1095
|
function readSiteDescription(input) {
|
|
964
|
-
|
|
1096
|
+
const reading = checkSiteDescription(input);
|
|
1097
|
+
return reading.status === "read" ? reading.description : void 0;
|
|
1098
|
+
}
|
|
1099
|
+
function checkSiteDescription(input) {
|
|
1100
|
+
if (!isJsonObject(input)) return invalid([{ code: "not-a-description" }]);
|
|
965
1101
|
const { id, title } = input;
|
|
966
|
-
const
|
|
967
|
-
const
|
|
968
|
-
if (
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
1102
|
+
const nameProblems = namingProblems(id, title);
|
|
1103
|
+
const schema = checkFields(input["schema"]);
|
|
1104
|
+
if (schema.status === "invalid") {
|
|
1105
|
+
const { problem } = schema;
|
|
1106
|
+
return invalid([...nameProblems, { code: "invalid-schema", problem }]);
|
|
1107
|
+
}
|
|
1108
|
+
const initial = parseContent(schema.fields, input["initialContent"]);
|
|
1109
|
+
const pages = checkPages(schema.fields, input["pages"]);
|
|
1110
|
+
const problems = [
|
|
1111
|
+
...nameProblems,
|
|
1112
|
+
...initialContentProblems(initial.issues),
|
|
1113
|
+
...pages.problems
|
|
1114
|
+
];
|
|
1115
|
+
const isNamed = isSiteId(id) && isText(title);
|
|
1116
|
+
if (!isNamed || pages.pages === void 0 || problems.length > 0) {
|
|
1117
|
+
return invalid(problems);
|
|
1118
|
+
}
|
|
1119
|
+
const description = {
|
|
1120
|
+
id,
|
|
1121
|
+
title,
|
|
1122
|
+
schema: schema.fields,
|
|
1123
|
+
initialContent: initial.content,
|
|
1124
|
+
pages: pages.pages
|
|
1125
|
+
};
|
|
1126
|
+
return { status: "read", description };
|
|
973
1127
|
}
|
|
974
1128
|
function isSearchTextsGroup(schema, path) {
|
|
975
1129
|
const target = resolveFieldPath(schema, path);
|
|
@@ -977,30 +1131,53 @@ function isSearchTextsGroup(schema, path) {
|
|
|
977
1131
|
const { title, description } = target.field.fields;
|
|
978
1132
|
return title?.kind === "text" && description?.kind === "text";
|
|
979
1133
|
}
|
|
980
|
-
function
|
|
981
|
-
|
|
1134
|
+
function namingProblems(id, title) {
|
|
1135
|
+
const idProblems = isSiteId(id) ? [] : [{ code: "invalid-id" }];
|
|
1136
|
+
const titleProblems = isText(title) ? [] : [{ code: "invalid-title" }];
|
|
1137
|
+
return [...idProblems, ...titleProblems];
|
|
1138
|
+
}
|
|
1139
|
+
function initialContentProblems(issues) {
|
|
1140
|
+
return issues.length > 0 ? [{ code: "invalid-initial-content", issues }] : [];
|
|
1141
|
+
}
|
|
1142
|
+
function checkPages(schema, input) {
|
|
1143
|
+
if (!Array.isArray(input) || input.length === 0) {
|
|
1144
|
+
return { pages: void 0, problems: [{ code: "no-pages" }] };
|
|
1145
|
+
}
|
|
982
1146
|
const entries = input;
|
|
983
|
-
const
|
|
984
|
-
const
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
const
|
|
988
|
-
|
|
1147
|
+
const readings = entries.map((entry) => readPage(schema, entry));
|
|
1148
|
+
const pageProblems = readings.flatMap(
|
|
1149
|
+
(reading, index) => typeof reading === "string" ? [{ code: "invalid-page", index, reason: reading }] : []
|
|
1150
|
+
);
|
|
1151
|
+
const pages = readings.filter((reading) => typeof reading !== "string");
|
|
1152
|
+
const problems = [...pageProblems, ...orderProblems(pages)];
|
|
1153
|
+
const [first, ...rest] = pages;
|
|
1154
|
+
const isSound = problems.length === 0 && first !== void 0;
|
|
1155
|
+
return { pages: isSound ? [first, ...rest] : void 0, problems };
|
|
1156
|
+
}
|
|
1157
|
+
function orderProblems(pages) {
|
|
1158
|
+
const paths = pages.map((page) => page.path);
|
|
1159
|
+
const repeated = paths.filter((path, index) => paths.indexOf(path) !== index);
|
|
1160
|
+
const isHomeMissing = paths.length > 0 && paths[0] !== HOME_PAGE_PATH;
|
|
1161
|
+
const homeProblems = isHomeMissing ? [{ code: "home-page-not-first" }] : [];
|
|
1162
|
+
const repeatProblems = [...new Set(repeated)].map(
|
|
1163
|
+
(path) => ({ code: "repeated-page-path", path })
|
|
1164
|
+
);
|
|
1165
|
+
return [...homeProblems, ...repeatProblems];
|
|
989
1166
|
}
|
|
990
1167
|
function readPage(schema, input) {
|
|
991
|
-
if (!
|
|
1168
|
+
if (!isJsonObject(input)) return "not-a-page";
|
|
992
1169
|
const { path, title, searchTexts } = input;
|
|
993
|
-
|
|
994
|
-
if (!
|
|
1170
|
+
if (!isPagePath(path)) return "invalid-path";
|
|
1171
|
+
if (!isText(title)) return "invalid-title";
|
|
995
1172
|
if (searchTexts === void 0) return { path, title };
|
|
996
1173
|
const isSearchTexts = typeof searchTexts === "string" && isSearchTextsGroup(schema, searchTexts);
|
|
997
|
-
return isSearchTexts ? { path, title, searchTexts } :
|
|
1174
|
+
return isSearchTexts ? { path, title, searchTexts } : "invalid-search-texts";
|
|
998
1175
|
}
|
|
999
|
-
function
|
|
1000
|
-
return typeof
|
|
1176
|
+
function isPagePath(path) {
|
|
1177
|
+
return typeof path === "string" && (path === HOME_PAGE_PATH || PAGE_PATH.test(path));
|
|
1001
1178
|
}
|
|
1002
|
-
function
|
|
1003
|
-
return
|
|
1179
|
+
function invalid(problems) {
|
|
1180
|
+
return { status: "invalid", problems };
|
|
1004
1181
|
}
|
|
1005
1182
|
|
|
1006
1183
|
export {
|
|
@@ -1022,13 +1199,17 @@ export {
|
|
|
1022
1199
|
collection,
|
|
1023
1200
|
defaultValue,
|
|
1024
1201
|
defaultContent,
|
|
1202
|
+
resolveFieldPath,
|
|
1203
|
+
isJsonObject,
|
|
1025
1204
|
linkTargetOf,
|
|
1205
|
+
safeHref,
|
|
1026
1206
|
hrefOf,
|
|
1027
1207
|
richTextOf,
|
|
1028
1208
|
plainTextOf,
|
|
1029
1209
|
asOneParagraph,
|
|
1030
1210
|
readRichText,
|
|
1031
1211
|
normalizedLine,
|
|
1212
|
+
linesOf,
|
|
1032
1213
|
RECORD_ID_KEY,
|
|
1033
1214
|
isRecordId,
|
|
1034
1215
|
newRecord,
|
|
@@ -1040,7 +1221,6 @@ export {
|
|
|
1040
1221
|
parseContent,
|
|
1041
1222
|
FIELD_PATH_ATTRIBUTE,
|
|
1042
1223
|
editable,
|
|
1043
|
-
resolveFieldPath,
|
|
1044
1224
|
REVEAL_ATTRIBUTE,
|
|
1045
1225
|
REVEALED_ATTRIBUTE,
|
|
1046
1226
|
REVEAL_EVENT,
|
|
@@ -1050,10 +1230,13 @@ export {
|
|
|
1050
1230
|
WEEKDAYS,
|
|
1051
1231
|
openingHoursField,
|
|
1052
1232
|
readFields,
|
|
1233
|
+
checkFields,
|
|
1053
1234
|
SITE_CONFIG_FILE,
|
|
1054
1235
|
isSiteId,
|
|
1055
1236
|
readSiteConfig,
|
|
1056
1237
|
SITE_DESCRIPTION_PATH,
|
|
1238
|
+
HOME_PAGE_PATH,
|
|
1057
1239
|
readSiteDescription,
|
|
1240
|
+
checkSiteDescription,
|
|
1058
1241
|
isSearchTextsGroup
|
|
1059
1242
|
};
|