@janga/norna 0.7.16 → 0.7.17

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 (200) hide show
  1. package/README.md +6 -6
  2. package/astro.config.mjs +35 -3
  3. package/docs/README.md +10 -10
  4. package/docs/commands.md +2 -2
  5. package/docs/content.md +82 -102
  6. package/docs/design/navigation-and-theme-plan.md +115 -0
  7. package/docs/design/norna-diagram-design.md +9 -9
  8. package/docs/design/site-examples-structure.md +9 -9
  9. package/docs/engine-development.md +9 -9
  10. package/docs/getting-started.md +20 -15
  11. package/docs/images-and-metadata.md +10 -9
  12. package/docs/pages.md +134 -0
  13. package/docs/public-files.md +49 -36
  14. package/docs/publishing.md +19 -3
  15. package/docs/site-files.md +29 -28
  16. package/docs/sitewide-content.md +20 -36
  17. package/docs/theme.md +20 -20
  18. package/docs/typography.md +10 -10
  19. package/examples/README.md +2 -2
  20. package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/site.schema.json +17 -41
  21. package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/sitewide.schema.json +26 -67
  22. package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/theme.schema.json +185 -88
  23. package/examples/complete-sites/dog-shelter-multi-page/.astro/data-store.json +1 -1
  24. package/examples/complete-sites/dog-shelter-multi-page/.astro/dev-local.json +4 -4
  25. package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.json +5 -5
  26. package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.log +33 -23
  27. package/examples/complete-sites/dog-shelter-multi-page/README.md +1 -1
  28. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/_metadata.json +8 -8
  29. package/examples/complete-sites/dog-shelter-multi-page/site/.norna/generated-images.json +20 -20
  30. package/examples/complete-sites/dog-shelter-multi-page/site/content.md +3 -4
  31. package/examples/complete-sites/dog-shelter-multi-page/site/{routes → pages}/010-dogs/content.md +3 -4
  32. package/examples/complete-sites/dog-shelter-multi-page/site/{routes → pages}/020-adopt/content.md +3 -4
  33. package/examples/complete-sites/dog-shelter-multi-page/site/sitewide-content.yaml +2 -4
  34. package/examples/complete-sites/dog-shelter-single-page/.astro/collections/site.schema.json +17 -41
  35. package/examples/complete-sites/dog-shelter-single-page/.astro/collections/sitewide.schema.json +26 -67
  36. package/examples/complete-sites/dog-shelter-single-page/.astro/collections/theme.schema.json +185 -88
  37. package/examples/complete-sites/dog-shelter-single-page/.astro/data-store.json +1 -1
  38. package/examples/complete-sites/dog-shelter-single-page/.astro/dev-local.json +8 -0
  39. package/examples/complete-sites/dog-shelter-single-page/.astro/dev.json +5 -5
  40. package/examples/complete-sites/dog-shelter-single-page/.astro/dev.log +16 -12
  41. package/examples/complete-sites/dog-shelter-single-page/README.md +1 -1
  42. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/_metadata.json +8 -8
  43. package/examples/complete-sites/dog-shelter-single-page/site/.norna/generated-images.json +12 -12
  44. package/examples/complete-sites/dog-shelter-single-page/site/content.md +5 -2
  45. package/examples/complete-sites/dog-shelter-single-page/site/sitewide-content.yaml +2 -4
  46. package/examples/feature-demos/media-and-surfaces/.astro/collections/site.schema.json +17 -41
  47. package/examples/feature-demos/media-and-surfaces/.astro/collections/sitewide.schema.json +26 -67
  48. package/examples/feature-demos/media-and-surfaces/.astro/collections/theme.schema.json +185 -88
  49. package/examples/feature-demos/media-and-surfaces/.astro/data-store.json +1 -1
  50. package/examples/feature-demos/media-and-surfaces/.astro/dev.log +4 -14
  51. package/examples/feature-demos/media-and-surfaces/README.md +2 -2
  52. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/_metadata.json +9 -9
  53. package/examples/feature-demos/media-and-surfaces/site/.norna/generated-images.json +16 -16
  54. package/examples/feature-demos/media-and-surfaces/site/content.md +4 -7
  55. package/examples/feature-demos/media-and-surfaces/site/{routes → pages}/010-media/content.md +6 -5
  56. package/examples/feature-demos/media-and-surfaces/site/{routes → pages}/020-surfaces/content.md +7 -6
  57. package/examples/feature-demos/media-and-surfaces/site/sitewide-content.yaml +0 -2
  58. package/examples/feature-demos/sitewide-content/README.md +3 -3
  59. package/examples/feature-demos/sitewide-content/site/content.md +8 -7
  60. package/examples/feature-demos/sitewide-content/site/pages/010-logo-and-navigation/content.md +17 -0
  61. package/examples/feature-demos/sitewide-content/site/{routes → pages}/020-notices/content.md +7 -6
  62. package/examples/feature-demos/sitewide-content/site/{routes → pages}/030-footer/content.md +6 -5
  63. package/examples/feature-demos/sitewide-content/site/sitewide-content.yaml +4 -13
  64. package/examples/feature-demos/theme-presets/README.md +1 -1
  65. package/examples/feature-demos/theme-presets/site/.norna/generated-images.json +60 -60
  66. package/examples/feature-demos/theme-presets/site/content.md +4 -7
  67. package/examples/feature-demos/theme-presets/site/{routes → pages}/010-portfolio/content.md +4 -7
  68. package/examples/feature-demos/theme-presets/site/{routes → pages}/020-documentation/content.md +4 -7
  69. package/examples/feature-demos/theme-presets/site/{routes → pages}/030-project/content.md +4 -7
  70. package/examples/feature-demos/theme-presets/site/{routes → pages}/040-statement/content.md +4 -7
  71. package/examples/feature-demos/theme-presets/site/sitewide-content.yaml +1 -10
  72. package/fixtures/basic/site/content.md +5 -6
  73. package/fixtures/basic/site/sitewide-content.yaml +1 -8
  74. package/fixtures/content-model-v2/site/content.md +11 -13
  75. package/fixtures/content-model-v2/site/pages/010-guide/content.md +18 -0
  76. package/fixtures/nested-pages/site/.norna/generated-images.json +10 -0
  77. package/fixtures/nested-pages/site/config.yaml +1 -0
  78. package/fixtures/nested-pages/site/content.md +15 -0
  79. package/fixtures/nested-pages/site/pages/010-guides/content.md +15 -0
  80. package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/content.md +21 -0
  81. package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/images/diagram.svg +21 -0
  82. package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/pages/010-macos/content.md +14 -0
  83. package/fixtures/nested-pages/site/pages/010-guides/pages/020-workflows/content.md +11 -0
  84. package/fixtures/nested-pages/site/pages/010-guides/theme.yaml +3 -0
  85. package/fixtures/nested-pages/site/pages/020-reference/content.md +10 -0
  86. package/fixtures/nested-pages/site/pages/020-reference/pages/010-installation/content.md +11 -0
  87. package/fixtures/nested-pages/site/theme.yaml +3 -0
  88. package/package.json +7 -4
  89. package/schemas/config.schema.json +5 -5
  90. package/schemas/content-frontmatter.schema.json +28 -63
  91. package/schemas/manifest.json +1 -0
  92. package/schemas/sitewide-content.schema.json +58 -132
  93. package/schemas/theme.schema.json +396 -86
  94. package/scripts/check-config.mjs +6 -5
  95. package/scripts/deploy-site.mjs +6 -8
  96. package/scripts/doctor.mjs +2 -2
  97. package/scripts/generate-images.mjs +11 -18
  98. package/scripts/generate-schemas.mjs +3 -217
  99. package/scripts/lib/base-path-redirect.mjs +9 -0
  100. package/scripts/lib/editor-language-service.mjs +126 -66
  101. package/scripts/lib/heading-ids.mjs +161 -0
  102. package/scripts/lib/navigation-model.mjs +51 -0
  103. package/scripts/lib/norna-markdown-blocks.mjs +3 -4
  104. package/scripts/lib/page-model.mjs +83 -0
  105. package/scripts/lib/project-config.mjs +28 -0
  106. package/scripts/lib/schema-definitions.mjs +41 -46
  107. package/scripts/lib/schema-editor-metadata.mjs +475 -0
  108. package/scripts/lib/schema-value-definitions.mjs +6 -6
  109. package/scripts/lib/site-content.mjs +124 -87
  110. package/scripts/lib/site-paths.mjs +2 -2
  111. package/scripts/lib/theme-config.mjs +9 -16
  112. package/scripts/lib/theme-presets.mjs +9 -1
  113. package/scripts/release.mjs +1 -1
  114. package/scripts/show-typography.mjs +26 -20
  115. package/scripts/sync-content-sections.mjs +106 -65
  116. package/scripts/{test-temporary-visibility.mjs → test-banner-visibility.mjs} +7 -24
  117. package/scripts/test-client-javascript.mjs +39 -42
  118. package/scripts/test-content-check.mjs +138 -54
  119. package/scripts/test-content-model-v2.mjs +445 -228
  120. package/scripts/test-documentation.mjs +6 -1
  121. package/scripts/test-editor-language-service.mjs +168 -24
  122. package/scripts/test-engine-commands.mjs +2 -1
  123. package/scripts/test-heading-ids.mjs +68 -0
  124. package/scripts/test-navigation-model.mjs +63 -0
  125. package/scripts/test-nested-pages.mjs +110 -0
  126. package/scripts/test-package-check.mjs +31 -40
  127. package/scripts/test-page-model.mjs +80 -0
  128. package/scripts/test-project-config.mjs +25 -1
  129. package/scripts/test-schemas.mjs +66 -14
  130. package/scripts/test-theme-presets.mjs +33 -24
  131. package/src/components/NavigationPageTree.astro +68 -0
  132. package/src/components/SectionNavigationScript.astro +110 -5
  133. package/src/components/SiteBreadcrumbs.astro +29 -0
  134. package/src/components/SiteNavigation.astro +65 -69
  135. package/src/components/SitePage.astro +70 -46
  136. package/src/components/SiteSection.astro +27 -20
  137. package/src/content.config.ts +9 -5
  138. package/src/layouts/BaseLayout.astro +11 -20
  139. package/src/lib/pageThemes.ts +42 -0
  140. package/src/lib/sectionContent.ts +109 -106
  141. package/src/lib/siteNavigation.ts +48 -0
  142. package/src/lib/sitePages.ts +85 -38
  143. package/src/pages/{[slug].astro → [...slug].astro} +2 -2
  144. package/src/styles/global.css +373 -55
  145. package/starters/basic/README.md +5 -7
  146. package/starters/basic/site/content.md +7 -4
  147. package/starters/basic/site/sitewide-content.yaml +1 -10
  148. package/starters/project/README.md +8 -8
  149. package/starters/project/site/content.md +5 -4
  150. package/starters/project/site/{routes → pages}/010-guide/content.md +5 -4
  151. package/starters/project/site/sitewide-content.yaml +1 -10
  152. package/docs/routes.md +0 -121
  153. package/examples/feature-demos/sitewide-content/site/routes/010-identity/content.md +0 -20
  154. package/fixtures/content-model-v2/site/routes/010-guide/content.md +0 -17
  155. package/scripts/lib/route-model.mjs +0 -17
  156. package/scripts/test-route-model.mjs +0 -32
  157. package/src/lib/routeThemes.ts +0 -41
  158. /package/examples/complete-sites/dog-shelter-multi-page/site/images/{intro/shelter-dog.jpg → shelter-dog.jpg} +0 -0
  159. /package/examples/complete-sites/dog-shelter-multi-page/site/{routes/010-dogs/images/dogs → pages/010-dogs/images}/rex.jpg +0 -0
  160. /package/examples/complete-sites/dog-shelter-multi-page/site/{routes/010-dogs/images/dogs → pages/010-dogs/images}/rover.jpg +0 -0
  161. /package/examples/complete-sites/dog-shelter-multi-page/site/{routes/020-adopt/images/adopt → pages/020-adopt/images}/adoption-dog.jpg +0 -0
  162. /package/examples/complete-sites/dog-shelter-single-page/site/images/{you-can-help/foster-dog.jpg → foster-dog.jpg} +0 -0
  163. /package/examples/complete-sites/dog-shelter-single-page/site/images/{what-we-do/rescue-dog.jpg → rescue-dog.jpg} +0 -0
  164. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/cards → pages/010-media/images}/card-carousel.svg +0 -0
  165. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/cards → pages/010-media/images}/card-stack.svg +0 -0
  166. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/cards → pages/010-media/images}/card-surfaces.svg +0 -0
  167. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/carousel → pages/010-media/images}/carousel-one.svg +0 -0
  168. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/carousel → pages/010-media/images}/carousel-three.svg +0 -0
  169. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/carousel → pages/010-media/images}/carousel-two.svg +0 -0
  170. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/stack → pages/010-media/images}/stack-one.svg +0 -0
  171. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/stack → pages/010-media/images}/stack-two.svg +0 -0
  172. /package/examples/feature-demos/media-and-surfaces/site/{routes → pages}/020-surfaces/theme.yaml +0 -0
  173. /package/examples/feature-demos/theme-presets/site/{routes/010-portfolio/images/portfolio → pages/010-portfolio/images}/puppy-portrait.jpg +0 -0
  174. /package/examples/feature-demos/theme-presets/site/{routes/010-portfolio/images/portfolio → pages/010-portfolio/images}/shepherd-portrait.jpg +0 -0
  175. /package/examples/feature-demos/theme-presets/site/{routes/010-portfolio/images/portfolio → pages/010-portfolio/images}/white-puppy.jpg +0 -0
  176. /package/examples/feature-demos/theme-presets/site/{routes → pages}/010-portfolio/theme.yaml +0 -0
  177. /package/examples/feature-demos/theme-presets/site/{routes/020-documentation/images/documentation → pages/020-documentation/images}/black-schnauzer.jpg +0 -0
  178. /package/examples/feature-demos/theme-presets/site/{routes/020-documentation/images/documentation → pages/020-documentation/images}/retriever-portrait.jpg +0 -0
  179. /package/examples/feature-demos/theme-presets/site/{routes/020-documentation/images/documentation → pages/020-documentation/images}/schnauzer-portrait.jpg +0 -0
  180. /package/examples/feature-demos/theme-presets/site/{routes → pages}/020-documentation/theme.yaml +0 -0
  181. /package/examples/feature-demos/theme-presets/site/{routes/030-project/images/project → pages/030-project/images}/boxer-portrait.jpg +0 -0
  182. /package/examples/feature-demos/theme-presets/site/{routes/030-project/images/project → pages/030-project/images}/grass-puppy.jpg +0 -0
  183. /package/examples/feature-demos/theme-presets/site/{routes/030-project/images/project → pages/030-project/images}/grey-street-dog.jpg +0 -0
  184. /package/examples/feature-demos/theme-presets/site/{routes → pages}/030-project/theme.yaml +0 -0
  185. /package/examples/feature-demos/theme-presets/site/{routes/040-statement/images/statement → pages/040-statement/images}/black-pedigree.jpg +0 -0
  186. /package/examples/feature-demos/theme-presets/site/{routes/040-statement/images/statement → pages/040-statement/images}/sheepdog-grass.jpg +0 -0
  187. /package/examples/feature-demos/theme-presets/site/{routes/040-statement/images/statement → pages/040-statement/images}/sheepdog-wide.jpg +0 -0
  188. /package/examples/feature-demos/theme-presets/site/{routes → pages}/040-statement/theme.yaml +0 -0
  189. /package/fixtures/basic/site/images/{work/.gitkeep → .gitkeep} +0 -0
  190. /package/fixtures/content-model-v2/site/images/{card-section/adopt.svg → adopt.svg} +0 -0
  191. /package/fixtures/content-model-v2/site/images/{intro/detail.jpg → detail.jpg} +0 -0
  192. /package/fixtures/content-model-v2/site/images/{intro/duplicate.jpg → duplicate.jpg} +0 -0
  193. /package/fixtures/content-model-v2/site/images/{card-section/foster.svg → foster.svg} +0 -0
  194. /package/fixtures/content-model-v2/site/images/{intro/hero.jpg → hero.jpg} +0 -0
  195. /package/fixtures/content-model-v2/site/images/{plain/image.jpg → image.jpg} +0 -0
  196. /package/fixtures/content-model-v2/site/images/{timed/slide-one.jpg → slide-one.jpg} +0 -0
  197. /package/fixtures/content-model-v2/site/images/{timed/slide-three.jpg → slide-three.jpg} +0 -0
  198. /package/fixtures/content-model-v2/site/images/{timed/slide-two.jpg → slide-two.jpg} +0 -0
  199. /package/fixtures/content-model-v2/site/{routes/010-guide/images/intro → pages/010-guide/images}/duplicate.jpg +0 -0
  200. /package/starters/basic/site/images/{work/.gitkeep → .gitkeep} +0 -0
