@janga/norna 0.7.5 → 0.7.7

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.
Files changed (142) hide show
  1. package/README.md +5 -5
  2. package/astro.config.mjs +76 -0
  3. package/bin/norna-cli.mjs +2 -2
  4. package/docs/README.md +3 -3
  5. package/docs/commands.md +6 -6
  6. package/docs/configuration.md +3 -2
  7. package/docs/content.md +154 -95
  8. package/docs/design/site-examples-structure.md +1 -1
  9. package/docs/engine-development.md +4 -4
  10. package/docs/getting-started.md +6 -4
  11. package/docs/images-and-metadata.md +112 -20
  12. package/docs/routes.md +52 -33
  13. package/docs/site-structure.md +7 -6
  14. package/docs/theme.md +8 -7
  15. package/docs/typography.md +5 -5
  16. package/examples/dog-gallery/site/content.md +41 -46
  17. package/examples/dog-gallery/site/routes/{dog-care → 010-dog-care}/route-content.md +1 -4
  18. package/fixtures/basic/site/content.md +2 -4
  19. package/fixtures/content-model-v2/.astro/collections/site.schema.json +776 -0
  20. package/fixtures/content-model-v2/.astro/collections/theme.schema.json +668 -0
  21. package/fixtures/content-model-v2/.astro/content-assets.mjs +4 -0
  22. package/fixtures/content-model-v2/.astro/content-modules.mjs +1 -0
  23. package/fixtures/content-model-v2/.astro/content.d.ts +175 -0
  24. package/fixtures/content-model-v2/.astro/data-store.json +1 -0
  25. package/fixtures/content-model-v2/.astro/dev-local.json +8 -0
  26. package/fixtures/content-model-v2/.astro/dev.json +13 -0
  27. package/fixtures/content-model-v2/.astro/dev.log +231 -0
  28. package/fixtures/content-model-v2/.astro/settings.json +5 -0
  29. package/fixtures/content-model-v2/.astro/types.d.ts +2 -0
  30. package/fixtures/content-model-v2/dist/_astro/GalleryGrid.astro_astro_type_script_index_0_lang.B0kxDyGz.js +1 -0
  31. package/fixtures/content-model-v2/dist/_astro/SitePage.BbfZub4g.css +1 -0
  32. package/fixtures/content-model-v2/dist/guide/index.html +3 -0
  33. package/fixtures/content-model-v2/dist/images/generated/intro/detail-27930b14-480.webp +0 -0
  34. package/fixtures/content-model-v2/dist/images/generated/intro/detail-27930b14-768.webp +0 -0
  35. package/fixtures/content-model-v2/dist/images/generated/intro/detail-27930b14-971.webp +0 -0
  36. package/fixtures/content-model-v2/dist/images/generated/intro/duplicate-49c98f6c-480.webp +0 -0
  37. package/fixtures/content-model-v2/dist/images/generated/intro/duplicate-49c98f6c-768.webp +0 -0
  38. package/fixtures/content-model-v2/dist/images/generated/intro/duplicate-49c98f6c-971.webp +0 -0
  39. package/fixtures/content-model-v2/dist/images/generated/intro/hero-6d588a83-480.webp +0 -0
  40. package/fixtures/content-model-v2/dist/images/generated/intro/hero-6d588a83-768.webp +0 -0
  41. package/fixtures/content-model-v2/dist/images/generated/intro/hero-6d588a83-971.webp +0 -0
  42. package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-1080.webp +0 -0
  43. package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-1440.webp +0 -0
  44. package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-1600.webp +0 -0
  45. package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-480.webp +0 -0
  46. package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-768.webp +0 -0
  47. package/fixtures/content-model-v2/dist/images/generated/routes/010-guide/images/intro/duplicate-5cf614d2-480.webp +0 -0
  48. package/fixtures/content-model-v2/dist/images/generated/routes/010-guide/images/intro/duplicate-5cf614d2-768.webp +0 -0
  49. package/fixtures/content-model-v2/dist/images/generated/routes/010-guide/images/intro/duplicate-5cf614d2-960.webp +0 -0
  50. package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-1080.webp +0 -0
  51. package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-1440.webp +0 -0
  52. package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-1600.webp +0 -0
  53. package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-480.webp +0 -0
  54. package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-768.webp +0 -0
  55. package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-1080.webp +0 -0
  56. package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-1440.webp +0 -0
  57. package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-1600.webp +0 -0
  58. package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-480.webp +0 -0
  59. package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-768.webp +0 -0
  60. package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-1080.webp +0 -0
  61. package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-1440.webp +0 -0
  62. package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-1600.webp +0 -0
  63. package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-480.webp +0 -0
  64. package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-768.webp +0 -0
  65. package/fixtures/content-model-v2/dist/index.html +8 -0
  66. package/fixtures/content-model-v2/node_modules/.vite/deps/_metadata.json +80 -0
  67. package/fixtures/content-model-v2/node_modules/.vite/deps/astro-D_GIpJIE.js +434 -0
  68. package/fixtures/content-model-v2/node_modules/.vite/deps/astro-D_GIpJIE.js.map +1 -0
  69. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
  70. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
  71. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_axobject-query.js +2702 -0
  72. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
  73. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
  74. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
  75. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
  76. package/fixtures/content-model-v2/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
  77. package/fixtures/content-model-v2/node_modules/.vite/deps/audit-DM9Hkl5B.js +1534 -0
  78. package/fixtures/content-model-v2/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +1 -0
  79. package/fixtures/content-model-v2/node_modules/.vite/deps/embla-carousel.js +1389 -0
  80. package/fixtures/content-model-v2/node_modules/.vite/deps/embla-carousel.js.map +1 -0
  81. package/fixtures/content-model-v2/node_modules/.vite/deps/highlight-DnZ1ULOL.js +48 -0
  82. package/fixtures/content-model-v2/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +1 -0
  83. package/fixtures/content-model-v2/node_modules/.vite/deps/icons-RyebKi45.js +42 -0
  84. package/fixtures/content-model-v2/node_modules/.vite/deps/icons-RyebKi45.js.map +1 -0
  85. package/fixtures/content-model-v2/node_modules/.vite/deps/package.json +3 -0
  86. package/fixtures/content-model-v2/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +4 -0
  87. package/fixtures/content-model-v2/node_modules/.vite/deps/settings-DaU-gNnK.js +190 -0
  88. package/fixtures/content-model-v2/node_modules/.vite/deps/settings-DaU-gNnK.js.map +1 -0
  89. package/fixtures/content-model-v2/node_modules/.vite/deps/toolbar-63PA619H.js +458 -0
  90. package/fixtures/content-model-v2/node_modules/.vite/deps/toolbar-63PA619H.js.map +1 -0
  91. package/fixtures/content-model-v2/node_modules/.vite/deps/ui-library--bkcNSYz.js +1016 -0
  92. package/fixtures/content-model-v2/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +1 -0
  93. package/fixtures/content-model-v2/node_modules/.vite/deps/window-jPkpKVcy.js +32 -0
  94. package/fixtures/content-model-v2/node_modules/.vite/deps/window-jPkpKVcy.js.map +1 -0
  95. package/fixtures/content-model-v2/node_modules/.vite/deps/xray-BXCHpEJP.js +111 -0
  96. package/fixtures/content-model-v2/node_modules/.vite/deps/xray-BXCHpEJP.js.map +1 -0
  97. package/fixtures/content-model-v2/site/.norna/generated-images.json +210 -0
  98. package/fixtures/content-model-v2/site/config.mjs +11 -0
  99. package/fixtures/content-model-v2/site/content.md +84 -0
  100. package/fixtures/content-model-v2/site/images/card-section/adopt.svg +9 -0
  101. package/fixtures/content-model-v2/site/images/card-section/foster.svg +12 -0
  102. package/fixtures/content-model-v2/site/images/intro/detail.jpg +0 -0
  103. package/fixtures/content-model-v2/site/images/intro/duplicate.jpg +0 -0
  104. package/fixtures/content-model-v2/site/images/intro/hero.jpg +0 -0
  105. package/fixtures/content-model-v2/site/images/plain/image.jpg +0 -0
  106. package/fixtures/content-model-v2/site/images/timed/slide-one.jpg +0 -0
  107. package/fixtures/content-model-v2/site/images/timed/slide-three.jpg +0 -0
  108. package/fixtures/content-model-v2/site/images/timed/slide-two.jpg +0 -0
  109. package/fixtures/content-model-v2/site/routes/010-guide/images/intro/duplicate.jpg +0 -0
  110. package/fixtures/content-model-v2/site/routes/010-guide/route-content.md +17 -0
  111. package/fixtures/content-model-v2/site/theme.md +9 -0
  112. package/package.json +4 -2
  113. package/scripts/deploy-site.mjs +12 -5
  114. package/scripts/dev-local.mjs +6 -0
  115. package/scripts/generate-images.mjs +132 -56
  116. package/scripts/lib/image-dimensions.mjs +26 -0
  117. package/scripts/lib/norna-markdown-blocks.mjs +643 -0
  118. package/scripts/lib/route-model.mjs +17 -0
  119. package/scripts/lib/site-content.mjs +45 -37
  120. package/scripts/show-typography.mjs +18 -10
  121. package/scripts/sync-content-sections.mjs +280 -423
  122. package/scripts/test-content-check.mjs +59 -45
  123. package/scripts/test-content-model-v2.mjs +998 -0
  124. package/scripts/test-engine-commands.mjs +1 -1
  125. package/scripts/test-package-check.mjs +38 -11
  126. package/scripts/test-route-model.mjs +32 -0
  127. package/scripts/test-temporary-visibility.mjs +3 -6
  128. package/src/components/CardList.astro +77 -0
  129. package/src/components/GalleryGrid.astro +22 -12
  130. package/src/components/SiteNavigation.astro +5 -4
  131. package/src/components/SitePage.astro +12 -6
  132. package/src/components/SiteSection.astro +35 -13
  133. package/src/content.config.ts +8 -23
  134. package/src/lib/generatedImages.ts +50 -10
  135. package/src/lib/sectionContent.ts +130 -35
  136. package/src/lib/sitePages.ts +36 -15
  137. package/src/pages/[slug].astro +3 -3
  138. package/src/styles/global.css +187 -0
  139. package/starters/basic/site/content.md +10 -6
  140. package/starters/project/README.md +2 -2
  141. package/starters/project/site/content.md +0 -10
  142. package/starters/project/site/routes/{guide → 010-guide}/route-content.md +0 -6
