@janga/norna 0.7.16 → 0.7.18

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 (342) hide show
  1. package/README.md +18 -9
  2. package/astro.config.mjs +35 -3
  3. package/bin/norna-cli.mjs +1 -1
  4. package/docs/README.md +16 -14
  5. package/docs/commands.md +2 -2
  6. package/docs/configuration.md +25 -2
  7. package/docs/content.md +92 -103
  8. package/docs/design/navigation-and-theme-plan.md +186 -0
  9. package/docs/design/norna-diagram-design.md +9 -9
  10. package/docs/design/site-examples-structure.md +15 -11
  11. package/docs/engine-development.md +19 -22
  12. package/docs/getting-started.md +21 -16
  13. package/docs/images-and-metadata.md +12 -9
  14. package/docs/pages.md +190 -0
  15. package/docs/public-files.md +49 -36
  16. package/docs/publishing.md +19 -3
  17. package/docs/site-files.md +46 -38
  18. package/docs/sitewide-content.md +20 -36
  19. package/docs/theme.md +109 -143
  20. package/docs/typography.md +19 -31
  21. package/examples/README.md +10 -4
  22. package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/site.schema.json +17 -41
  23. package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/sitewide.schema.json +26 -67
  24. package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/theme.schema.json +185 -88
  25. package/examples/complete-sites/dog-shelter-multi-page/.astro/data-store.json +1 -1
  26. package/examples/complete-sites/dog-shelter-multi-page/.astro/dev-local.json +4 -4
  27. package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.json +5 -5
  28. package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.log +33 -23
  29. package/examples/complete-sites/dog-shelter-multi-page/README.md +1 -1
  30. package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/_metadata.json +8 -8
  31. package/examples/complete-sites/dog-shelter-multi-page/site/.norna/generated-images.json +20 -20
  32. package/examples/complete-sites/dog-shelter-multi-page/site/{content.md → pages/000-home/content.md} +3 -4
  33. package/examples/complete-sites/dog-shelter-multi-page/site/{routes → pages}/010-dogs/content.md +3 -4
  34. package/examples/complete-sites/dog-shelter-multi-page/site/{routes → pages}/020-adopt/content.md +3 -4
  35. package/examples/complete-sites/dog-shelter-multi-page/site/sitewide-content.yaml +2 -4
  36. package/examples/complete-sites/dog-shelter-single-page/.astro/collections/site.schema.json +17 -41
  37. package/examples/complete-sites/dog-shelter-single-page/.astro/collections/sitewide.schema.json +26 -67
  38. package/examples/complete-sites/dog-shelter-single-page/.astro/collections/theme.schema.json +185 -88
  39. package/examples/complete-sites/dog-shelter-single-page/.astro/data-store.json +1 -1
  40. package/examples/complete-sites/dog-shelter-single-page/.astro/dev-local.json +8 -0
  41. package/examples/complete-sites/dog-shelter-single-page/.astro/dev.json +5 -5
  42. package/examples/complete-sites/dog-shelter-single-page/.astro/dev.log +16 -12
  43. package/examples/complete-sites/dog-shelter-single-page/README.md +1 -1
  44. package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/_metadata.json +8 -8
  45. package/examples/complete-sites/dog-shelter-single-page/site/.norna/generated-images.json +12 -12
  46. package/examples/complete-sites/dog-shelter-single-page/site/{content.md → pages/000-home/content.md} +5 -2
  47. package/examples/complete-sites/dog-shelter-single-page/site/sitewide-content.yaml +2 -4
  48. package/examples/feature-demos/media-and-surfaces/.astro/collections/site.schema.json +17 -41
  49. package/examples/feature-demos/media-and-surfaces/.astro/collections/sitewide.schema.json +26 -67
  50. package/examples/feature-demos/media-and-surfaces/.astro/collections/theme.schema.json +180 -91
  51. package/examples/feature-demos/media-and-surfaces/.astro/data-store.json +1 -1
  52. package/examples/feature-demos/media-and-surfaces/.astro/dev.log +4 -14
  53. package/examples/feature-demos/media-and-surfaces/README.md +2 -2
  54. package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/_metadata.json +9 -9
  55. package/examples/feature-demos/media-and-surfaces/site/.norna/generated-images.json +16 -16
  56. package/examples/feature-demos/media-and-surfaces/site/{content.md → pages/000-home/content.md} +4 -7
  57. package/examples/feature-demos/media-and-surfaces/site/{routes → pages}/010-media/content.md +6 -5
  58. package/examples/feature-demos/media-and-surfaces/site/{routes → pages}/020-surfaces/content.md +9 -7
  59. package/examples/feature-demos/media-and-surfaces/site/pages/020-surfaces/theme.yaml +2 -0
  60. package/examples/feature-demos/media-and-surfaces/site/sitewide-content.yaml +0 -2
  61. package/examples/feature-demos/sitewide-content/README.md +3 -3
  62. package/examples/feature-demos/sitewide-content/site/pages/000-home/content.md +17 -0
  63. package/examples/feature-demos/sitewide-content/site/pages/010-logo-and-navigation/content.md +17 -0
  64. package/examples/feature-demos/sitewide-content/site/{routes → pages}/020-notices/content.md +7 -6
  65. package/examples/feature-demos/sitewide-content/site/{routes → pages}/030-footer/content.md +6 -5
  66. package/examples/feature-demos/sitewide-content/site/sitewide-content.yaml +4 -13
  67. package/examples/feature-demos/theme-preset-documentation/README.md +16 -0
  68. package/examples/feature-demos/theme-preset-documentation/site/.norna/generated-images.json +74 -0
  69. package/examples/feature-demos/{theme-presets/site/routes/020-documentation → theme-preset-documentation/site/pages/000-home}/content.md +4 -7
  70. package/examples/feature-demos/theme-preset-documentation/site/sitewide-content.yaml +3 -0
  71. package/examples/feature-demos/theme-preset-portfolio/README.md +16 -0
  72. package/examples/feature-demos/theme-preset-portfolio/site/.norna/generated-images.json +74 -0
  73. package/examples/feature-demos/theme-preset-portfolio/site/config.yaml +1 -0
  74. package/examples/feature-demos/{theme-presets/site/routes/010-portfolio → theme-preset-portfolio/site/pages/000-home}/content.md +4 -7
  75. package/examples/feature-demos/theme-preset-portfolio/site/public/robots.txt +2 -0
  76. package/examples/feature-demos/theme-preset-portfolio/site/sitewide-content.yaml +3 -0
  77. package/examples/feature-demos/theme-preset-project/README.md +16 -0
  78. package/examples/feature-demos/theme-preset-project/site/.norna/generated-images.json +74 -0
  79. package/examples/feature-demos/theme-preset-project/site/config.yaml +1 -0
  80. package/examples/feature-demos/{theme-presets/site/routes/030-project → theme-preset-project/site/pages/000-home}/content.md +4 -7
  81. package/examples/feature-demos/theme-preset-project/site/public/robots.txt +2 -0
  82. package/examples/feature-demos/theme-preset-project/site/sitewide-content.yaml +3 -0
  83. package/examples/feature-demos/theme-preset-statement/README.md +16 -0
  84. package/examples/feature-demos/theme-preset-statement/site/.norna/generated-images.json +74 -0
  85. package/examples/feature-demos/theme-preset-statement/site/config.yaml +1 -0
  86. package/examples/feature-demos/{theme-presets/site/routes/040-statement → theme-preset-statement/site/pages/000-home}/content.md +4 -7
  87. package/examples/feature-demos/theme-preset-statement/site/public/robots.txt +2 -0
  88. package/examples/feature-demos/theme-preset-statement/site/sitewide-content.yaml +3 -0
  89. package/fixtures/basic/site/{content.md → pages/000-home/content.md} +5 -6
  90. package/fixtures/basic/site/sitewide-content.yaml +1 -8
  91. package/fixtures/basic/site/theme.yaml +1 -8
  92. package/fixtures/content-model-v2/site/{content.md → pages/000-home/content.md} +11 -13
  93. package/fixtures/content-model-v2/site/pages/010-guide/content.md +18 -0
  94. package/fixtures/nested-pages/.astro/collections/site.schema.json +35 -0
  95. package/fixtures/nested-pages/.astro/collections/sitewide.schema.json +97 -0
  96. package/fixtures/nested-pages/.astro/collections/theme.schema.json +844 -0
  97. package/fixtures/nested-pages/.astro/content-assets.mjs +4 -0
  98. package/fixtures/nested-pages/.astro/content-modules.mjs +1 -0
  99. package/fixtures/nested-pages/.astro/content.d.ts +183 -0
  100. package/fixtures/nested-pages/.astro/data-store.json +1 -0
  101. package/fixtures/nested-pages/.astro/dev-local.json +8 -0
  102. package/fixtures/nested-pages/.astro/dev.log +1128 -0
  103. package/fixtures/nested-pages/.astro/settings.json +5 -0
  104. package/fixtures/nested-pages/.astro/types.d.ts +2 -0
  105. package/fixtures/nested-pages/node_modules/.vite/deps/_metadata.json +74 -0
  106. package/fixtures/nested-pages/node_modules/.vite/deps/astro-D_GIpJIE.js +434 -0
  107. package/fixtures/nested-pages/node_modules/.vite/deps/astro-D_GIpJIE.js.map +1 -0
  108. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
  109. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
  110. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_axobject-query.js +2702 -0
  111. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
  112. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
  113. package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
  114. package/fixtures/nested-pages/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
  115. package/fixtures/nested-pages/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
  116. package/fixtures/nested-pages/node_modules/.vite/deps/audit-DM9Hkl5B.js +1534 -0
  117. package/fixtures/nested-pages/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +1 -0
  118. package/fixtures/nested-pages/node_modules/.vite/deps/highlight-DnZ1ULOL.js +48 -0
  119. package/fixtures/nested-pages/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +1 -0
  120. package/fixtures/nested-pages/node_modules/.vite/deps/icons-RyebKi45.js +42 -0
  121. package/fixtures/nested-pages/node_modules/.vite/deps/icons-RyebKi45.js.map +1 -0
  122. package/fixtures/nested-pages/node_modules/.vite/deps/package.json +3 -0
  123. package/fixtures/nested-pages/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +4 -0
  124. package/fixtures/nested-pages/node_modules/.vite/deps/settings-DaU-gNnK.js +190 -0
  125. package/fixtures/nested-pages/node_modules/.vite/deps/settings-DaU-gNnK.js.map +1 -0
  126. package/fixtures/nested-pages/node_modules/.vite/deps/toolbar-63PA619H.js +458 -0
  127. package/fixtures/nested-pages/node_modules/.vite/deps/toolbar-63PA619H.js.map +1 -0
  128. package/fixtures/nested-pages/node_modules/.vite/deps/ui-library--bkcNSYz.js +1016 -0
  129. package/fixtures/nested-pages/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +1 -0
  130. package/fixtures/nested-pages/node_modules/.vite/deps/window-jPkpKVcy.js +32 -0
  131. package/fixtures/nested-pages/node_modules/.vite/deps/window-jPkpKVcy.js.map +1 -0
  132. package/fixtures/nested-pages/node_modules/.vite/deps/xray-BXCHpEJP.js +111 -0
  133. package/fixtures/nested-pages/node_modules/.vite/deps/xray-BXCHpEJP.js.map +1 -0
  134. package/fixtures/nested-pages/site/.norna/generated-images.json +10 -0
  135. package/fixtures/nested-pages/site/config.yaml +3 -0
  136. package/fixtures/nested-pages/site/pages/000-home/content.md +15 -0
  137. package/fixtures/nested-pages/site/pages/010-guides/content.md +15 -0
  138. package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/content.md +21 -0
  139. package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/images/diagram.svg +21 -0
  140. package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/pages/010-macos/content.md +18 -0
  141. package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/theme.yaml +2 -0
  142. package/fixtures/nested-pages/site/pages/010-guides/pages/020-workflows/content.md +11 -0
  143. package/fixtures/nested-pages/site/pages/010-guides/pages/030-release-notes/content.md +9 -0
  144. package/fixtures/nested-pages/site/pages/010-guides/pages/040-configuration/content.md +3 -0
  145. package/fixtures/nested-pages/site/pages/010-guides/pages/050-content/content.md +3 -0
  146. package/fixtures/nested-pages/site/pages/010-guides/pages/060-themes/content.md +3 -0
  147. package/fixtures/nested-pages/site/pages/010-guides/pages/070-images/content.md +3 -0
  148. package/fixtures/nested-pages/site/pages/010-guides/pages/080-cards/content.md +3 -0
  149. package/fixtures/nested-pages/site/pages/010-guides/pages/090-notes/content.md +3 -0
  150. package/fixtures/nested-pages/site/pages/010-guides/pages/100-validation/content.md +3 -0
  151. package/fixtures/nested-pages/site/pages/010-guides/pages/110-synchronization/content.md +3 -0
  152. package/fixtures/nested-pages/site/pages/010-guides/pages/120-building/content.md +3 -0
  153. package/fixtures/nested-pages/site/pages/010-guides/pages/130-publishing/content.md +3 -0
  154. package/fixtures/nested-pages/site/pages/010-guides/pages/140-troubleshooting/content.md +3 -0
  155. package/fixtures/nested-pages/site/pages/010-guides/pages/150-migration/content.md +3 -0
  156. package/fixtures/nested-pages/site/pages/010-guides/pages/160-accessibility/content.md +3 -0
  157. package/fixtures/nested-pages/site/pages/010-guides/pages/170-performance/content.md +3 -0
  158. package/fixtures/nested-pages/site/pages/010-guides/pages/180-security/content.md +3 -0
  159. package/fixtures/nested-pages/site/pages/010-guides/pages/190-localization/content.md +3 -0
  160. package/fixtures/nested-pages/site/pages/010-guides/pages/200-upgrading/content.md +3 -0
  161. package/fixtures/nested-pages/site/pages/010-guides/pages/210-faq/content.md +3 -0
  162. package/fixtures/nested-pages/site/pages/010-guides/theme.yaml +7 -0
  163. package/fixtures/nested-pages/site/pages/020-reference/content.md +10 -0
  164. package/fixtures/nested-pages/site/pages/020-reference/pages/010-installation/content.md +11 -0
  165. package/fixtures/nested-pages/site/theme.yaml +1 -0
  166. package/package.json +8 -4
  167. package/schemas/config.schema.json +46 -5
  168. package/schemas/content-frontmatter.schema.json +28 -63
  169. package/schemas/manifest.json +4 -2
  170. package/schemas/page-theme.schema.json +192 -0
  171. package/schemas/sitewide-content.schema.json +58 -132
  172. package/schemas/theme.schema.json +434 -152
  173. package/scripts/check-config.mjs +8 -6
  174. package/scripts/deploy-site.mjs +6 -8
  175. package/scripts/doctor.mjs +2 -2
  176. package/scripts/generate-images.mjs +15 -29
  177. package/scripts/generate-schemas.mjs +7 -218
  178. package/scripts/lib/base-path-redirect.mjs +9 -0
  179. package/scripts/lib/editor-language-service.mjs +125 -79
  180. package/scripts/lib/example-sites.mjs +15 -3
  181. package/scripts/lib/heading-ids.mjs +161 -0
  182. package/scripts/lib/navigation-model.mjs +51 -0
  183. package/scripts/lib/norna-markdown-blocks.mjs +3 -4
  184. package/scripts/lib/page-model.mjs +102 -0
  185. package/scripts/lib/presentation.mjs +25 -17
  186. package/scripts/lib/project-config.mjs +46 -8
  187. package/scripts/lib/schema-definitions.mjs +78 -55
  188. package/scripts/lib/schema-editor-metadata.mjs +519 -0
  189. package/scripts/lib/schema-value-definitions.mjs +20 -6
  190. package/scripts/lib/site-content.mjs +159 -97
  191. package/scripts/lib/site-conventions.mjs +1 -0
  192. package/scripts/lib/site-paths.mjs +11 -5
  193. package/scripts/lib/theme-config.mjs +12 -18
  194. package/scripts/lib/theme-presets.mjs +55 -14
  195. package/scripts/release.mjs +1 -1
  196. package/scripts/show-typography.mjs +4 -30
  197. package/scripts/sync-content-sections.mjs +107 -68
  198. package/scripts/{test-temporary-visibility.mjs → test-banner-visibility.mjs} +8 -25
  199. package/scripts/test-client-javascript.mjs +50 -46
  200. package/scripts/test-content-check.mjs +153 -69
  201. package/scripts/test-content-model-v2.mjs +566 -285
  202. package/scripts/test-documentation.mjs +10 -3
  203. package/scripts/test-editor-language-service.mjs +172 -25
  204. package/scripts/test-engine-commands.mjs +6 -5
  205. package/scripts/test-examples.mjs +30 -1
  206. package/scripts/test-heading-ids.mjs +68 -0
  207. package/scripts/test-navigation-model.mjs +63 -0
  208. package/scripts/test-navigation.mjs +11 -2
  209. package/scripts/test-nested-pages.mjs +125 -0
  210. package/scripts/test-package-check.mjs +55 -95
  211. package/scripts/test-page-model.mjs +108 -0
  212. package/scripts/test-project-config.mjs +22 -3
  213. package/scripts/test-schemas.mjs +82 -15
  214. package/scripts/test-theme-presets.mjs +57 -42
  215. package/src/components/NavigationPageTree.astro +157 -0
  216. package/src/components/PageContentsLinks.astro +40 -0
  217. package/src/components/SectionNavigationScript.astro +117 -5
  218. package/src/components/SiteBreadcrumbs.astro +32 -0
  219. package/src/components/SiteNavigation.astro +97 -68
  220. package/src/components/SitePage.astro +93 -55
  221. package/src/components/SiteSection.astro +29 -27
  222. package/src/components/SiteTreeNavigation.astro +35 -0
  223. package/src/components/TreeNavigationScript.astro +83 -0
  224. package/src/content.config.ts +8 -10
  225. package/src/layouts/BaseLayout.astro +21 -20
  226. package/src/lib/pageThemes.ts +44 -0
  227. package/src/lib/sectionContent.ts +106 -107
  228. package/src/lib/siteNavigation.ts +49 -0
  229. package/src/lib/sitePages.ts +96 -38
  230. package/src/pages/{[slug].astro → [...slug].astro} +2 -2
  231. package/src/pages/index.astro +2 -1
  232. package/src/styles/global.css +743 -57
  233. package/starters/basic/README.md +9 -11
  234. package/starters/basic/site/{content.md → pages/000-home/content.md} +7 -4
  235. package/starters/basic/site/sitewide-content.yaml +1 -10
  236. package/starters/basic/site/theme.yaml +1 -105
  237. package/starters/project/README.md +14 -14
  238. package/starters/project/site/{content.md → pages/000-home/content.md} +5 -4
  239. package/starters/project/site/{routes → pages}/010-guide/content.md +5 -4
  240. package/starters/project/site/sitewide-content.yaml +1 -10
  241. package/starters/project/site/theme.yaml +1 -31
  242. package/docs/routes.md +0 -121
  243. package/examples/feature-demos/media-and-surfaces/site/routes/020-surfaces/theme.yaml +0 -3
  244. package/examples/feature-demos/sitewide-content/site/content.md +0 -16
  245. package/examples/feature-demos/sitewide-content/site/routes/010-identity/content.md +0 -20
  246. package/examples/feature-demos/theme-presets/README.md +0 -18
  247. package/examples/feature-demos/theme-presets/site/.norna/generated-images.json +0 -290
  248. package/examples/feature-demos/theme-presets/site/content.md +0 -17
  249. package/examples/feature-demos/theme-presets/site/sitewide-content.yaml +0 -12
  250. package/examples/feature-demos/theme-presets/site/theme.yaml +0 -1
  251. package/fixtures/content-model-v2/site/routes/010-guide/content.md +0 -17
  252. package/scripts/lib/route-model.mjs +0 -17
  253. package/scripts/test-route-model.mjs +0 -32
  254. package/src/lib/routeThemes.ts +0 -41
  255. /package/examples/complete-sites/dog-shelter-multi-page/site/{images/intro → pages/000-home/images}/shelter-dog.jpg +0 -0
  256. /package/examples/complete-sites/dog-shelter-multi-page/site/{routes/010-dogs/images/dogs → pages/010-dogs/images}/rex.jpg +0 -0
  257. /package/examples/complete-sites/dog-shelter-multi-page/site/{routes/010-dogs/images/dogs → pages/010-dogs/images}/rover.jpg +0 -0
  258. /package/examples/complete-sites/dog-shelter-multi-page/site/{routes/020-adopt/images/adopt → pages/020-adopt/images}/adoption-dog.jpg +0 -0
  259. /package/examples/complete-sites/dog-shelter-single-page/site/{images/you-can-help → pages/000-home/images}/foster-dog.jpg +0 -0
  260. /package/examples/complete-sites/dog-shelter-single-page/site/{images/what-we-do → pages/000-home/images}/rescue-dog.jpg +0 -0
  261. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/cards → pages/010-media/images}/card-carousel.svg +0 -0
  262. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/cards → pages/010-media/images}/card-stack.svg +0 -0
  263. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/cards → pages/010-media/images}/card-surfaces.svg +0 -0
  264. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/carousel → pages/010-media/images}/carousel-one.svg +0 -0
  265. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/carousel → pages/010-media/images}/carousel-three.svg +0 -0
  266. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/carousel → pages/010-media/images}/carousel-two.svg +0 -0
  267. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/stack → pages/010-media/images}/stack-one.svg +0 -0
  268. /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/stack → pages/010-media/images}/stack-two.svg +0 -0
  269. /package/examples/feature-demos/{theme-presets → theme-preset-documentation}/site/config.yaml +0 -0
  270. /package/examples/feature-demos/{theme-presets/site/routes/020-documentation/images/documentation → theme-preset-documentation/site/pages/000-home/images}/black-schnauzer.jpg +0 -0
  271. /package/examples/feature-demos/{theme-presets/site/routes/020-documentation/images/documentation → theme-preset-documentation/site/pages/000-home/images}/retriever-portrait.jpg +0 -0
  272. /package/examples/feature-demos/{theme-presets/site/routes/020-documentation/images/documentation → theme-preset-documentation/site/pages/000-home/images}/schnauzer-portrait.jpg +0 -0
  273. /package/examples/feature-demos/{theme-presets → theme-preset-documentation}/site/public/robots.txt +0 -0
  274. /package/examples/feature-demos/{theme-presets/site/routes/020-documentation → theme-preset-documentation/site}/theme.yaml +0 -0
  275. /package/examples/feature-demos/{theme-presets/site/routes/010-portfolio/images/portfolio → theme-preset-portfolio/site/pages/000-home/images}/puppy-portrait.jpg +0 -0
  276. /package/examples/feature-demos/{theme-presets/site/routes/010-portfolio/images/portfolio → theme-preset-portfolio/site/pages/000-home/images}/shepherd-portrait.jpg +0 -0
  277. /package/examples/feature-demos/{theme-presets/site/routes/010-portfolio/images/portfolio → theme-preset-portfolio/site/pages/000-home/images}/white-puppy.jpg +0 -0
  278. /package/examples/feature-demos/{theme-presets/site/routes/010-portfolio → theme-preset-portfolio/site}/theme.yaml +0 -0
  279. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/collections/routeTheme.schema.json +0 -0
  280. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/collections/site.schema.json +0 -0
  281. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/collections/sitewide.schema.json +0 -0
  282. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/collections/theme.schema.json +0 -0
  283. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/content-assets.mjs +0 -0
  284. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/content-modules.mjs +0 -0
  285. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/content.d.ts +0 -0
  286. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/data-store.json +0 -0
  287. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/dev-local.json +0 -0
  288. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/dev.json +0 -0
  289. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/dev.log +0 -0
  290. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/settings.json +0 -0
  291. /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/types.d.ts +0 -0
  292. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/_metadata.json +0 -0
  293. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro-D_GIpJIE.js +0 -0
  294. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro-D_GIpJIE.js.map +0 -0
  295. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_aria-query.js +0 -0
  296. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -0
  297. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_axobject-query.js +0 -0
  298. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -0
  299. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_html-escaper.js +0 -0
  300. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -0
  301. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -0
  302. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -0
  303. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/audit-DM9Hkl5B.js +0 -0
  304. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +0 -0
  305. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/embla-carousel.js +0 -0
  306. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/embla-carousel.js.map +0 -0
  307. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/highlight-DnZ1ULOL.js +0 -0
  308. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +0 -0
  309. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/icons-RyebKi45.js +0 -0
  310. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/icons-RyebKi45.js.map +0 -0
  311. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/package.json +0 -0
  312. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -0
  313. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/settings-DaU-gNnK.js +0 -0
  314. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/settings-DaU-gNnK.js.map +0 -0
  315. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/toolbar-63PA619H.js +0 -0
  316. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/toolbar-63PA619H.js.map +0 -0
  317. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/ui-library--bkcNSYz.js +0 -0
  318. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +0 -0
  319. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/window-jPkpKVcy.js +0 -0
  320. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/window-jPkpKVcy.js.map +0 -0
  321. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/xray-BXCHpEJP.js +0 -0
  322. /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/xray-BXCHpEJP.js.map +0 -0
  323. /package/examples/feature-demos/{theme-presets/site/routes/030-project/images/project → theme-preset-project/site/pages/000-home/images}/boxer-portrait.jpg +0 -0
  324. /package/examples/feature-demos/{theme-presets/site/routes/030-project/images/project → theme-preset-project/site/pages/000-home/images}/grass-puppy.jpg +0 -0
  325. /package/examples/feature-demos/{theme-presets/site/routes/030-project/images/project → theme-preset-project/site/pages/000-home/images}/grey-street-dog.jpg +0 -0
  326. /package/examples/feature-demos/{theme-presets/site/routes/030-project → theme-preset-project/site}/theme.yaml +0 -0
  327. /package/examples/feature-demos/{theme-presets/site/routes/040-statement/images/statement → theme-preset-statement/site/pages/000-home/images}/black-pedigree.jpg +0 -0
  328. /package/examples/feature-demos/{theme-presets/site/routes/040-statement/images/statement → theme-preset-statement/site/pages/000-home/images}/sheepdog-grass.jpg +0 -0
  329. /package/examples/feature-demos/{theme-presets/site/routes/040-statement/images/statement → theme-preset-statement/site/pages/000-home/images}/sheepdog-wide.jpg +0 -0
  330. /package/examples/feature-demos/{theme-presets/site/routes/040-statement → theme-preset-statement/site}/theme.yaml +0 -0
  331. /package/fixtures/basic/site/{images/work → pages/000-home/images}/.gitkeep +0 -0
  332. /package/fixtures/content-model-v2/site/{images/card-section → pages/000-home/images}/adopt.svg +0 -0
  333. /package/fixtures/content-model-v2/site/{images/intro → pages/000-home/images}/detail.jpg +0 -0
  334. /package/fixtures/content-model-v2/site/{images/intro → pages/000-home/images}/duplicate.jpg +0 -0
  335. /package/fixtures/content-model-v2/site/{images/card-section → pages/000-home/images}/foster.svg +0 -0
  336. /package/fixtures/content-model-v2/site/{images/intro → pages/000-home/images}/hero.jpg +0 -0
  337. /package/fixtures/content-model-v2/site/{images/plain → pages/000-home/images}/image.jpg +0 -0
  338. /package/fixtures/content-model-v2/site/{images/timed → pages/000-home/images}/slide-one.jpg +0 -0
  339. /package/fixtures/content-model-v2/site/{images/timed → pages/000-home/images}/slide-three.jpg +0 -0
  340. /package/fixtures/content-model-v2/site/{images/timed → pages/000-home/images}/slide-two.jpg +0 -0
  341. /package/fixtures/content-model-v2/site/{routes/010-guide/images/intro → pages/010-guide/images}/duplicate.jpg +0 -0
  342. /package/starters/basic/site/{images/work → pages/000-home/images}/.gitkeep +0 -0