@@ -1,5 +1,5 @@
1
1
  import type { CollectionEntry } from 'astro:content';
2
- import { parseRouteDirectory } from '../../scripts/lib/route-model.mjs';
2
+ import { decodePageDirectoryPath, parsePageDirectoryPath } from '../../scripts/lib/page-model.mjs';
3
3
 
4
4
  type SiteEntry = CollectionEntry<'site'>;
5
5
 
@@ -7,64 +7,109 @@ export type SitePage = {
7
7
  entry: SiteEntry;
8
8
  isHome: boolean;
9
9
  navigation: {
10
- include: boolean;
11
- label: string;
10
+ listed: boolean;
12
11
  order: number;
13
12
  };
13
+ depth: number;
14
+ pageDirectories: string[];
14
15
  pathSegment: string;
15
16
  pathname: string;
16
- routeDirectory: string | null;
17
- routeId: string;
17
+ pageDirectory: string | null;
18
+ pageId: string;
19
+ pageIds: string[];
20
+ pageOrders: number[];
21
+ pagePath: string;
22
+ parentPagePath: string | null;
18
23
  title: string;
19
24
  };
20
25
 
21
- const routeIdMarker = '-route-';
26
+ const pageIdMarker = '-page-';
22
27
 
23
- const stripRouteIdPrefix = (id: string) => {
24
- const markerIndex = id.indexOf(routeIdMarker);
25
- return markerIndex === -1 ? null : id.slice(markerIndex + routeIdMarker.length);
28
+ const stripPageIdPrefix = (id: string) => {
29
+ const markerIndex = id.lastIndexOf(pageIdMarker);
30
+ if (markerIndex === -1) return null;
31
+
32
+ const candidate = id.slice(markerIndex + pageIdMarker.length);
33
+ try {
34
+ return parsePageDirectoryPath(decodePageDirectoryPath(candidate)).pageDirectory;
35
+ } catch {
36
+ return null;
37
+ }
26
38
  };
27
39
 
28
- export const isHomePageEntry = (entry: SiteEntry) => stripRouteIdPrefix(entry.id) === null;
40
+ export const isHomePageEntry = (entry: SiteEntry) => stripPageIdPrefix(entry.id) === null;
29
41
 
30
- const getRouteDirectory = (entry: SiteEntry) => isHomePageEntry(entry) ? null : stripRouteIdPrefix(entry.id);
42
+ const getPageDirectory = (entry: SiteEntry) => isHomePageEntry(entry) ? null : stripPageIdPrefix(entry.id);
31
43
 
32
- const getRouteMetadata = (entry: SiteEntry) => {
33
- const routeDirectory = getRouteDirectory(entry);
34
- return routeDirectory ? parseRouteDirectory(routeDirectory, `route directory ${routeDirectory}`) : null;
44
+ const getPageMetadata = (entry: SiteEntry) => {
45
+ const pageDirectory = getPageDirectory(entry);
46
+ return pageDirectory ? parsePageDirectoryPath(pageDirectory, `page directory ${pageDirectory}`) : null;
35
47
  };
36
48
 
37
49
  const getPagePathname = (pathSegment: string) => pathSegment ? `/${pathSegment}/` : '/';
38
50
 
51
+ const compareNumberPaths = (left: number[], right: number[]) => {
52
+ const sharedLength = Math.min(left.length, right.length);
53
+ for (let index = 0; index < sharedLength; index += 1) {
54
+ const difference = left[index] - right[index];
55
+ if (difference !== 0) return difference;
56
+ }
57
+
58
+ return left.length - right.length;
59
+ };
60
+
61
+ const decodeHtmlEntities = (value: string) => value
62
+ .replace(/&amp;/g, '&')
63
+ .replace(/&lt;/g, '<')
64
+ .replace(/&gt;/g, '>')
65
+ .replace(/&quot;/g, '"')
66
+ .replace(/&#39;/g, "'");
67
+
68
+ const getPageTitle = (entry: SiteEntry) => {
69
+ const html = entry.rendered?.html ?? '';
70
+ const headings = Array.from(html.matchAll(/<h1\b[^>]*>([\s\S]*?)<\/h1>/gi));
71
+ if (headings.length !== 1) {
72
+ throw new Error(`Page entry "${entry.id}" must contain exactly one Markdown H1 page title.`);
73
+ }
74
+
75
+ return decodeHtmlEntities((headings[0]?.[1] ?? '').replace(/<[^>]*>/g, '')).trim();
76
+ };
77
+
39
78
  export const getSitePage = (entry: SiteEntry): SitePage => {
40
79
  const isHome = isHomePageEntry(entry);
41
- const routeMetadata = getRouteMetadata(entry);
42
- const routeDirectory = routeMetadata?.routeDirectory ?? null;
43
- const routeId = routeMetadata?.routeId ?? '';
44
- const pathSegment = isHome ? '' : routeId;
80
+ const pageMetadata = getPageMetadata(entry);
81
+ const pageDirectory = pageMetadata?.pageDirectory ?? null;
82
+ const pageId = pageMetadata?.pageId ?? '';
83
+ const pagePath = pageMetadata?.pagePath ?? '';
84
+ const pathSegment = isHome ? '' : pagePath;
45
85
  const navigation = entry.data.navigation ?? {};
46
86
 
47
87
  return {
48
88
  entry,
49
89
  isHome,
90
+ depth: pageMetadata?.depth ?? 0,
50
91
  navigation: {
51
- include: navigation.include ?? true,
52
- label: navigation.label ?? entry.data.title,
53
- order: isHome ? 0 : routeMetadata?.routeOrder ?? 100,
92
+ listed: navigation.listed ?? true,
93
+ order: isHome ? 0 : pageMetadata?.pageOrder ?? 100,
54
94
  },
55
95
  pathSegment,
56
96
  pathname: getPagePathname(pathSegment),
57
- routeDirectory,
58
- routeId,
59
- title: entry.data.title,
97
+ pageDirectory,
98
+ pageDirectories: pageMetadata?.pageDirectories ?? [],
99
+ pageId,
100
+ pageIds: pageMetadata?.pageIds ?? [],
101
+ pageOrders: pageMetadata?.pageOrders ?? [],
102
+ pagePath,
103
+ parentPagePath: pageMetadata?.parentPagePath ?? null,
104
+ title: getPageTitle(entry),
60
105
  };
61
106
  };
62
107
 
63
108
  export const getSitePages = (entries: SiteEntry[]) => {
64
109
  const pages = entries.map(getSitePage);
65
110
  const pathnames = new Map<string, SitePage>();
66
- const routeIds = new Map<string, SitePage>();
67
- const routeOrders = new Map<number, SitePage>();
111
+ const siblingPageIds = new Map<string, SitePage>();
112
+ const siblingPageOrders = new Map<string, SitePage>();
68
113
 
69
114
  for (const page of pages) {
70
115
  const existing = pathnames.get(page.pathname);
@@ -76,26 +121,28 @@ export const getSitePages = (entries: SiteEntry[]) => {
76
121
 
77
122
  if (page.isHome) continue;
78
123
 
79
- const existingRouteId = routeIds.get(page.routeId);
80
- if (existingRouteId) {
81
- throw new Error(`Duplicate route id "${page.routeId}" from "${existingRouteId.entry.id}" and "${page.entry.id}".`);
124
+ const siblingScope = page.parentPagePath ?? '';
125
+ const pageIdKey = `${siblingScope}\0${page.pageId}`;
126
+ const existingPageId = siblingPageIds.get(pageIdKey);
127
+ if (existingPageId) {
128
+ throw new Error(`Duplicate sibling page id "${page.pageId}" below "${page.parentPagePath ?? '/'}" from "${existingPageId.entry.id}" and "${page.entry.id}".`);
82
129
  }
83
130
 
84
- routeIds.set(page.routeId, page);
131
+ siblingPageIds.set(pageIdKey, page);
85
132
 
86
- const existingRouteOrder = routeOrders.get(page.navigation.order);
87
- if (existingRouteOrder) {
88
- throw new Error(`Duplicate route order "${String(page.navigation.order).padStart(3, '0')}" from "${existingRouteOrder.entry.id}" and "${page.entry.id}".`);
133
+ const pageOrderKey = `${siblingScope}\0${page.navigation.order}`;
134
+ const existingPageOrder = siblingPageOrders.get(pageOrderKey);
135
+ if (existingPageOrder) {
136
+ throw new Error(`Duplicate sibling page order "${String(page.navigation.order).padStart(3, '0')}" below "${page.parentPagePath ?? '/'}" from "${existingPageOrder.entry.id}" and "${page.entry.id}".`);
89
137
  }
90
138
 
91
- routeOrders.set(page.navigation.order, page);
139
+ siblingPageOrders.set(pageOrderKey, page);
92
140
  }
93
141
 
94
142
  return pages.sort((left, right) => (
95
- left.navigation.order - right.navigation.order ||
96
- left.navigation.label.localeCompare(right.navigation.label, 'sv') ||
143
+ left.isHome ? -1 : right.isHome ? 1 :
144
+ compareNumberPaths(left.pageOrders, right.pageOrders) ||
145
+ left.title.localeCompare(right.title, 'sv') ||
97
146
  left.pathname.localeCompare(right.pathname, 'sv')
98
147
  ));
99
148
  };
100
-
101
- export const getNavigationPages = (pages: SitePage[]) => pages.filter((page) => page.navigation.include);
@@ -18,13 +18,13 @@ export async function getStaticPaths() {
18
18
  const { entry } = Astro.props;
19
19
 
20
20
  if (!entry || isHomePageEntry(entry)) {
21
- throw new Error('Route page entry is missing.');
21
+ throw new Error('Page entry is missing.');
22
22
  }
23
23
 
24
24
  const currentPage = getSitePage(entry);
25
25
 
26
26
  if (!currentPage.pathSegment) {
27
- throw new Error(`Route "${entry.id}" needs a route id.`);
27
+ throw new Error(`Page "${entry.id}" needs a page id.`);
28
28
  }
29
29
  ---
30
30