@@ -10,14 +10,17 @@ import {
10
10
  siteRoutesLabel,
11
11
  siteThemeLabel,
12
12
  } from './site-paths.mjs';
13
+ import { parseRouteDirectory } from './route-model.mjs';
13
14
 
14
- export const supportedImageExtensions = new Set(['.jpg', '.jpeg', '.png']);
15
+ export const rasterImageExtensions = new Set(['.jpg', '.jpeg', '.png']);
16
+ export const staticImageExtensions = new Set(['.svg']);
17
+ export const supportedImageExtensions = new Set([...rasterImageExtensions, ...staticImageExtensions]);
15
18
 
16
19
  const h2Regex = /^##\s+.*$/gm;
17
20
  const explicitHeadingIdRegex = /\s*\{#([a-z0-9-]+)\}\s*$/;
18
21
  const inlineStyleReferenceRegex = /\[[^\]\n]+\]\{\.([a-z][a-z0-9-]*)\}/g;
19
22
  const frontmatterDelimiterRegex = /^---\s*$/;
20
- const knownContentTopLevelFrontmatterKeys = new Set(['title', 'description', 'slug', 'navigation', 'presentation', 'frame', 'sections']);
23
+ const knownContentTopLevelFrontmatterKeys = new Set(['title', 'description', 'navigation', 'presentation', 'frame', 'sections']);
21
24
  const knownThemeTopLevelFrontmatterKeys = new Set(['navigation', 'layout', 'gallery', 'typography', 'presentation', 'frame']);