@@ -0,0 +1,32 @@
1
+ ---
2
+ import projectConfig from '../../scripts/lib/project-config.mjs';
3
+ import { withBasePath } from '../lib/basePath';
4
+ import type { SitePage } from '../lib/sitePages';
5
+
6
+ interface Props {
7
+ currentPage: SitePage;
8
+ pages: SitePage[];
9
+ }
10
+
11
+ const { currentPage, pages } = Astro.props;
12
+ const pagesByPath = new Map(pages.map((page) => [page.pagePath, page]));
13
+ const ancestors: SitePage[] = [];
14
+ let parentPagePath = currentPage.parentPagePath;
15
+ while (parentPagePath !== null) {
16
+ const parentPage = pagesByPath.get(parentPagePath);
17
+ if (!parentPage) break;
18
+ ancestors.unshift(parentPage);
19
+ parentPagePath = parentPage.parentPagePath;
20
+ }
21
+ const pageHref = (page: SitePage) => withBasePath(projectConfig.site.basePath, page.pathname);
22
+ const breadcrumbPages = ancestors;
23
+ ---
24
+
25
+ <nav class="site-breadcrumbs" aria-label={projectConfig.locale.labels.breadcrumb}>
26
+ <ol>
27
+ {breadcrumbPages.map((page) => (
28
+ <li><a href={pageHref(page)}>{page.title}</a></li>
29
+ ))}
30
+ <li><span aria-current="page">{currentPage.title}</span></li>
31
+ </ol>
32
+ </nav>
@@ -2,7 +2,9 @@
2
2
  import projectConfig from '../../scripts/lib/project-config.mjs';
