@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.
@@ -35,6 +35,9 @@ function url(options) {
35
35
  function time(options) {
36
36
  return { kind: "time", ...options };
37
37
  }
38
+ function slug(options) {
39
+ return { kind: "slug", ...options };
40
+ }
38
41
  function reference(options) {
39
42
  return { kind: "reference", ...options };
40
43
  }
@@ -52,6 +55,7 @@ function collection(options) {
52
55
  function defaultValue(field) {
53
56
  switch (field.kind) {
54
57
  case "text":
58
+ case "slug":
55
59
  return "";
56
60
  case "toggle":
57
61
  return false;
@@ -118,6 +122,7 @@ function resolveInsideField(field, segments, path) {
118
122
  case "choices":
119
123
  case "url":
120
124
  case "time":
125
+ case "slug":
121
126
  case "reference":
122
127
  case "references":
123
128
  return void 0;
@@ -353,6 +358,7 @@ function collectionIn(fields, content, names) {
353
358
  case "choices":
354
359
  case "url":
355
360
  case "time":
361
+ case "slug":
356
362
  case "reference":
357
363
  case "references":
358
364
  return void 0;
@@ -379,6 +385,7 @@ function collectionFieldAt(schema, path) {
379
385
  case "choices":
380
386
  case "url":
381
387
  case "time":
388
+ case "slug":
382
389
  case "reference":
383
390
  case "references":
384
391
  return void 0;
@@ -412,6 +419,7 @@ function withIdsIn(field, value, newId) {
412
419
  case "choices":
413
420
  case "url":
414
421
  case "time":
422
+ case "slug":
415
423
  case "reference":
416
424
  case "references":
417
425
  return value;
@@ -467,6 +475,7 @@ function issuesInField(field, value, path, idsOf) {
467
475
  case "choices":
468
476
  case "url":
469
477
  case "time":
478
+ case "slug":
470
479
  return [];
471
480
  }
472
481
  }
@@ -488,6 +497,146 @@ function stringsOf(value) {
488
497
  return list.filter((id) => typeof id === "string");
489
498
  }
490
499
 
500
+ // src/schema/slug-checks.ts
501
+ function slugFieldOf(item) {
502
+ const named = Object.entries(item).find(([, field]) => field.kind === "slug");
503
+ return named?.[0];
504
+ }
505
+ function takenSlugIssues(schema, content) {
506
+ return issuesInFields2(schema, content, []);
507
+ }
508
+ function issuesInFields2(fields, content, path) {
509
+ return Object.entries(fields).flatMap(
510
+ ([name, field]) => issuesInField2(field, content[name], [...path, name])
511
+ );
512
+ }
513
+ function issuesInField2(field, value, path) {
514
+ switch (field.kind) {
515
+ case "group":
516
+ return isContentObject(value) ? issuesInFields2(field.fields, value, path) : [];
517
+ case "collection":
518
+ return issuesInCollection(field, recordsOf2(value), path);
519
+ case "text":
520
+ case "richText":
521
+ case "number":
522
+ case "date":
523
+ case "image":
524
+ case "link":
525
+ case "toggle":
526
+ case "choice":
527
+ case "choices":
528
+ case "url":
529
+ case "time":
530
+ case "slug":
531
+ case "reference":
532
+ case "references":
533
+ return [];
534
+ }
535
+ }
536
+ function issuesInCollection(collection2, records, path) {
537
+ const ownIssues = takenSlugsOf(collection2, records, path);
538
+ const innerIssues = records.flatMap(
539
+ (record, index) => issuesInFields2(collection2.item, record, [...path, index])
540
+ );
541
+ return [...ownIssues, ...innerIssues];
542
+ }
543
+ function takenSlugsOf(collection2, records, path) {
544
+ const slugName = slugFieldOf(collection2.item);
545
+ if (slugName === void 0) return [];
546
+ const ids = records.map((record) => record[RECORD_ID_KEY]);
547
+ const slugs = records.map((record) => record[slugName]);
548
+ return slugs.flatMap((slug2, index) => {
549
+ if (typeof slug2 !== "string" || slug2 === "") return [];
550
+ const isAnEarlierSlug = slugs.indexOf(slug2) < index;
551
+ const isAnotherId = ids.some(
552
+ (id, idIndex) => id === slug2 && idIndex !== index
553
+ );
554
+ const isTaken = isAnEarlierSlug || isAnotherId;
555
+ return isTaken ? [{ path: [...path, index, slugName], code: "taken-slug" }] : [];
556
+ });
557
+ }
558
+ function recordsOf2(value) {
559
+ return Array.isArray(value) ? value.filter(isContentObject) : [];
560
+ }
561
+
562
+ // src/schema/slugs.ts
563
+ var PAGE_SEGMENT = "[a-z0-9][a-z0-9-]*";
564
+ var MAX_SLUG_LENGTH = 80;
565
+ var SLUG_PATTERN = new RegExp(`^${PAGE_SEGMENT}$`, "u");
566
+ var NOT_SLUG_CHARACTERS = /[^a-z0-9]+/gu;
567
+ var EDGE_HYPHENS = /^-+|-+$/gu;
568
+ var COMBINING_MARKS = new RegExp("\\p{M}", "gu");
569
+ var HYPHEN = "-";
570
+ var TRANSLITERATION = new Map(
571
+ Object.entries({
572
+ \u0430: "a",
573
+ \u0431: "b",
574
+ \u0432: "v",
575
+ \u0433: "g",
576
+ \u0434: "d",
577
+ \u0435: "e",
578
+ \u0451: "e",
579
+ \u0436: "zh",
580
+ \u0437: "z",
581
+ \u0438: "i",
582
+ \u0439: "y",
583
+ \u043A: "k",
584
+ \u043B: "l",
585
+ \u043C: "m",
586
+ \u043D: "n",
587
+ \u043E: "o",
588
+ \u043F: "p",
589
+ \u0440: "r",
590
+ \u0441: "s",
591
+ \u0442: "t",
592
+ \u0443: "u",
593
+ \u0444: "f",
594
+ \u0445: "h",
595
+ \u0446: "ts",
596
+ \u0447: "ch",
597
+ \u0448: "sh",
598
+ \u0449: "sch",
599
+ \u044A: "",
600
+ \u044B: "y",
601
+ \u044C: "",
602
+ \u044D: "e",
603
+ \u044E: "yu",
604
+ \u044F: "ya",
605
+ // Letters of Ukrainian and Belarusian words a Russian text may quote.
606
+ \u0456: "i",
607
+ \u0457: "yi",
608
+ \u0454: "ye",
609
+ \u0491: "g",
610
+ \u045E: "u",
611
+ // «Баня 6×4» is `banya-6x4`, the way its size is said.
612
+ "\xD7": "x"
613
+ })
614
+ );
615
+ var TRANSLITERATED = new RegExp(
616
+ `[${[...TRANSLITERATION.keys()].join("")}]`,
617
+ "gu"
618
+ );
619
+ function isSlug(value) {
620
+ return SLUG_PATTERN.test(value);
621
+ }
622
+ function slugOf(words) {
623
+ const latin = words.normalize("NFC").toLowerCase().replace(
624
+ TRANSLITERATED,
625
+ (character) => TRANSLITERATION.get(character) ?? character
626
+ ).normalize("NFKD").replace(COMBINING_MARKS, "");
627
+ const hyphenated = latin.toLowerCase().replace(NOT_SLUG_CHARACTERS, HYPHEN).replace(EDGE_HYPHENS, "");
628
+ return shortened(hyphenated);
629
+ }
630
+ function shortened(slug2) {
631
+ if (slug2.length <= MAX_SLUG_LENGTH) return slug2;
632
+ const cut = slug2.slice(0, MAX_SLUG_LENGTH);
633
+ const isWordWhole = slug2[MAX_SLUG_LENGTH] === HYPHEN;
634
+ if (isWordWhole) return cut;
635
+ const lastHyphen = cut.lastIndexOf(HYPHEN);
636
+ const whole = lastHyphen > 0 ? cut.slice(0, lastHyphen) : cut;
637
+ return whole.replace(EDGE_HYPHENS, "");
638
+ }
639
+
491
640
  // src/schema/parse-content.ts
492
641
  var DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/u;
493
642
  var TIME_PATTERN = /^(?:[01]\d|2[0-3]):[0-5]\d$/u;
@@ -501,9 +650,10 @@ function countCharacters(text2) {
501
650
  function parseContent(schema, input) {
502
651
  const parsed = parseFields(schema, input, []);
503
652
  const missingRecords = missingRecordIssues(schema, parsed.value);
653
+ const takenSlugs = takenSlugIssues(schema, parsed.value);
504
654
  return {
505
655
  content: parsed.value,
506
- issues: [...parsed.issues, ...missingRecords]
656
+ issues: [...parsed.issues, ...missingRecords, ...takenSlugs]
507
657
  };
508
658
  }
509
659
  function parseFields(fields, input, path) {
@@ -547,6 +697,8 @@ function parseField(field, input, path) {
547
697
  return parseUrl(field, input, path);
548
698
  case "time":
549
699
  return parseTime(field, input, path);
700
+ case "slug":
701
+ return parseSlug(field, input, path);
550
702
  case "reference":
551
703
  return parseReference(field, input, path);
552
704
  case "references":
@@ -724,6 +876,17 @@ function parseTime(field, input, path) {
724
876
  }
725
877
  return valid(input);
726
878
  }
879
+ function parseSlug(field, input, path) {
880
+ if (typeof input !== "string") {
881
+ return invalidType(defaultValue(field), path);
882
+ }
883
+ if (input === "") {
884
+ return valid(input);
885
+ }
886
+ const formatIssues = isSlug(input) ? [] : [{ path, code: "invalid-slug" }];
887
+ const lengthIssues = tooLongIssues(MAX_SLUG_LENGTH, input, path);
888
+ return { value: input, issues: [...formatIssues, ...lengthIssues] };
889
+ }
727
890
  function parseCollection(field, input, path) {
728
891
  if (!Array.isArray(input)) {
729
892
  return invalidType(defaultValue(field), path);
@@ -863,6 +1026,30 @@ function openingHoursField(options = {}) {
863
1026
  }
864
1027
  });
865
1028
  }
1029
+ function productFields() {
1030
+ return {
1031
+ name: text({ label: "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435", maxLength: 120 }),
1032
+ slug: slug({ label: "\u0410\u0434\u0440\u0435\u0441 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u044B" }),
1033
+ photos: collection({
1034
+ label: "\u0424\u043E\u0442\u043E",
1035
+ item: { photo: image({ label: "\u0424\u043E\u0442\u043E" }) }
1036
+ }),
1037
+ summary: richText({ label: "\u041A\u0440\u0430\u0442\u043A\u043E", inline: true, maxLength: 200 }),
1038
+ description: richText({ label: "\u041E\u043F\u0438\u0441\u0430\u043D\u0438\u0435" }),
1039
+ price: number({ label: "\u0426\u0435\u043D\u0430, \u20BD", min: 0 }),
1040
+ priceNote: text({ label: "\u0423\u0442\u043E\u0447\u043D\u0435\u043D\u0438\u0435 \u0446\u0435\u043D\u044B", maxLength: 40 }),
1041
+ characteristics: collection({
1042
+ label: "\u0425\u0430\u0440\u0430\u043A\u0442\u0435\u0440\u0438\u0441\u0442\u0438\u043A\u0438",
1043
+ itemTitle: "name",
1044
+ item: {
1045
+ name: text({ label: "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435", maxLength: 60 }),
1046
+ value: text({ label: "\u0417\u043D\u0430\u0447\u0435\u043D\u0438\u0435", maxLength: 120 })
1047
+ }
1048
+ }),
1049
+ isHidden: toggle({ label: "\u0421\u043A\u0440\u044B\u0442\u044C \u0441 \u0441\u0430\u0439\u0442\u0430" }),
1050
+ seo: seoFields()
1051
+ };
1052
+ }
866
1053
 
867
1054
  // src/schema/read-fields.ts
868
1055
  var TITLE_KINDS = /* @__PURE__ */ new Set([
@@ -876,7 +1063,7 @@ function readFields(input) {
876
1063
  function checkFields(input) {
877
1064
  const reading = readFieldsAt(input, []);
878
1065
  if (!reading.isRead) return { status: "invalid", problem: reading.problem };
879
- const problem = firstUnknownCollection(reading.value, reading.value, []);
1066
+ const problem = firstUnknownCollection(reading.value, reading.value, []) ?? firstMisplacedSlug(reading.value, []);
880
1067
  return problem === void 0 ? { status: "read", fields: reading.value } : { status: "invalid", problem };
881
1068
  }
882
1069
  function readFieldsAt(input, path) {
@@ -922,6 +1109,7 @@ function readFieldOfKind(field) {
922
1109
  case "toggle":
923
1110
  case "url":
924
1111
  case "time":
1112
+ case "slug":
925
1113
  return read({ kind, label });
926
1114
  case "choice":
927
1115
  case "choices":
@@ -1002,9 +1190,51 @@ function unknownCollectionIn(schema, field, path) {
1002
1190
  case "choices":
1003
1191
  case "url":
1004
1192
  case "time":
1193
+ case "slug":
1194
+ return void 0;
1195
+ }
1196
+ }
1197
+ function firstMisplacedSlug(fields, path) {
1198
+ for (const [name, field] of Object.entries(fields)) {
1199
+ const problem = misplacedSlugIn(field, [...path, name]);
1200
+ if (problem !== void 0) return problem;
1201
+ }
1202
+ return void 0;
1203
+ }
1204
+ function misplacedSlugIn(field, path) {
1205
+ switch (field.kind) {
1206
+ case "slug":
1207
+ return { path, reason: "misplaced-slug" };
1208
+ case "group":
1209
+ return firstMisplacedSlug(field.fields, path);
1210
+ case "collection":
1211
+ return misplacedSlugInRecord(field.item, path);
1212
+ case "text":
1213
+ case "richText":
1214
+ case "number":
1215
+ case "date":
1216
+ case "image":
1217
+ case "link":
1218
+ case "toggle":
1219
+ case "choice":
1220
+ case "choices":
1221
+ case "url":
1222
+ case "time":
1223
+ case "reference":
1224
+ case "references":
1005
1225
  return void 0;
1006
1226
  }
1007
1227
  }
1228
+ function misplacedSlugInRecord(item, path) {
1229
+ const fields = Object.entries(item);
1230
+ const slugNames = fields.filter(([, field]) => field.kind === "slug").map(([name]) => name);
1231
+ const [, secondSlug] = slugNames;
1232
+ if (secondSlug !== void 0) {
1233
+ return { path: [...path, secondSlug], reason: "misplaced-slug" };
1234
+ }
1235
+ const otherFields = fields.filter(([, field]) => field.kind !== "slug");
1236
+ return firstMisplacedSlug(Object.fromEntries(otherFields), path);
1237
+ }
1008
1238
  function referencesCollection(schema, field) {
1009
1239
  return collectionFieldAt(schema, field.collection) !== void 0;
1010
1240
  }
@@ -1031,6 +1261,7 @@ var FIELD_KINDS = {
1031
1261
  choices: true,
1032
1262
  url: true,
1033
1263
  time: true,
1264
+ slug: true,
1034
1265
  reference: true,
1035
1266
  references: true,
1036
1267
  group: true,
@@ -1094,9 +1325,71 @@ function originOf(value) {
1094
1325
  return WEB_PROTOCOLS.has(url2.protocol) ? url2.origin : void 0;
1095
1326
  }
1096
1327
 
1328
+ // src/schema/record-pages.ts
1329
+ var RECORD_PLACEHOLDER = "[record]";
1330
+ var RECORD_PAGE_PATH = new RegExp(
1331
+ `^(/${PAGE_SEGMENT})+/${escaped(RECORD_PLACEHOLDER)}$`,
1332
+ "u"
1333
+ );
1334
+ var PAGE_PATH_SEPARATOR = "/";
1335
+ var NOT_FOUND = -1;
1336
+ function recordPages(schema, options) {
1337
+ const { shows, ...described } = options;
1338
+ const collection2 = collectionFieldAt(schema, described.collection);
1339
+ if (collection2 === void 0) {
1340
+ throw new Error(
1341
+ `@seamlet/astro: the schema has no collection "${described.collection}" for the pages at "${described.path}"`
1342
+ );
1343
+ }
1344
+ const isShown = (record) => shows === void 0 || shows(record);
1345
+ const pathOf = (record) => {
1346
+ const address = isShown(record) ? recordAddressOf(collection2, record) : void 0;
1347
+ return address === void 0 ? void 0 : recordPagePath(described.path, address);
1348
+ };
1349
+ return { ...described, isShown, pathOf };
1350
+ }
1351
+ function isRecordPagePath(path) {
1352
+ return typeof path === "string" && RECORD_PAGE_PATH.test(path);
1353
+ }
1354
+ function recordAddressOf(collection2, record) {
1355
+ const slugName = slugFieldOf(collection2.item);
1356
+ const slug2 = slugName === void 0 ? void 0 : record[slugName];
1357
+ if (typeof slug2 === "string" && isSlug(slug2)) return slug2;
1358
+ const id = record[RECORD_ID_KEY];
1359
+ return isRecordId(id) ? id : void 0;
1360
+ }
1361
+ function recordPagePath(pagePath, address) {
1362
+ return pagePath.replace(RECORD_PLACEHOLDER, address);
1363
+ }
1364
+ function recordPageAt(pages, path) {
1365
+ for (const page of pages) {
1366
+ const address = addressUnder(page.path, path);
1367
+ if (address !== void 0) return { page, address };
1368
+ }
1369
+ return void 0;
1370
+ }
1371
+ function recordAt(collection2, records, address) {
1372
+ const pageIndex = records.findIndex(
1373
+ (record2) => recordAddressOf(collection2, record2) === address
1374
+ );
1375
+ const index = pageIndex === NOT_FOUND ? records.findIndex((record2) => record2[RECORD_ID_KEY] === address) : pageIndex;
1376
+ const record = records[index];
1377
+ return record === void 0 ? void 0 : { record, index };
1378
+ }
1379
+ function addressUnder(recordPagePath2, path) {
1380
+ const prefix = recordPagePath2.slice(0, -RECORD_PLACEHOLDER.length);
1381
+ if (!path.startsWith(prefix)) return void 0;
1382
+ const address = path.slice(prefix.length);
1383
+ const isOneSegment = address !== "" && !address.includes(PAGE_PATH_SEPARATOR);
1384
+ return isOneSegment ? address : void 0;
1385
+ }
1386
+ function escaped(text2) {
1387
+ return text2.replace(/[[\]]/gu, "\\$&");
1388
+ }
1389
+
1097
1390
  // src/schema/site-description.ts
1098
1391
  var SITE_DESCRIPTION_PATH = "/seamlet/site.json";
1099
- var PAGE_PATH = /^(\/[a-z0-9][a-z0-9-]*)+$/u;
1392
+ var PAGE_PATH = new RegExp(`^(/${PAGE_SEGMENT})+$`, "u");
1100
1393
  var HOME_PAGE_PATH = "/";
1101
1394
  function readSiteDescription(input) {
1102
1395
  const reading = checkSiteDescription(input);
@@ -1113,13 +1406,18 @@ function checkSiteDescription(input) {
1113
1406
  }
1114
1407
  const initial = parseContent(schema.fields, input["initialContent"]);
1115
1408
  const pages = checkPages(schema.fields, input["pages"]);
1409
+ const recordPages2 = checkRecordPages(schema.fields, input["recordPages"]);
1116
1410
  const problems = [
1117
1411
  ...nameProblems,
1118
1412
  ...initialContentProblems(initial.issues),
1119
- ...pages.problems
1413
+ ...pages.problems,
1414
+ ...recordPages2.problems,
1415
+ ...placementProblems(pages.pages ?? [], recordPages2.pages ?? []),
1416
+ ...addressProblems(schema.fields, initial.content, recordPages2.pages ?? [])
1120
1417
  ];
1121
1418
  const isNamed = isSiteId(id) && isText(title);
1122
- if (!isNamed || pages.pages === void 0 || problems.length > 0) {
1419
+ const isWhole = pages.pages !== void 0 && recordPages2.pages !== void 0;
1420
+ if (!isNamed || !isWhole || problems.length > 0) {
1123
1421
  return invalid(problems);
1124
1422
  }
1125
1423
  const description = {
@@ -1127,7 +1425,8 @@ function checkSiteDescription(input) {
1127
1425
  title,
1128
1426
  schema: schema.fields,
1129
1427
  initialContent: initial.content,
1130
- pages: pages.pages
1428
+ pages: pages.pages,
1429
+ ...recordPages2.pages.length === 0 ? {} : { recordPages: recordPages2.pages }
1131
1430
  };
1132
1431
  return { status: "read", description };
1133
1432
  }
@@ -1179,6 +1478,76 @@ function readPage(schema, input) {
1179
1478
  const isSearchTexts = typeof searchTexts === "string" && isSearchTextsGroup(schema, searchTexts);
1180
1479
  return isSearchTexts ? { path, title, searchTexts } : "invalid-search-texts";
1181
1480
  }
1481
+ function checkRecordPages(schema, input) {
1482
+ if (input === void 0) return { pages: [], problems: [] };
1483
+ if (!Array.isArray(input)) {
1484
+ return { pages: void 0, problems: [{ code: "invalid-record-pages" }] };
1485
+ }
1486
+ const entries = input;
1487
+ const readings = entries.map((entry) => readRecordPage(schema, entry));
1488
+ const pageProblems = readings.flatMap(
1489
+ (reading, index) => typeof reading === "string" ? [{ code: "invalid-record-page", index, reason: reading }] : []
1490
+ );
1491
+ const pages = readings.filter((reading) => typeof reading !== "string");
1492
+ const problems = [...pageProblems, ...recordPageRepeats(pages)];
1493
+ return { pages: problems.length === 0 ? pages : void 0, problems };
1494
+ }
1495
+ function recordPageRepeats(pages) {
1496
+ const paths = pages.map((page) => page.path);
1497
+ const collections = pages.map((page) => page.collection);
1498
+ const repeatedPaths = paths.filter(
1499
+ (path, index) => paths.indexOf(path) !== index
1500
+ );
1501
+ const repeatedCollections = collections.filter(
1502
+ (collection2, index) => collections.indexOf(collection2) !== index
1503
+ );
1504
+ const pathProblems = [...new Set(repeatedPaths)].map(
1505
+ (path) => ({ code: "repeated-page-path", path })
1506
+ );
1507
+ const collectionProblems = [...new Set(repeatedCollections)].map(
1508
+ (collection2) => ({ code: "repeated-record-pages", collection: collection2 })
1509
+ );
1510
+ return [...pathProblems, ...collectionProblems];
1511
+ }
1512
+ function readRecordPage(schema, input) {
1513
+ if (!isJsonObject(input)) return "not-a-page";
1514
+ const { path, collection: collection2, searchTexts } = input;
1515
+ if (!isRecordPagePath(path)) return "invalid-path";
1516
+ if (typeof collection2 !== "string") return "unknown-collection";
1517
+ const field = collectionFieldAt(schema, collection2);
1518
+ if (field === void 0) return "unknown-collection";
1519
+ if (searchTexts === void 0) return { path, collection: collection2 };
1520
+ const isSearchTexts = typeof searchTexts === "string" && isSearchTextsGroup(field.item, searchTexts);
1521
+ return isSearchTexts ? { path, collection: collection2, searchTexts } : "invalid-search-texts";
1522
+ }
1523
+ function placementProblems(pages, recordPages2) {
1524
+ const misplaced = pages.filter(
1525
+ (page) => recordPageAt(recordPages2, page.path) !== void 0
1526
+ );
1527
+ return misplaced.map(
1528
+ (page) => ({ code: "page-among-record-pages", path: page.path })
1529
+ );
1530
+ }
1531
+ function addressProblems(schema, initialContent, recordPages2) {
1532
+ return recordPages2.flatMap((page) => {
1533
+ const found = collectionAt(schema, initialContent, page.collection);
1534
+ if (found === void 0) return [];
1535
+ const addresses = found.records.map(
1536
+ (record) => recordAddressOf(found.collection, record)
1537
+ );
1538
+ return addresses.flatMap(
1539
+ (_, index) => recordAddressProblems(page.collection, addresses, index)
1540
+ );
1541
+ });
1542
+ }
1543
+ function recordAddressProblems(collection2, addresses, index) {
1544
+ const address = addresses[index];
1545
+ if (address === void 0) {
1546
+ return [{ code: "record-without-address", collection: collection2, index }];
1547
+ }
1548
+ const isRepeated = addresses.indexOf(address) < index;
1549
+ return isRepeated ? [{ code: "repeated-record-address", collection: collection2, address }] : [];
1550
+ }
1182
1551
  function isPagePath(path) {
1183
1552
  return typeof path === "string" && (path === HOME_PAGE_PATH || PAGE_PATH.test(path));
1184
1553
  }
@@ -1199,14 +1568,17 @@ export {
1199
1568
  choices,
1200
1569
  url,
1201
1570
  time,
1571
+ slug,
1202
1572
  reference,
1203
1573
  references,
1204
1574
  group,
1205
1575
  collection,
1206
1576
  defaultValue,
1207
1577
  defaultContent,
1578
+ FIELD_PATH_SEPARATOR,
1208
1579
  resolveFieldPath,
1209
1580
  isJsonObject,
1581
+ isContentObject,
1210
1582
  linkTargetOf,
1211
1583
  safeHref,
1212
1584
  hrefOf,
@@ -1221,7 +1593,12 @@ export {
1221
1593
  newRecord,
1222
1594
  withRecordIds,
1223
1595
  collectionAt,
1596
+ collectionFieldAt,
1224
1597
  findRecord,
1598
+ slugFieldOf,
1599
+ MAX_SLUG_LENGTH,
1600
+ isSlug,
1601
+ slugOf,
1225
1602
  formatFieldPath,
1226
1603
  countCharacters,
1227
1604
  parseContent,
@@ -1235,11 +1612,20 @@ export {
1235
1612
  contactsFields,
1236
1613
  WEEKDAYS,
1237
1614
  openingHoursField,
1615
+ productFields,
1238
1616
  readFields,
1239
1617
  checkFields,
1240
1618
  SITE_CONFIG_FILE,
1241
1619
  isSiteId,
1242
1620
  readSiteConfig,
1621
+ RECORD_PLACEHOLDER,
1622
+ PAGE_PATH_SEPARATOR,
1623
+ recordPages,
1624
+ isRecordPagePath,
1625
+ recordAddressOf,
1626
+ recordPagePath,
1627
+ recordPageAt,
1628
+ recordAt,
1243
1629
  SITE_DESCRIPTION_PATH,
1244
1630
  HOME_PAGE_PATH,
1245
1631
  readSiteDescription,
@@ -6,7 +6,7 @@ import {
6
6
  readRichText,
7
7
  resolveFieldPath,
8
8
  safeHref
9
- } from "./chunk-ZS32CW5F.js";
9
+ } from "./chunk-UL2C2IB2.js";
10
10
 
11
11
  // src/bridge/editor-shortcuts.ts
12
12
  function shortcutOf(event) {
package/dist/cli.js CHANGED
@@ -1,12 +1,12 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ SITE_MODULE_FILE
4
+ } from "./chunk-QCVWHHJC.js";
2
5
  import {
3
6
  SITE_CONFIG_FILE,
4
7
  isSiteId,
5
8
  readSiteConfig
6
- } from "./chunk-ZS32CW5F.js";
7
- import {
8
- SITE_MODULE_FILE
9
- } from "./chunk-QCVWHHJC.js";
9
+ } from "./chunk-UL2C2IB2.js";
10
10
 
11
11
  // src/cli.ts
12
12
  import process from "process";
package/dist/index.js CHANGED
@@ -1,12 +1,6 @@
1
1
  import {
2
2
  OWN_PHOTOS_PATH
3
3
  } from "./chunk-W2LIOR2C.js";
4
- import {
5
- SITE_CONFIG_FILE,
6
- SITE_DESCRIPTION_PATH,
7
- isJsonObject,
8
- readSiteConfig
9
- } from "./chunk-ZS32CW5F.js";
10
4
  import {
11
5
  PREVIEW_PATH,
12
6
  SITE_MODULE_FILE,
@@ -14,7 +8,14 @@ import {
14
8
  } from "./chunk-QCVWHHJC.js";
15
9
  import {
16
10
  isUploadedPhoto
17
- } from "./chunk-QL4K4HR6.js";
11
+ } from "./chunk-OCA2WYK3.js";
12
+ import "./chunk-HFPZVDST.js";
13
+ import {
14
+ SITE_CONFIG_FILE,
15
+ SITE_DESCRIPTION_PATH,
16
+ isJsonObject,
17
+ readSiteConfig
18
+ } from "./chunk-UL2C2IB2.js";
18
19
 
19
20
  // src/index.ts
20
21
  import process from "process";
@@ -1,4 +1,4 @@
1
- import { R as RichText, F as Fields, a as Content, b as FieldPath } from './parse-content-Eg8yRq_t.js';
1
+ import { R as RichText, F as Fields, C as Content, a as FieldPath } from './parse-content-Bnn6Wa-l.js';
2
2
 
3
3
  interface Box {
4
4
  readonly top: number;