22
25
  const knownNestedFrontmatterKeys = new Set([
23
26
  'align',
@@ -55,7 +58,6 @@ const knownNestedFrontmatterKeys = new Set([
55
58
  'mobile',
56
59
  'navigation',
57
60
  'overrides',
58
- 'order',
59
61
  'paragraphSpacing',
60
62
  'pageWidth',
61
63
  'preset',
@@ -85,7 +87,9 @@ export const getContentFiles = async () => {
85
87
  imagesDir: siteImagesDir,
86
88
  imagesLabel: siteImagesLabel,
87
89
  isHome: true,
88
- routeFolder: null,
90
+ routeDirectory: null,
91
+ routeId: null,
92
+ routeOrder: 0,
89
93
  }];
90
94
  const routeEntries = await readdir(siteRoutesDir, { withFileTypes: true }).catch((error) => {
91
95
  if (error?.code === 'ENOENT') {
@@ -98,19 +102,23 @@ export const getContentFiles = async () => {
98
102
  for (const entry of routeEntries) {
99
103
  if (!entry.isDirectory()) continue;
100
104
 
101
- const routeFolder = entry.name;
102
- const routeDir = path.join(siteRoutesDir, routeFolder);
105
+ const routeDirectory = entry.name;
106
+ const routeDir = path.join(siteRoutesDir, routeDirectory);
103
107
  const routeContentPath = path.join(routeDir, 'route-content.md');
104
108
 
105
109
  if (!(await fileExists(routeContentPath))) continue;
106
110
 
111
+ const { routeId, routeOrder } = parseRouteDirectory(routeDirectory, `${siteRoutesLabel}/${routeDirectory}`);
112
+
107
113
  contentFiles.push({
108
- contentLabel: `${siteRoutesLabel}/${routeFolder}/route-content.md`,
114
+ contentLabel: `${siteRoutesLabel}/${routeDirectory}/route-content.md`,
109
115
  contentPath: routeContentPath,
110
116
  imagesDir: path.join(routeDir, 'images'),
111
- imagesLabel: `${siteRoutesLabel}/${routeFolder}/images`,
117
+ imagesLabel: `${siteRoutesLabel}/${routeDirectory}/images`,
112
118
  isHome: false,
113
- routeFolder,
119
+ routeDirectory,
120
+ routeId,
121
+ routeOrder,
114
122
  });
115
123
  }
116
124
 
@@ -242,7 +250,7 @@ export const validateFrontmatterStructure = (frontmatter, addIssue, {
242
250
  if (knownTopLevelFrontmatterKeys.has(key)) continue;
243
251
 
244
252
  const fix = knownNestedFrontmatterKeys.has(key)
245
- ? `Indent "${key}:" under the section or object it belongs to. For image rows, "gallery:" normally belongs under a "sections" item.`
253
+ ? `Indent "${key}:" under the object it belongs to, or move image content into Norna Markdown blocks.`
246
254
  : `Move "${key}:" under the correct parent key, or remove it if it is not part of the ${fileKind} schema.`;
247
255
 
248
256
  addIssue({
@@ -271,7 +279,6 @@ export const getFrontmatterSections = (frontmatter) => {
271
279
  const sections = [];
272
280
  const lines = frontmatter.split(/\r?\n/);
273
281
  let inSections = false;
274
- let currentSection = null;
275
282
 
276
283
  for (const [index, line] of lines.entries()) {
277
284
  if (/^sections:\s*$/.test(line)) {
@@ -282,30 +289,9 @@ export const getFrontmatterSections = (frontmatter) => {
282
289
  if (!inSections) continue;
283
290
  if (/^[a-zA-Z0-9_-]+:/.test(line)) break;
284
291
 
285
- const sectionMatch = line.match(/^\s{2}-\s+id:\s*([a-z0-9-]+)\s*$/);
292
+ const sectionMatch = line.match(/^\s{2}([a-z0-9-]+):(?:\s+.*)?\s*$/);
286
293
  if (sectionMatch) {
287
- currentSection = { id: sectionMatch[1], images: [], imageReferences: [], carousels: [] };
288
- sections.push(currentSection);
289
- continue;
290
- }
291
-
292
- const carouselMatch = line.match(/^\s{6}-\s+carousel:\s*$/);
293
- if (carouselMatch && currentSection) {
294
- currentSection.carousels.push({ images: [], imageReferences: [], line: index + 1 });
295
- continue;
296
- }
297
-
298
- const imageMatch = line.match(/^\s{6,}-\s+image:\s*["']?([^"'\n]+)["']?\s*$/);
299
- if (imageMatch && currentSection) {
300
- const image = imageMatch[1].trim();
301
- const isCarouselImage = /^\s{10}-\s+image:/.test(line);
302
- const carousel = isCarouselImage ? currentSection.carousels.at(-1) : null;
303
- currentSection.images.push(image);
304
- currentSection.imageReferences.push({ image, line: index + 1 });
305
- if (carousel) {
306
- carousel.images.push(image);
307
- carousel.imageReferences.push({ image, line: index + 1 });
308
- }
294
+ sections.push({ id: sectionMatch[1], line: index + 1 });
309
295
  }
310
296
  }
311
297
 
@@ -361,8 +347,9 @@ export const getBodySections = (body) => {
361
347
  const text = body.slice(start, end).trimEnd();
362
348
  const heading = match[0];
363
349
  const id = getHeadingId(heading);
350
+ const line = body.slice(0, start).split(/\r?\n/).length;
364
351
 
365
- sections.push({ id, heading, text });
352
+ sections.push({ id, heading, line, text });
366
353
  }
367
354
 
368
355
  return { prelude, sections };
@@ -391,7 +378,7 @@ export const getImageFiles = async (directory) => {
391
378
  return files;
392
379
  };
393
380
 
394
- export const getImageIndex = async (contentDir, fail) => {
381
+ export const getImageIndex = async (contentDir, fail, contentLabel = siteImagesLabel) => {
395
382
  const imageFiles = await getImageFiles(contentDir);
396
383
  const imagesByName = new Map();
397
384
 
@@ -400,7 +387,7 @@ export const getImageIndex = async (contentDir, fail) => {
400
387
  const existingPath = imagesByName.get(imageName);
401
388
 
402
389
  if (existingPath) {
403
- fail(`Duplicate image filename "${imageName}" found at ${siteImagesLabel}/${toPosixPath(path.relative(contentDir, existingPath))} and ${siteImagesLabel}/${toPosixPath(path.relative(contentDir, imagePath))}. Image filenames must be globally unique.`);
390
+ fail(`Duplicate image filename "${imageName}" found at ${contentLabel}/${toPosixPath(path.relative(contentDir, existingPath))} and ${contentLabel}/${toPosixPath(path.relative(contentDir, imagePath))}. The filename is ambiguous within this page image root.`);
404
391
  continue;
405
392
  }
406
393
 
@@ -409,3 +396,24 @@ export const getImageIndex = async (contentDir, fail) => {
409
396
 
410
397
  return imagesByName;
411
398
  };
399
+
400
+ export const getImageCandidatesByName = async (contentDir) => {
401
+ const imageFiles = await getImageFiles(contentDir);
402
+ const imagesByName = new Map();
403
+
404
+ for (const imagePath of imageFiles) {
405
+ const imageName = path.basename(imagePath);
406
+
407
+ if (!imagesByName.has(imageName)) {
408
+ imagesByName.set(imageName, []);
409
+ }
410
+
411
+ imagesByName.get(imageName).push(imagePath);
412
+ }
413
+
414
+ for (const candidates of imagesByName.values()) {
415
+ candidates.sort((left, right) => left.localeCompare(right, 'sv'));
416
+ }
417
+
418
+ return imagesByName;
419
+ };
@@ -9,6 +9,7 @@ import {
9
9
  typographyRhythms,
10
10
  } from './lib/typography.mjs';
11
11
  import {
12
+ getBodySections,
12
13
  getContentFiles,
13
14
  readSiteFile,
14
15
  splitSiteFile,
@@ -268,11 +269,11 @@ const formatAnnotatedTypography = (annotated) => ({
268
269
  resolved: annotateResolvedValues(annotated.resolved, annotated.sources),
269
270
  });
270
271
 
271
- const getSectionBlocks = (lines) => {
272
+ const getSectionMetadataBlocks = (lines) => {
272
273
  const sectionsMap = findMap(lines, 'sections');
273
- if (!sectionsMap) return [];
274
+ if (!sectionsMap) return new Map();
274
275
 
275
- const sections = [];
276
+ const sections = new Map();
276
277
  let current = null;
277
278
 
278
279
  for (let index = sectionsMap.index + 1; index < lines.length; index += 1) {
@@ -282,15 +283,16 @@ const getSectionBlocks = (lines) => {
282
283
  const indent = countIndent(line);
283
284
  if (indent <= sectionsMap.indent) break;
284
285
 
285
- const sectionMatch = line.match(/^\s{2}-\s+id:\s*([a-z0-9-]+)\s*$/);
286
+ const sectionMatch = line.match(/^\s{2}([a-z0-9-]+):(?:\s+.*)?\s*$/);
286
287
  if (sectionMatch) {
287
288
  if (current) current.end = index;
288
289
  current = { id: sectionMatch[1], start: index, end: lines.length };
289
- sections.push(current);
290
+ sections.set(current.id, current);
290
291
  }
291
292
  }
292
293
 
293
- const finalSection = sections.at(-1);
294
+ const sectionList = Array.from(sections.values());
295
+ const finalSection = sectionList.at(-1);
294
296
  if (finalSection) {
295
297
  const afterSections = lines.findIndex((line, index) => (
296
298
  index > finalSection.start &&
@@ -328,7 +330,7 @@ const readThemeTypography = async () => {
328
330
  };
329
331
 
330
332
  const readPageTypography = async (contentFile, themeTypography) => {
331
- const { frontmatter, frontmatterBody } = await readSiteFile(contentFile.contentPath, contentFile.contentLabel);
333
+ const { frontmatter, frontmatterBody, body } = await readSiteFile(contentFile.contentPath, contentFile.contentLabel);
332
334
  const indentationIssues = [];
333
335
  validateFrontmatterIndentation(frontmatter, (issue) => indentationIssues.push(issue));
334
336
  validateContentFrontmatterStructure(frontmatter, (issue) => indentationIssues.push(issue));
@@ -345,8 +347,14 @@ const readPageTypography = async (contentFile, themeTypography) => {
345
347
  ? findMap(lines, 'typography', pagePresentation.index + 1, pagePresentation.nextIndex)?.value
346
348
  : null;
347
349
  const pageTypography = resolveAnnotatedTypographyOverride(themeTypography, pageTypographyConfig ?? undefined, contentFile.contentLabel);
348
- const sections = getSectionBlocks(lines).map((section) => {
349
- const presentation = findMap(lines, 'presentation', section.start + 1, section.end);
350
+ const sectionMetadataBlocks = getSectionMetadataBlocks(lines);
351
+ const sections = getBodySections(body).sections
352
+ .filter((section) => section.id)
353
+ .map((section) => {
354
+ const metadata = sectionMetadataBlocks.get(section.id);
355
+ const presentation = metadata
356
+ ? findMap(lines, 'presentation', metadata.start + 1, metadata.end)
357
+ : null;
350
358
  const typography = presentation
351
359
  ? findMap(lines, 'typography', presentation.index + 1, presentation.nextIndex)?.value
352
360
  : null;
@@ -359,7 +367,7 @@ const readPageTypography = async (contentFile, themeTypography) => {
359
367
 
360
368
  return {
361
369
  source: contentFile.contentLabel,
362
- route: contentFile.isHome ? '/' : `/${contentFile.routeFolder}/`,
370
+ route: contentFile.isHome ? '/' : `/${contentFile.routeId}/`,
363
371
  pageTypography,
364
372
  sections,
365
373
  };