3
3
  import { withBasePath } from '../lib/basePath';
4
4
  import type { SectionNavigation } from '../lib/sectionContent';
5
+ import { getListedPageNavigationTree, type PageNavigationEntry } from '../lib/siteNavigation';
5
6
  import type { SitePage } from '../lib/sitePages';
7
+ import NavigationPageTree from './NavigationPageTree.astro';
6
8
  import SectionNavigationScript from './SectionNavigationScript.astro';
7
9
 
8
10
  type FrameColors = {
@@ -11,21 +13,18 @@ type FrameColors = {
11
13
  };
12
14
 
13
15
  interface Props {
14
- navigationLabel: string;
15
16
  logo?: {
16
17
  src: string;
17
18
  alt: string;
18
19
  height?: string;
19
20
  };
20
21
  currentPage: SitePage;
21
- pageNavigation: Array<{
22
- page: SitePage;
23
- sections: SectionNavigation[];
24
- }>;
22
+ pageNavigation: PageNavigationEntry[];
23
+ navigationMode: 'sections' | 'top' | 'tree';
25
24
  frameColors: FrameColors;
26
25
  }
27
26
 
28
- const { navigationLabel, logo, currentPage, pageNavigation, frameColors } = Astro.props;
27
+ const { logo, currentPage, pageNavigation, navigationMode, frameColors } = Astro.props;
29
28
  const siteTopBackgroundColor = frameColors.backgroundColor === 'transparent'
30
29
  ? 'var(--color-page)'
31
30
  : frameColors.backgroundColor;
@@ -34,54 +33,60 @@ const navigationStyle = [
34
33
  `--site-top-text-color: ${frameColors.textColor}`,
35
34
  ...(logo?.height ? [`--site-brand-logo-height: ${logo.height}`] : []),
36
35
  ].join('; ');
37
- const showSiteNavigation = pageNavigation.length > 1;
38
- const showNavigationRow = showSiteNavigation || Boolean(logo);
36
+ const siteNavigationPages = pageNavigation.filter(({ page }) => page.isHome || page.navigation.listed);
37
+ const siteNavigationTree = getListedPageNavigationTree(pageNavigation);
38
+ const topLevelNavigation = siteNavigationTree;
39
+ const showSiteNavigation = siteNavigationPages.some(({ page }) => !page.isHome);
39
40
  const homePage = pageNavigation.find(({ page }) => page.isHome)?.page ?? currentPage;
40
41
  const currentPageNavigation = pageNavigation.find(({ page }) => page.pathname === currentPage.pathname);
41
42
  const isCurrentPage = (page: SitePage) => page.pathname === currentPage.pathname;
43
+ const isCurrentBranch = (page: SitePage) => currentPage.pageDirectory === page.pageDirectory
44
+ || currentPage.pageDirectory.startsWith(`${page.pageDirectory}/pages/`);
42
45
  const pageHref = (page: SitePage) => withBasePath(projectConfig.site.basePath, page.pathname);
43
- const sectionHref = (page: SitePage, section: SectionNavigation) => `${pageHref(page)}#${section.id}`;
44
46
  const hasSectionMenu = (pageSections: SectionNavigation[]) => pageSections.length > 1;
45
- const showSinglePageSectionNavigation = Boolean(
46
- !showSiteNavigation && currentPageNavigation && hasSectionMenu(currentPageNavigation.sections),
47
- );
48
47
  const hasCurrentPageSectionMenu = Boolean(
49
- showSiteNavigation && currentPageNavigation && hasSectionMenu(currentPageNavigation.sections),
48
+ currentPageNavigation && hasSectionMenu(currentPageNavigation.sections),
49
+ );
50
+ const hasTreePageContents = Boolean(
51
+ navigationMode === 'tree' && currentPageNavigation?.headings.length,
50
52
  );
51
- const hasSectionNavigation = showSinglePageSectionNavigation || hasCurrentPageSectionMenu;
53
+ const showSinglePageTopLink = !showSiteNavigation;
54
+ const showMobileNavigation = showSiteNavigation || hasCurrentPageSectionMenu || hasTreePageContents;
55
+ const showNavigationRow = showMobileNavigation || Boolean(logo);
56
+ const needsNavigationScript = hasCurrentPageSectionMenu || hasTreePageContents || navigationMode === 'tree';
52
57
  ---
53
58
 
54
59
  <a class="skip-link" href="#main-content">{projectConfig.locale.labels.skipToContent}</a>
55
60
 
56
61
  <header
57
62
  class:list={['site-top', { 'site-top-has-site-nav': showSiteNavigation }]}
63
+ data-navigation-mode={navigationMode}
58
64
  style={navigationStyle}
59
65
  >
60
66
  {showNavigationRow && (
61
67
  <div class="site-nav-row">
62
- <a class="site-brand" href={pageHref(homePage)}>
63
- {logo ? <img class="site-brand-logo" src={withBasePath(projectConfig.site.basePath, logo.src)} alt={logo.alt} /> : navigationLabel}
64
- </a>
68
+ {logo && (
69
+ <a class="site-brand" href={pageHref(homePage)}>
70
+ <img class="site-brand-logo" src={withBasePath(projectConfig.site.basePath, logo.src)} alt={logo.alt} />
71
+ </a>
72
+ )}
65
73
 
66
74
  {showSiteNavigation && (
67
75
  <nav class="site-nav" aria-label={projectConfig.locale.labels.siteNavigation}>
68
76
  <ul>
69
- {pageNavigation.map(({ page, sections: pageSections }) => (
70
- <li class:list={['site-nav-item', { 'site-nav-item-has-sections': hasSectionMenu(pageSections) }]}>
71
- <a href={pageHref(page)} aria-current={isCurrentPage(page) ? 'page' : undefined}>
72
- {page.navigation.label}
73
- </a>
74
- {hasSectionMenu(pageSections) && (
75
- <div class="site-nav-submenu">
76
- <nav aria-label={`${page.navigation.label} ${projectConfig.locale.labels.pageNavigation}`}>
77
- <ul>
78
- {pageSections.map((section) => (
79
- <li>
80
- <a href={sectionHref(page, section)}>{section.title}</a>
81
- </li>
82
- ))}
83
- </ul>
84
- </nav>
77
+ {topLevelNavigation.map((node) => (
78
+ <li class:list={['site-nav-item', {
79
+ 'site-nav-item-has-submenu': navigationMode !== 'tree' && node.children.length > 0,
80
+ 'site-nav-item-current-branch': !isCurrentPage(node.page) && isCurrentBranch(node.page),
81
+ }]}>
82
+ <a href={pageHref(node.page)} aria-current={isCurrentPage(node.page) ? 'page' : undefined}>
83
+ {node.page.title}
84
+ </a>
85
+ {navigationMode !== 'tree' && node.children.length > 0 && (
86
+ <div class="site-nav-submenu">
87
+ <nav aria-label={`${node.page.title} ${projectConfig.locale.labels.siteNavigation}`}>
88
+ <NavigationPageTree nodes={[node]} currentPage={currentPage} variant="submenu" />
89
+ </nav>
85
90
  </div>
86
91
  )}
87
92
  </li>
@@ -90,55 +95,79 @@ const hasSectionNavigation = showSinglePageSectionNavigation || hasCurrentPageSe
90
95
  </nav>
91
96
  )}
92
97
 
93
- {showSiteNavigation && <details class="mobile-nav-menu">
94
- <summary>
95
- <span>{currentPage.navigation.label}</span>
96
- </summary>
97
- <div class="mobile-nav-panel">
98
- <nav class="mobile-site-nav" aria-label={projectConfig.locale.labels.siteNavigation}>
99
- <h2>{projectConfig.locale.labels.siteNavigation}</h2>
100
- <ul>
101
- {pageNavigation.map(({ page }) => (
102
- <li>
103
- <a href={pageHref(page)} aria-current={isCurrentPage(page) ? 'page' : undefined}>
104
- {page.navigation.label}
105
- </a>
106
- </li>
107
- ))}
108
- </ul>
109
- </nav>
98
+ {showMobileNavigation && <details class="mobile-nav-menu">
99
+ <summary>
100
+ <span>{projectConfig.locale.labels.navigationMenu}</span>
101
+ </summary>
102
+ <div class="mobile-nav-panel">
103
+ <div class="mobile-site-nav" data-navigation-root={pageHref(homePage)}>
104
+ {showSiteNavigation && (
105
+ <nav class="mobile-nav-view" aria-label={projectConfig.locale.labels.siteNavigation}>
106
+ <h2 tabindex="-1">{projectConfig.locale.labels.siteNavigation}</h2>
107
+ <NavigationPageTree
108
+ nodes={siteNavigationTree}
109
+ currentPage={currentPage}
110
+ variant="mobile"
111
+ integratePageSections={navigationMode === 'tree'}
112
+ />
113
+ </nav>
114
+ )}
110
115
 
111
- {currentPageNavigation && hasCurrentPageSectionMenu && (
112
- <nav class="mobile-page-nav" aria-label={projectConfig.locale.labels.pageNavigation}>
113
- <h2>{projectConfig.locale.labels.pageNavigation}</h2>
114
- <ul>
115
- {currentPageNavigation.sections.map((section) => (
116
- <li>
117
- <a href={sectionHref(currentPage, section)}>
118
- {section.title}
116
+ {navigationMode !== 'tree' && hasCurrentPageSectionMenu && currentPageNavigation && (
117
+ <nav class="mobile-nav-view" aria-label={projectConfig.locale.labels.pageNavigation}>
118
+ {!showSinglePageTopLink && (
119
+ <h2 tabindex="-1">{projectConfig.locale.labels.pageNavigation}</h2>
120
+ )}
121
+ <ul class="mobile-nav-sections">
122
+ {showSinglePageTopLink && (
123
+ <li class="mobile-nav-page-top-item">
124
+ <a
125
+ class="mobile-nav-destination mobile-nav-page-top"
126
+ data-page-top
127
+ href={pageHref(currentPage)}
128
+ >
129
+ {currentPage.title}
119
130
  </a>
120
131
  </li>
132
+ )}
133
+ {currentPageNavigation.sections.map((section) => (
134
+ <li>
135
+ <a class="mobile-nav-destination" href={`#${section.id}`}>{section.title}</a>
136
+ </li>
121
137
  ))}
122
138
  </ul>
123
139
  </nav>
124
140
  )}
125
141
  </div>
126
- </details>}
142
+ </div>
143
+ </details>}
127
144
  </div>
128
145
  )}
129
146
 
130
- {showSinglePageSectionNavigation && currentPageNavigation && (
147
+ {navigationMode !== 'tree' && hasCurrentPageSectionMenu && currentPageNavigation && (
131
148
  <nav class="page-nav" aria-label={projectConfig.locale.labels.pageNavigation}>
132
- <ul>
133
- {currentPageNavigation.sections.map((section) => (
134
- <li>
135
- <a href={`#${section.id}`}>{section.title}</a>
136
- </li>
137
- ))}
138
- </ul>
149
+ <div class="page-nav-inner">
150
+ {!showSinglePageTopLink && (
151
+ <span class="page-nav-label">{projectConfig.locale.labels.pageNavigation}</span>
152
+ )}
153
+ <ul>
154
+ {showSinglePageTopLink && (
155
+ <li class="page-nav-page-top-item">
156
+ <a class="page-nav-page-top" data-page-top href={pageHref(currentPage)}>
157
+ {currentPage.title}
158
+ </a>
159
+ </li>
160
+ )}
161
+ {currentPageNavigation.sections.map((section) => (
162
+ <li>
163
+ <a href={`#${section.id}`}>{section.title}</a>
164
+ </li>
165
+ ))}
166
+ </ul>
167
+ </div>
139
168
  </nav>
140
169
  )}
141
170
 
142
171
  </header>
143
172
 
144
- {hasSectionNavigation && <SectionNavigationScript />}
173
+ {needsNavigationScript && <SectionNavigationScript />}
@@ -2,19 +2,24 @@
2
2
  import { getCollection, type CollectionEntry } from 'astro:content';
3
3
  import path from 'node:path';
4
4
  import BaseLayout from '../layouts/BaseLayout.astro';
5
+ import SiteBreadcrumbs from './SiteBreadcrumbs.astro';
5
6
  import SiteNavigation from './SiteNavigation.astro';
6
7
  import SiteBanners from './SiteBanners.astro';
7
8
  import SiteNotesScript from './SiteNotesScript.astro';
8
9
  import SiteSection from './SiteSection.astro';
9
- import { getSectionNavigation, getSectionsContent } from '../lib/sectionContent';
10
- import { getRouteTheme } from '../lib/routeThemes';
11
- import { getNavigationPages, getSitePage, getSitePages } from '../lib/sitePages';
10
+ import SiteTreeNavigation from './SiteTreeNavigation.astro';
11
+ import TreeNavigationScript from './TreeNavigationScript.astro';
12
+ import { getHeadingNavigation, getSectionsContent, type ResolvedSection } from '../lib/sectionContent';
13
+ import { getPageTheme } from '../lib/pageThemes';
14
+ import { getSitePage, getSitePages, type SitePage } from '../lib/sitePages';
12
15
  import { getToday, isVisibleToday } from '../lib/visibility';
13
16
  import { getLogoAsset } from '../lib/sitePublicAssets';
14
17
  import { readSiteFile } from '../../scripts/lib/site-content.mjs';
15
- import { siteContentPath, siteRoutesDir } from '../../scripts/lib/site-paths.mjs';
16
- import { resolveThemeVisualConfig } from '../../scripts/lib/project-config.mjs';
17
- import { resolveFrameColors, resolvePagePresentation } from '../../scripts/lib/presentation.mjs';
18
+ import { resolveNavigationModel } from '../../scripts/lib/navigation-model.mjs';
19
+ import { sitePagesDir } from '../../scripts/lib/site-paths.mjs';
20
+ import { projectConfig, resolveThemeVisualConfig } from '../../scripts/lib/project-config.mjs';
21
+ import { getTextWidthCssValue, resolveFrameColors, resolvePagePresentation } from '../../scripts/lib/presentation.mjs';
22
+ import { mergePageThemeConfig } from '../../scripts/lib/theme-presets.mjs';
18
23
 
19
24
  type SiteEntry = CollectionEntry<'site'>;
20
25
 
@@ -26,71 +31,92 @@ const { entry } = Astro.props;
26
31
  const siteDirectory = (process.env.NORNA_SITE_DIR ?? 'site').trim();
27
32
  const sitePages = getSitePages(await getCollection('site'));
28
33
  const currentPage = getSitePage(entry);
29
- const siteContentLabel = entry.id.includes('-route-')
30
- ? `${siteDirectory || 'site'}/routes/${currentPage.routeDirectory}/content.md`
31
- : `${siteDirectory || 'site'}/content.md`;
32
- const pageContentPath = currentPage.routeDirectory
33
- ? path.join(siteRoutesDir, currentPage.routeDirectory, 'content.md')
34
- : siteContentPath;
35
- const rawMarkdown = (await readSiteFile(pageContentPath, siteContentLabel)).body;
34
+ const getPageContentFile = (page: SitePage) => ({
35
+ label: `${siteDirectory || 'site'}/pages/${page.pageDirectory}/content.md`,
36
+ path: path.join(sitePagesDir, page.pageDirectory, 'content.md'),
37
+ });
38
+ const pageContentFile = getPageContentFile(currentPage);
39
+ const siteContentLabel = pageContentFile.label;
40
+ const rawMarkdown = (await readSiteFile(pageContentFile.path, pageContentFile.label)).body;
36
41
  const [siteTheme] = await getCollection('theme');
37
42
  if (!siteTheme) {
38
43
  throw new Error('site/theme.yaml is required. Add a root theme before building the site.');
39
44
  }
40
45
  const [sitewide] = await getCollection('sitewide');
41
46
  const sitewideData = sitewide?.data ?? {};
42
- const navigationPages = getNavigationPages(sitePages);
43
47
  const homePage = sitePages.find((page) => page.isHome) ?? currentPage;
44
48
  const siteThemeData = siteTheme.data;
45
- const routeTheme = await getRouteTheme(currentPage.routeDirectory);
46
- const themeData = routeTheme?.data ?? siteThemeData;
47
- const themeLabel = routeTheme
48
- ? `${siteDirectory || 'site'}/${routeTheme.id}`
49
+ const pageTheme = await getPageTheme(currentPage.pageDirectory);
50
+ const themeData = mergePageThemeConfig(siteThemeData, pageTheme?.data);
51
+ const themeLabel = pageTheme
52
+ ? `${siteDirectory || 'site'}/${pageTheme.id}`
49
53
  : `${siteDirectory || 'site'}/${siteTheme.id}`;
50
54
  const visualTheme = resolveThemeVisualConfig(themeData, themeLabel);
51
- const navigationLabel = sitewideData.navigation?.label ?? homePage.title;
52
55
  const logoAsset = getLogoAsset();
53
- const logoSettings = sitewideData.navigation?.logo;
56
+ const logoSettings = sitewideData.logo;
54
57
  const logo = logoAsset
55
58
  ? {
56
59
  src: logoAsset.href,
57
- alt: navigationLabel,
60
+ alt: homePage.title,
58
61
  height: logoSettings?.height,
59
62
  }
60
63
  : undefined;
61
64
  const pagePresentation = resolvePagePresentation(themeData, themeLabel);
62
- const frameColors = resolveFrameColors({ theme: themeData, sourceLabel: themeLabel });
63
- const sections = entry.data.sections;
65
+ const pageTextWidth = getTextWidthCssValue(pagePresentation.typography.values.body.width);
66
+ const frameColors = resolveFrameColors({ theme: siteThemeData, sourceLabel: `${siteDirectory || 'site'}/${siteTheme.id}` });
64
67
  const siteHtml = entry.rendered?.html ?? '';
65
- const resolvedSections = await getSectionsContent(siteHtml, rawMarkdown, sections, currentPage);
68
+ const resolvedSections = await getSectionsContent(siteHtml, rawMarkdown, currentPage, siteContentLabel);
66
69
  const today = getToday();
67
70
  const siteBanners = (sitewideData.banners ?? []).filter((banner) => isVisibleToday(banner.visible, today));
68
- const visibleSections = resolvedSections.filter((section) => isVisibleToday(section.visible, today));
69
- const hasMarginNotes = visibleSections.some((section) => section.contentBlocks.some((block) => block.type === 'note'));
71
+ const hasMarginNotes = resolvedSections.some((section) => section.contentBlocks.some((block) => block.type === 'note'));
72
+ const hasPrioritizableImage = (section: ResolvedSection) => section.contentBlocks.some((block) => (
73
+ block.type === 'image-stack'
74
+ || block.type === 'image-carousel'
75
+ ));
76
+ const firstImageSectionIndex = resolvedSections.findIndex(hasPrioritizableImage);
77
+ let contentSectionIndex = 0;
78
+ const renderedSections = resolvedSections.map((section, index) => ({
79
+ section,
80
+ sectionIndex: section.headingLevel === 1 ? 0 : contentSectionIndex++,
81
+ prioritizeFirstImage: index === firstImageSectionIndex,
82
+ }));
70
83
 
71
84
  const getPageContent = async (page: SitePage) => {
72
- if (page.pathname === currentPage.pathname) {
73
- return visibleSections.map(({ id, title }) => ({ id, title }));
74
- }
75
-
76
- const pageSections = getSectionNavigation(page.entry.rendered?.html ?? '', page.entry.data.sections);
77
-
78
- return pageSections.filter((section) => isVisibleToday(page.entry.data.sections[section.id]?.visible, today));
85
+ const contentFile = getPageContentFile(page);
86
+ const pageMarkdown = page.pathname === currentPage.pathname
87
+ ? rawMarkdown
88
+ : (await readSiteFile(contentFile.path, contentFile.label)).body;
89
+ return getHeadingNavigation(pageMarkdown, contentFile.label);
79
90
  };
80
91
 
81
- const pageNavigation = await Promise.all(navigationPages.map(async (page) => ({
82
- page,
83
- sections: await getPageContent(page),
84
- })));
92
+ const pageNavigation = await Promise.all(sitePages.map(async (page) => {
93
+ const headings = await getPageContent(page);
94
+ return {
95
+ page,
96
+ headings,
97
+ sections: headings
98
+ .filter((heading) => heading.depth === 2)
99
+ .map(({ id, title }) => ({ id, title })),
100
+ };
101
+ }));
102
+ const navigationModel = resolveNavigationModel({
103
+ mode: projectConfig.navigation.mode,
104
+ pages: pageNavigation.map(({ page, headings }) => ({
105
+ depth: Math.max(1, page.depth),
106
+ headings,
107
+ isHome: page.isHome,
108
+ listed: page.navigation.listed,
109
+ })),
110
+ });
111
+ const hasTreeNavigation = navigationModel.mode === 'tree';
112
+ const showBreadcrumbs = !currentPage.isHome && currentPage.parentPagePath !== null;
113
+ const reserveBreadcrumbSpace = hasTreeNavigation || showBreadcrumbs;
85
114
 
86
- if (visibleSections.length === 0) {
87
- throw new Error(`No sections are visible for ${today}. Check sections.<section-id>.visible in ${siteContentLabel}.`);
88
- }
89
115
  ---
90
116
 
91
117
  <BaseLayout
92
- title={entry.data.title}
93
- description={entry.data.description}
118
+ title={currentPage.title}
119
+ description={entry.data.page?.description}
94
120
  presentation={pagePresentation}
95
121
  visualTheme={visualTheme}
96
122
  frameColors={frameColors}
@@ -98,25 +124,37 @@ if (visibleSections.length === 0) {
98
124
  footer={sitewideData.footer}
99
125
  >
100
126
  <SiteNavigation
101
- navigationLabel={navigationLabel}
102
127
  logo={logo}
103
128
  currentPage={currentPage}
104
129
  pageNavigation={pageNavigation}
130
+ navigationMode={navigationModel.mode}
105
131
  frameColors={frameColors}
106
132
  />
107
133
  {siteBanners.length > 0 && <SiteBanners banners={siteBanners} />}
108
134
 
109
- <main id="main-content" class="site-content">
110
- {visibleSections.map((section, index) => (
111
- <SiteSection
112
- section={section}
113
- pagePresentation={pagePresentation}
114
- visualTheme={visualTheme}
115
- sectionIndex={index}
116
- headingLevel={index === 0 ? 1 : 2}
117
- prioritizeFirstImage={index === 0}
118
- />
119
- ))}
120
- </main>
135
+ <div class:list={['site-page-layout', { 'site-page-layout-tree': hasTreeNavigation }]}>
136
+ {hasTreeNavigation && (
137
+ <SiteTreeNavigation currentPage={currentPage} pageNavigation={pageNavigation} />
138
+ )}
139
+ <main
140
+ class:list={['site-content', { 'site-content-has-breadcrumbs': reserveBreadcrumbSpace }]}
141
+ id="main-content"
142
+ style={`--page-text-width: ${pageTextWidth}`}
143
+ >
144
+ {showBreadcrumbs
145
+ ? <SiteBreadcrumbs currentPage={currentPage} pages={sitePages} />
146
+ : reserveBreadcrumbSpace && <div class="site-breadcrumbs" aria-hidden="true"></div>}
147
+ {renderedSections.map(({ section, sectionIndex, prioritizeFirstImage }) => (
148
+ <SiteSection
149
+ section={section}
150
+ pagePresentation={pagePresentation}
151
+ visualTheme={visualTheme}
152
+ sectionIndex={sectionIndex}
153
+ prioritizeFirstImage={prioritizeFirstImage}
154
+ />
155
+ ))}
156
+ </main>
157
+ </div>
158
+ {hasTreeNavigation && <TreeNavigationScript />}
121
159
  {hasMarginNotes && <SiteNotesScript />}
122
160
  </BaseLayout>
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  import projectConfig from '../../scripts/lib/project-config.mjs';
3
- import { resolveSectionSurface } from '../../scripts/lib/presentation.mjs';
3
+ import { getTextWidthCssValue, resolveSectionSurface } from '../../scripts/lib/presentation.mjs';
4
4
  import CardList from './CardList.astro';
5
5
  import ImageCarousel from './ImageCarousel.astro';
6
6
  import ImageStack from './ImageStack.astro';
@@ -35,34 +35,36 @@ interface Props {
35
35
  pagePresentation: ResolvedPagePresentation;
36
36
  visualTheme: VisualTheme;
37
37
  sectionIndex: number;
38
- headingLevel: 1 | 2;
39
38
  prioritizeFirstImage?: boolean;
40
39
  }
41
40
 
42
- const { section, pagePresentation, visualTheme, sectionIndex, headingLevel, prioritizeFirstImage = false } = Astro.props;
43
- const HeadingTag = headingLevel === 1 ? 'h1' : 'h2';
44
- // Sections are authored with Markdown ## headings even when the first one renders as HTML h1.
45
- const sectionHeadingVisualLevel = 'h2';
46
- const firstContentBlockIsVisual = section.contentBlocks[0]?.type !== 'html'
47
- && section.contentBlocks[0]?.type !== 'note';
41
+ const { section, pagePresentation, visualTheme, sectionIndex, prioritizeFirstImage = false } = Astro.props;
42
+ const isPageTitle = section.headingLevel === 1;
43
+ const HeadingTag = section.headingLevel === 1 ? 'h1' : 'h2';
44
+ const sectionHeadingVisualLevel = section.headingLevel === 1 ? 'h1' : 'h2';
45
+ const headingId = section.id ?? 'page-title';
46
+ const firstContentBlock = section.contentBlocks[0];
47
+ const firstContentBlockIsVisual = firstContentBlock !== undefined
48
+ && firstContentBlock.type !== 'html'
49
+ && firstContentBlock.type !== 'note';
48
50
  type HeadingLevel = 'h1' | 'h2' | 'h3' | 'h4';
49
51
  const headingSizeValues: Record<HeadingLevel, Record<TextSize, ResponsiveValue<string>>> = {
50
52
  h1: {
51
53
  small: {
52
- desktop: 'clamp(1.5rem, 1.18rem + 1.15vw, 2.25rem)',
53
- mobile: 'clamp(1.42rem, 1.12rem + 1.5vw, 2rem)',
54
+ desktop: 'clamp(1.35rem, 1.2rem + 0.55vw, 1.7rem)',
55
+ mobile: 'clamp(1.32rem, 1.16rem + 0.8vw, 1.58rem)',
54
56
  },
55
57
  medium: {
56
- desktop: 'clamp(1.85rem, 1.35rem + 1.8vw, 3.1rem)',
57
- mobile: 'clamp(1.65rem, 1.18rem + 2.2vw, 2.45rem)',
58
+ desktop: 'clamp(1.65rem, 1.35rem + 1.1vw, 2.4rem)',
59
+ mobile: 'clamp(1.5rem, 1.22rem + 1.4vw, 2rem)',
58
60
  },
59
61
  large: {
60
- desktop: 'clamp(2.25rem, 1.45rem + 2.75vw, 4.25rem)',
61
- mobile: 'clamp(1.9rem, 1.26rem + 3.2vw, 3.15rem)',
62
+ desktop: 'clamp(1.95rem, 1.48rem + 1.7vw, 3.2rem)',
63
+ mobile: 'clamp(1.75rem, 1.32rem + 2.15vw, 2.5rem)',
62
64
  },
63
65
  xlarge: {
64
- desktop: 'clamp(2.75rem, 1.6rem + 3.8vw, 5.5rem)',
65
- mobile: 'clamp(2.2rem, 1.35rem + 4.4vw, 3.9rem)',
66
+ desktop: 'clamp(2.35rem, 1.58rem + 2.75vw, 4.4rem)',
67
+ mobile: 'clamp(2rem, 1.4rem + 3vw, 3.1rem)',
66
68
  },
67
69
  },
68
70
  h2: {
@@ -163,12 +165,9 @@ const getSizeValue = (
163
165
  const getHeadingSizeValue = (level: HeadingLevel, size: TextSize) => (
164
166
  headingSizeValues[level][size]
165
167
  );
166
- const textWidthValues: Record<TextWidth, string> = {
167
- narrow: 'min(60ch, var(--text-width))',
168
- normal: 'min(72ch, var(--text-width))',
169
- wide: 'min(72ch, var(--image-layout-width))',
170
- };
171
- const surface = resolveSectionSurface(pagePresentation, sectionIndex);
168
+ const surface = isPageTitle
169
+ ? pagePresentation.palette.surfaces.base
170
+ : resolveSectionSurface(pagePresentation, sectionIndex);
172
171
  const typography = pagePresentation.typography.values;
173
172
  const heading = typography.headings[sectionHeadingVisualLevel] as {
174
173
  align: ResponsiveValue<TextAlign>;
@@ -201,7 +200,7 @@ const justifyByAlign: Record<TextAlign, string> = {
201
200
  center: 'center',
202
201
  right: 'flex-end',
203
202
  };
204
- const bodyWidthValue = textWidthValues[typography.body.width as TextWidth];
203
+ const bodyWidthValue = getTextWidthCssValue(typography.body.width as TextWidth);
205
204
  const headingWidth = {
206
205
  desktop: bodyWidthValue,
207
206
  mobile: bodyWidthValue,
@@ -249,6 +248,7 @@ for (const block of section.contentBlocks) {
249
248
  }
250
249
 
251
250
  flushReadingBlocks();
251
+ const firstMediaGroupIndex = contentGroups.findIndex((group) => group.type === 'media');
252
252
  const markdownHeadingStyle = (level: 'h3' | 'h4') => {
253
253
  const headingConfig = markdownHeadings[level];
254
254
  const fontSize = getHeadingSizeValue(level, headingConfig.size);
@@ -298,10 +298,12 @@ const sectionStyle = [
298
298
  ---
299
299
 
300
300
  <section
301
- aria-labelledby={section.id}
301
+ aria-labelledby={headingId}
302
302
  class:list={[
303
303
  'site-section',
304
304
  {
305
+ 'site-section-page-title': isPageTitle,
306
+ 'site-section-page-title-empty': isPageTitle && section.contentBlocks.length === 0,
305
307
  'site-section-first-block-is-visual': firstContentBlockIsVisual,
306
308
  'site-section-has-margin-notes': hasMarginNotes,
307
309
  },
@@ -311,7 +313,7 @@ const sectionStyle = [
311
313
  >
312
314
  <div class="section-card">
313
315
  <header class="section-header">
314
- <HeadingTag id={section.id} set:html={section.titleHtml} />
316
+ <HeadingTag id={headingId} set:html={section.titleHtml} />
315
317
  </header>
316
318
 
317
319
  <div class:list={['section-body', { 'section-body-has-media': hasMedia, 'section-body-text-only': !hasMedia }]}>
@@ -381,7 +383,7 @@ const sectionStyle = [
381
383
  title={section.title}
382
384
  images={block.images}
383
385
  maxAvailableWidthPercent={visualTheme.images.maxAvailableWidthPercent}
384
- priorityFirstImage={prioritizeFirstImage && groupIndex === 0}
386
+ priorityFirstImage={prioritizeFirstImage && groupIndex === firstMediaGroupIndex}
385
387
  />
386
388
  );
387
389
  }
@@ -395,7 +397,7 @@ const sectionStyle = [
395
397
  title={section.title}
396
398
  images={block.images}
397
399
  maxAvailableWidthPercent={visualTheme.images.maxAvailableWidthPercent}
398
- priorityFirstImage={prioritizeFirstImage && groupIndex === 0}
400
+ priorityFirstImage={prioritizeFirstImage && groupIndex === firstMediaGroupIndex}
399
401
  />
400
402
  );
401
403
  })}
@@ -0,0 +1,35 @@
1
+ ---
2
+ import projectConfig from '../../scripts/lib/project-config.mjs';
3
+ import { withBasePath } from '../lib/basePath';
4
+ import { getListedPageNavigationTree, type PageNavigationEntry } from '../lib/siteNavigation';
5
+ import type { SitePage } from '../lib/sitePages';
6
+ import NavigationPageTree from './NavigationPageTree.astro';
7
+
8
+ interface Props {
9
+ currentPage: SitePage;
10
+ pageNavigation: PageNavigationEntry[];
11
+ }
12
+
13
+ const { currentPage, pageNavigation } = Astro.props;
14
+ const roots = getListedPageNavigationTree(pageNavigation);
15
+ const activeRoot = roots.find((node) => (
16
+ currentPage.pageDirectory === node.page.pageDirectory
17
+ || currentPage.pageDirectory.startsWith(`${node.page.pageDirectory}/pages/`)
18
+ ));
19
+ ---
20
+
21
+ {activeRoot && (
22
+ <aside
23
+ class="tree-local-navigation"
24
+ data-navigation-root={withBasePath(projectConfig.site.basePath, activeRoot.page.pathname)}
25
+ >
26
+ <nav aria-label={`${activeRoot.page.title}: ${projectConfig.locale.labels.siteNavigation}`}>
27
+ <NavigationPageTree
28
+ nodes={[activeRoot]}
29
+ currentPage={currentPage}
30
+ variant="sidebar"
31
+ integratePageSections
32
+ />
33
+ </nav>
34
+ </aside>
35
+ )}