@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
@@ -9,10 +9,14 @@ const cliPath = path.join(repoRoot, 'bin', 'norna.mjs');
9
9
  const tutorialPath = path.join(repoRoot, 'docs', 'getting-started.md');
10
10
  const scratchRoot = path.join(repoRoot, 'todos');
11
11
  const obsoleteSourceReferences = [
12
+ 'site/content.md',
13
+ 'site/images/',
12
14
  'site/config.md',
13
15
  'site/theme.md',
14
16
  'site/sitewide-content.md',
17
+ 'site/routes/',
15
18
  'route-content.md',
19
+ 'docs/routes.md',
16
20
  'docs/site-structure.md',
17
21
  ];
18
22
  const obsoleteSiteFilenames = new Set([
@@ -129,7 +133,10 @@ const checkObsoleteSiteFiles = async () => {
129
133
  ];
130
134
  const files = (await Promise.all(siteRoots.map(collectFiles))).flat();
131
135
  const obsolete = files
132
- .filter((filePath) => obsoleteSiteFilenames.has(path.basename(filePath)))
136
+ .filter((filePath) => (
137
+ obsoleteSiteFilenames.has(path.basename(filePath))
138
+ || filePath.split(path.sep).includes('routes')
139
+ ))
133
140
  .map((filePath) => path.relative(repoRoot, filePath));
134
141
 
135
142
  assert.deepEqual(obsolete, [], `Obsolete Norna site files:\n${obsolete.join('\n')}`);
@@ -144,8 +151,8 @@ try {
144
151
 
145
152
  await Promise.all([
146
153
  writeFile(
147
- path.join(tutorialSiteRoot, 'site', 'content.md'),
148
- extractTutorialFile(tutorial, 'Replace `site/content.md` with:', 'md'),
154
+ path.join(tutorialSiteRoot, 'site', 'pages', '000-home', 'content.md'),
155
+ extractTutorialFile(tutorial, 'Replace `site/pages/000-home/content.md` with:', 'md'),
149
156
  ),
150
157
  writeFile(
151
158
  path.join(tutorialSiteRoot, 'site', 'sitewide-content.yaml'),
@@ -1,7 +1,9 @@
1
1
  import assert from 'node:assert/strict';
2
- import { mkdtemp, mkdir, rm, writeFile } from 'node:fs/promises';
2
+ import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
+ import projectContext from '../editors/vscode/norna-project.cjs';
6
+ import yamlSchemaCompletions from '../editors/vscode/yaml-schema-completions.cjs';
5
7
  import { documentationRef } from './lib/documentation-links.mjs';
6
8
  import {
7
9
  findNornaSiteRoot,
@@ -16,14 +18,28 @@ import {
16
18
 
17
19
  const root = await mkdtemp(path.join(os.tmpdir(), 'norna-editor-language-'));
18
20
  const siteRoot = path.join(root, 'site');
19
- const homeContentPath = path.join(siteRoot, 'content.md');
20
- const routeContentPath = path.join(siteRoot, 'routes', '010-about', 'content.md');
21
+ const homeContentPath = path.join(siteRoot, 'pages', '000-home', 'content.md');
22
+ const pageContentPath = path.join(siteRoot, 'pages', '010-about', 'content.md');
23
+ const pageThemePath = path.join(siteRoot, 'pages', '010-about', 'theme.yaml');
24
+ const nestedPageContentPath = path.join(siteRoot, 'pages', '010-about', 'pages', '020-team', 'content.md');
25
+ const nestedPageThemePath = path.join(siteRoot, 'pages', '010-about', 'pages', '020-team', 'theme.yaml');
26
+ const installedNornaRoot = path.join(root, 'node_modules', '@janga', 'norna');
27
+ const packageManifestPath = path.join(installedNornaRoot, 'schemas', 'manifest.json');
28
+ const {
29
+ getNornaDocumentContext,
30
+ getNornaProjectContext,
31
+ supportedEditorApiVersion,
32
+ supportedSchemaVersion,
33
+ } = projectContext;
34
+ const { getYamlSchemaSnippetCompletions } = yamlSchemaCompletions;
21
35
 
22
36
  const homeSource = `---
23
- title: Editor fixture
24
- description: Exercises project-local image discovery.
37
+ page:
38
+ description: Exercises project-local image discovery.
25
39
  ---
26
40
 
41
+ # Editor fixture
42
+
27
43
  ## Intro {#intro}
28
44
 
29
45
  Text with a missing note {note-ref}.
@@ -33,28 +49,44 @@ Text with a missing note {note-ref}.
33
49
  - image: portrait.jpg
34
50
  \`\`\`
35
51
  `;
36
- const routeSource = `---
37
- title: About
38
- description: About this fixture.
52
+ const pageSource = `---
53
+ page:
54
+ description: About this fixture.
39
55
  ---
40
56
 
57
+ # About
58
+
41
59
  ## Team {#team}
42
60
 
43
- Route content.
61
+ Page content.
44
62
  `;
45
63
 
46
64
  try {
47
- await mkdir(path.join(siteRoot, 'images', 'intro'), { recursive: true });
48
- await mkdir(path.join(siteRoot, 'routes', '010-about', 'images', 'team'), { recursive: true });
65
+ await mkdir(path.join(installedNornaRoot, 'schemas'), { recursive: true });
66
+ await mkdir(path.join(siteRoot, 'pages', '000-home', 'images'), { recursive: true });
67
+ await mkdir(path.join(siteRoot, 'pages', '010-about', 'images'), { recursive: true });
68
+ await mkdir(path.dirname(nestedPageContentPath), { recursive: true });
49
69
  await mkdir(path.join(siteRoot, 'public'), { recursive: true });
70
+ await writeFile(path.join(root, 'package.json'), JSON.stringify({ name: 'editor-fixture', version: '1.0.0' }));
71
+ await writeFile(path.join(installedNornaRoot, 'package.json'), JSON.stringify({ name: '@janga/norna', version: '9.8.7' }));
72
+ await writeFile(packageManifestPath, JSON.stringify({
73
+ editorApiVersion: supportedEditorApiVersion,
74
+ files: {
75
+ config: 'config.schema.json',
76
+ contentFrontmatter: 'content-frontmatter.schema.json',
77
+ pageTheme: 'page-theme.schema.json',
78
+ sitewideContent: 'sitewide-content.schema.json',
79
+ theme: 'theme.schema.json',
80
+ },
81
+ schemaVersion: supportedSchemaVersion,
82
+ }));
50
83
  await writeFile(path.join(siteRoot, 'config.yaml'), 'url: https://example.com/\n');
51
- await writeFile(path.join(siteRoot, 'sitewide-content.yaml'), `navigation:
52
- label: Editor fixture
53
- logo:
54
- height: 2rem
84
+ await writeFile(path.join(siteRoot, 'theme.yaml'), 'preset: project\n');
85
+ await writeFile(path.join(siteRoot, 'sitewide-content.yaml'), `logo:
86
+ height: 2rem
55
87
  `);
56
- await writeFile(path.join(siteRoot, 'images', 'intro', 'local.jpg'), 'local');
57
- await writeFile(path.join(siteRoot, 'routes', '010-about', 'images', 'team', 'portrait.jpg'), 'portrait');
88
+ await writeFile(path.join(siteRoot, 'pages', '000-home', 'images', 'local.jpg'), 'local');
89
+ await writeFile(path.join(siteRoot, 'pages', '010-about', 'images', 'portrait.jpg'), 'portrait');
58
90
  await writeFile(path.join(siteRoot, 'public', 'logo.svg'), '<svg/>');
59
91
  await writeFile(path.join(siteRoot, 'public', 'logo.png'), 'logo');
60
92
  await writeFile(path.join(siteRoot, 'public', 'favicon.svg'), '<svg/>');
@@ -62,9 +94,106 @@ try {
62
94
  await writeFile(path.join(siteRoot, 'public', 'favion.ico'), 'typo');
63
95
  await writeFile(path.join(siteRoot, 'public', 'favicon-32x32.png'), 'unrecognized');
64
96
  await writeFile(homeContentPath, homeSource);
65
- await writeFile(routeContentPath, routeSource);
97
+ await writeFile(pageContentPath, pageSource);
98
+ await writeFile(pageThemePath, 'layout:\n textWidth: wide\n');
99
+ await writeFile(nestedPageContentPath, pageSource.replace('# About', '# Team'));
100
+ await writeFile(nestedPageThemePath, 'layout:\n contentSpacing: compact\n');
66
101
 
67
102
  assert.equal(await findNornaSiteRoot(homeContentPath), siteRoot);
103
+ assert.equal(getNornaDocumentContext(homeContentPath).schemaKind, 'contentFrontmatter');
104
+ assert.equal(getNornaDocumentContext(homeContentPath).pageDirectory, '000-home');
105
+ assert.equal(getNornaDocumentContext(homeContentPath).nornaPackage.root, installedNornaRoot);
106
+ assert.equal(getNornaDocumentContext(path.join(siteRoot, 'config.yaml')).schemaKind, 'config');
107
+ assert.equal(getNornaDocumentContext(path.join(siteRoot, 'theme.yaml')).schemaKind, 'theme');
108
+ assert.equal(getNornaDocumentContext(path.join(siteRoot, 'sitewide-content.yaml')).schemaKind, 'sitewideContent');
109
+ assert.equal(getNornaDocumentContext(pageContentPath).pageDirectory, '010-about');
110
+ assert.equal(getNornaDocumentContext(pageThemePath).schemaKind, 'pageTheme');
111
+ assert.equal(getNornaDocumentContext(nestedPageContentPath).pageDirectory, '010-about/pages/020-team');
112
+ assert.equal(getNornaDocumentContext(nestedPageContentPath).schemaKind, 'contentFrontmatter');
113
+ assert.equal(getNornaDocumentContext(nestedPageThemePath).schemaKind, 'pageTheme');
114
+ assert.equal(getNornaProjectContext(path.join(siteRoot, 'public', 'logo.svg')).siteRoot, siteRoot);
115
+ assert.equal(getNornaDocumentContext(path.join(siteRoot, 'public', 'logo.svg')), null);
116
+ assert.equal(getNornaDocumentContext(path.join(root, 'README.md')), null);
117
+ assert.equal(getNornaDocumentContext(path.join(root, 'docs', 'content.md')), null);
118
+ await mkdir(path.join(siteRoot, 'pages', 'about'), { recursive: true });
119
+ await writeFile(path.join(siteRoot, 'pages', 'about', 'content.md'), pageSource);
120
+ assert.equal(getNornaDocumentContext(path.join(siteRoot, 'pages', 'about', 'content.md')), null);
121
+ assert.equal(getNornaDocumentContext(path.join(siteRoot, 'pages', '000-home', 'pages', '010-news', 'content.md')), null);
122
+ await mkdir(path.join(siteRoot, 'pages', '010-about', 'nested'), { recursive: true });
123
+ await writeFile(path.join(siteRoot, 'pages', '010-about', 'nested', 'content.md'), pageSource);
124
+ assert.equal(getNornaDocumentContext(path.join(siteRoot, 'pages', '010-about', 'nested', 'content.md')), null);
125
+
126
+ const compatibleManifest = await readFile(packageManifestPath, 'utf8');
127
+ await writeFile(packageManifestPath, JSON.stringify({
128
+ ...JSON.parse(compatibleManifest),
129
+ editorApiVersion: supportedEditorApiVersion + 1,
130
+ }));
131
+ assert.equal(getNornaDocumentContext(homeContentPath).schemaCompatible, true);
132
+ assert.equal(getNornaDocumentContext(homeContentPath).editorCompatible, false);
133
+ await writeFile(packageManifestPath, JSON.stringify({
134
+ ...JSON.parse(compatibleManifest),
135
+ schemaVersion: supportedSchemaVersion + 1,
136
+ }));
137
+ assert.equal(getNornaDocumentContext(homeContentPath).schemaCompatible, false);
138
+ await writeFile(packageManifestPath, compatibleManifest);
139
+
140
+ const sitewideSchema = JSON.parse(await readFile(path.join(process.cwd(), 'schemas', 'sitewide-content.schema.json'), 'utf8'));
141
+ const bannerSource = 'banners:\n - ';
142
+ const bannerSnippets = getYamlSchemaSnippetCompletions({
143
+ lineText: ' - ',
144
+ offset: bannerSource.length,
145
+ schema: sitewideSchema,
146
+ source: bannerSource,
147
+ });
148
+ assert.equal(bannerSnippets.length, 1);
149
+ assert.equal(bannerSnippets[0].label, 'Norna: Warning banner');
150
+ assert.equal(bannerSnippets[0].text, ` - id: \${1:project-status}
151
+ tone: warning
152
+ title: \${2:Important notice}
153
+ text: \${3:Brief explanation.}`);
154
+ assert.deepEqual(getYamlSchemaSnippetCompletions({
155
+ lineText: ' - id: existing',
156
+ offset: bannerSource.length,
157
+ schema: sitewideSchema,
158
+ source: bannerSource,
159
+ }), []);
160
+ const unrelatedSequence = 'other:\n - ';
161
+ assert.deepEqual(getYamlSchemaSnippetCompletions({
162
+ lineText: ' - ',
163
+ offset: unrelatedSequence.length,
164
+ schema: sitewideSchema,
165
+ source: unrelatedSequence,
166
+ }), []);
167
+ const themeSchema = JSON.parse(await readFile(path.join(process.cwd(), 'schemas', 'theme.schema.json'), 'utf8'));
168
+ const gutterSource = 'layout:\n gutter: ';
169
+ const gutterSnippets = getYamlSchemaSnippetCompletions({
170
+ lineText: ' gutter: ',
171
+ offset: gutterSource.length,
172
+ schema: themeSchema,
173
+ source: gutterSource,
174
+ });
175
+ assert.equal(gutterSnippets[0].label, 'Norna: Responsive page gutter');
176
+ assert.equal(gutterSnippets[0].text, ` gutter:
177
+ desktop: \${1:clamp(1.25rem, 4vw, 3rem)}
178
+ mobile: \${2:1rem}`);
179
+ const typographySource = 'typography:\n overrides: ';
180
+ const typographySnippets = getYamlSchemaSnippetCompletions({
181
+ lineText: ' overrides: ',
182
+ offset: typographySource.length,
183
+ schema: themeSchema,
184
+ source: typographySource,
185
+ });
186
+ assert.equal(typographySnippets[0].label, 'Norna: Typography overrides');
187
+ assert.match(typographySnippets[0].text, /headings:\n h2:/);
188
+ const buildInfoSource = 'footer:\n buildInfo: ';
189
+ const buildInfoSnippets = getYamlSchemaSnippetCompletions({
190
+ lineText: ' buildInfo: ',
191
+ offset: buildInfoSource.length,
192
+ schema: sitewideSchema,
193
+ source: buildInfoSource,
194
+ });
195
+ assert.deepEqual(buildInfoSnippets, []);
196
+
68
197
  const publicAssetStatus = await getSitePublicAssetStatus(homeContentPath);
69
198
  assert.deepEqual(publicAssetStatus.logos, ['logo.png', 'logo.svg']);
70
199
  assert.deepEqual(publicAssetStatus.browserIcons, ['favicon.svg']);
@@ -75,12 +204,14 @@ try {
75
204
  await rm(path.join(siteRoot, 'public', 'logo.svg'), { force: true });
76
205
  await rm(path.join(siteRoot, 'public', 'logo.png'), { force: true });
77
206
  const missingLogoStatus = await getSitePublicAssetStatus(homeContentPath);
78
- assert.ok(missingLogoStatus.issues.some(({ code, line }) => code === 'missing-logo-file' && line === 3));
207
+ assert.ok(missingLogoStatus.issues.some(({ code, line }) => code === 'missing-logo-file' && line === 1));
79
208
  assert.equal(nornaBlockDefinitions['norna-image-stack'].description.includes('vertical stack'), true);
80
209
  assert.match(
81
210
  nornaBlockDefinitions['norna-image-stack'].documentation,
82
- new RegExp(`/blob/${documentationRef.replaceAll('.', '\\.')}\/docs/content\\.md#norna-blocks`),
211
+ new RegExp(`/blob/${documentationRef.replaceAll('.', '\\.')}\/docs/content\\.md#image-stack`),
83
212
  );
213
+ assert.match(nornaBlockDefinitions['norna-image-carousel'].documentation, /docs\/content\.md#image-carousel/);
214
+ assert.match(nornaBlockDefinitions['norna-card-list'].documentation, /docs\/content\.md#card-list/);
84
215
  assert.equal(nornaBlockDefinitions['norna-card-list'].options.layout.default, 'image-top');
85
216
 
86
217
  const stackFieldSource = homeSource.replace('- image: portrait.jpg', '- image: portrait.jpg\n ');
@@ -116,7 +247,7 @@ try {
116
247
  line: localDefinitionLine,
117
248
  source: homeSource,
118
249
  });
119
- assert.deepEqual(localDefinition.files, [path.join(siteRoot, 'images', 'intro', 'local.jpg')]);
250
+ assert.deepEqual(localDefinition.files, [path.join(siteRoot, 'pages', '000-home', 'images', 'local.jpg')]);
120
251
 
121
252
  const diagnostics = await getMarkdownDiagnostics({ documentPath: homeContentPath, source: homeSource });
122
253
  assert.ok(diagnostics.some(({ code, message }) => code === 'image-needs-sync' && message.includes('Run "norna content:sync"')));
@@ -125,7 +256,23 @@ try {
125
256
  documentPath: homeContentPath,
126
257
  source: homeSource.replace('## Intro {#intro}', '## Intro'),
127
258
  });
128
- assert.ok(missingIdDiagnostics.some(({ code }) => code === 'missing-section-id'));
259
+ assert.equal(missingIdDiagnostics.some(({ code }) => code === 'missing-section-id'), false);
260
+ const duplicateIdDiagnostics = await getMarkdownDiagnostics({
261
+ documentPath: homeContentPath,
262
+ source: homeSource.replace('## Intro {#intro}', '## Intro\n\n### Intro'),
263
+ });
264
+ assert.ok(duplicateIdDiagnostics.some(({ code }) => code === 'duplicate-heading-id'));
265
+ const missingTitleDiagnostics = await getMarkdownDiagnostics({
266
+ documentPath: homeContentPath,
267
+ source: homeSource.replace('# Editor fixture', '## Editor fixture {#editor-fixture}'),
268
+ });
269
+ assert.ok(missingTitleDiagnostics.some(({ code }) => code === 'missing-page-title'));
270
+ const codeHeadingDiagnostics = await getMarkdownDiagnostics({
271
+ documentPath: homeContentPath,
272
+ source: `${homeSource}\n\`\`\`md\n# Example title\n## Example section\n\`\`\`\n`,
273
+ });
274
+ assert.equal(codeHeadingDiagnostics.some(({ code }) => code === 'duplicate-page-title'), false);
275
+ assert.equal(codeHeadingDiagnostics.some(({ code }) => code === 'duplicate-heading-id'), false);
129
276
  const unclosedBlockDiagnostics = await getMarkdownDiagnostics({
130
277
  documentPath: homeContentPath,
131
278
  source: homeSource.replace(/\n```\n$/, '\n'),
@@ -137,14 +284,14 @@ try {
137
284
  });
138
285
  assert.ok(markdownImageDiagnostics.some(({ code }) => code === 'local-markdown-image'));
139
286
 
140
- await writeFile(routeContentPath, routeSource.replace('Route content.', `\`\`\`norna-image-stack
287
+ await writeFile(pageContentPath, pageSource.replace('Page content.', `\`\`\`norna-image-stack
141
288
  - image: portrait.jpg
142
289
  \`\`\``));
143
290
  const sharedDiagnostics = await getMarkdownDiagnostics({ documentPath: homeContentPath, source: homeSource });
144
291
  assert.ok(sharedDiagnostics.some(({ message }) => message.includes('is still referenced by')));
145
292
 
146
- await mkdir(path.join(siteRoot, 'routes', '020-contact', 'images', 'people'), { recursive: true });
147
- await writeFile(path.join(siteRoot, 'routes', '020-contact', 'images', 'people', 'portrait.jpg'), 'duplicate');
293
+ await mkdir(path.join(siteRoot, 'pages', '020-contact', 'images'), { recursive: true });
294
+ await writeFile(path.join(siteRoot, 'pages', '020-contact', 'images', 'portrait.jpg'), 'duplicate');
148
295
  const ambiguousDiagnostics = await getMarkdownDiagnostics({ documentPath: homeContentPath, source: homeSource });
149
296
  assert.ok(ambiguousDiagnostics.some(({ message }) => message.includes('is ambiguous')));
150
297
 
@@ -39,9 +39,9 @@ try {
39
39
  assert.match(deployWatchHelpResult.stdout, /Default: deploy\.yml/);
40
40
 
41
41
  const directSiteRoot = path.join(tempRoot, 'current-directory-site');
42
- await mkdir(directSiteRoot, { recursive: true });
42
+ await mkdir(path.join(directSiteRoot, 'pages', '000-home'), { recursive: true });
43
43
  await writeFile(path.join(directSiteRoot, 'config.yaml'), 'url: https://example.com/\n');
44
- await writeFile(path.join(directSiteRoot, 'content.md'), '---\ntitle: Direct Site\ndescription: Direct site fixture.\n---\n\n## Intro {#intro}\n\nText.\n');
44
+ await writeFile(path.join(directSiteRoot, 'pages', '000-home', 'content.md'), '# Direct Site\n\n## Intro {#intro}\n\nText.\n');
45
45
  const directSiteProjectRoot = await realpath(tempRoot);
46
46
  const directSiteDoctorResult = runCli(['doctor'], {
47
47
  cwd: directSiteRoot,
@@ -99,7 +99,7 @@ try {
99
99
  assert.match(showResult.stdout, /theme:/);
100
100
  assert.match(showResult.stdout, /pages:/);
101
101
  assert.match(showResult.stdout, /\s+\/:/);
102
- assert.match(showResult.stdout, /value: restrained/);
102
+ assert.match(showResult.stdout, /value: reading/);
103
103
  assert.match(showResult.stdout, /source: "site\/theme\.yaml"/);
104
104
  assert.match(showResult.stdout, /intro:/);
105
105
 
@@ -116,7 +116,7 @@ try {
116
116
  assert.equal(customStandalonePackageJson.scripts['norna:dev'], 'norna --site-dir presentation dev:local');
117
117
  assert.equal(customStandalonePackageJson.scripts['norna:check'], 'norna --site-dir presentation check');
118
118
  assert.equal(customStandalonePackageJson.scripts['norna:build'], 'norna --site-dir presentation build');
119
- await readFile(path.join(customStandaloneSiteRoot, 'presentation', 'content.md'));
119
+ await readFile(path.join(customStandaloneSiteRoot, 'presentation', 'pages', '000-home', 'content.md'));
120
120
  await readFile(path.join(customStandaloneSiteRoot, 'presentation', 'config.yaml'));
121
121
  await readFile(path.join(customStandaloneSiteRoot, 'presentation', 'theme.yaml'));
122
122
 
@@ -143,7 +143,7 @@ try {
143
143
  assert.equal(mixedPackageJson.scripts['norna:check'], 'norna --site-dir presentation check');
144
144
  assert.equal(mixedPackageJson.scripts['norna:engine:update'], 'norna --site-dir presentation engine:update');
145
145
  assert.equal(mixedPackageJson.scripts['norna:engine:version'], 'norna --site-dir presentation engine:version');
146
- await readFile(path.join(mixedProjectRoot, 'presentation', 'content.md'));
146
+ await readFile(path.join(mixedProjectRoot, 'presentation', 'pages', '000-home', 'content.md'));
147
147
  await readFile(path.join(mixedProjectRoot, 'presentation', 'config.yaml'));
148
148
  await readFile(path.join(mixedProjectRoot, 'presentation', 'theme.yaml'));
149
149
 
@@ -185,6 +185,7 @@ try {
185
185
  "['run', 'schemas:generate']",
186
186
  "['test']",
187
187
  "['commit', '-m', `Release ${releaseTag}`]",
188
+ "['tag', '-a', releaseTag, '-m', `Release ${releaseTag}`]",
188
189
  "['run', 'release:publish']",
189
190
  "['push', '--follow-tags']",
190
191
  ];
@@ -1,5 +1,7 @@
1
+ import assert from 'node:assert/strict';
1
2
  import { existsSync } from 'node:fs';
2
- import { readFile } from 'node:fs/promises';
3
+ import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
3
5
  import path from 'node:path';
4
6
  import { fileURLToPath } from 'node:url';
5
7
  import { getExampleSites } from './lib/example-sites.mjs';
@@ -9,6 +11,33 @@ const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
9
11
  const cliPath = path.join(root, 'bin', 'norna.mjs');
10
12
  const examples = await getExampleSites(root);
11
13
 
14
+ const discoveryRoot = await mkdtemp(path.join(tmpdir(), 'norna-example-discovery-'));
15
+ try {
16
+ for (const category of ['complete-sites', 'feature-demos']) {
17
+ await mkdir(path.join(discoveryRoot, 'examples', category), { recursive: true });
18
+ }
19
+ const validSite = path.join(discoveryRoot, 'examples', 'complete-sites', 'valid', 'site');
20
+ await mkdir(path.join(validSite, 'pages', '000-home'), { recursive: true });
21
+ await writeFile(path.join(validSite, 'config.yaml'), 'url: https://example.com/\n');
22
+ await writeFile(path.join(validSite, 'pages', '000-home', 'content.md'), '# Example\n');
23
+ await mkdir(path.join(
24
+ discoveryRoot,
25
+ 'examples',
26
+ 'feature-demos',
27
+ 'removed-example',
28
+ 'site',
29
+ '.norna',
30
+ 'public',
31
+ 'images',
32
+ 'generated',
33
+ ), { recursive: true });
34
+
35
+ const discovered = await getExampleSites(discoveryRoot);
36
+ assert.deepEqual(discovered.map(({ name }) => name), ['valid']);
37
+ } finally {
38
+ await rm(discoveryRoot, { recursive: true, force: true });
39
+ }
40
+
12
41
  for (const example of examples) {
13
42
  console.log(`\nBuilding ${example.siteLabel}`);
14
43
  await runInherit(process.execPath, [cliPath, '--site-dir', example.siteLabel, 'build'], { cwd: root });
@@ -0,0 +1,68 @@
1
+ import assert from 'node:assert/strict';
2
+ import {
3
+ formatHeadingIdentifierIssue,
4
+ getHeadingIdentifierIssues,
5
+ getMarkdownHeadings,
6
+ slugifyHeadingText,
7
+ } from './lib/heading-ids.mjs';
8
+
9
+ assert.equal(slugifyHeadingText('Förstå Norna'), 'forsta-norna');
10
+ assert.equal(slugifyHeadingText('Café Über Straße'), 'cafe-uber-strasse');
11
+ assert.equal(slugifyHeadingText('Smørrebrød & œuvre'), 'smorrebrod-oeuvre');
12
+ assert.equal(slugifyHeadingText("What's new?"), 'whats-new');
13
+
14
+ const { headings } = await getMarkdownHeadings(`# Page title
15
+
16
+ ## A **bold** [heading](https://example.com)
17
+
18
+ ### Café details
19
+
20
+ ## Renamed heading {#stable-id}
21
+
22
+ \`\`\`md
23
+ ## Not a heading
24
+ \`\`\`
25
+ `);
26
+
27
+ assert.deepEqual(
28
+ headings.map(({ depth, explicitId, id, title }) => ({ depth, explicitId, id, title })),
29
+ [
30
+ { depth: 1, explicitId: null, id: null, title: 'Page title' },
31
+ { depth: 2, explicitId: null, id: 'a-bold-heading', title: 'A bold heading' },
32
+ { depth: 3, explicitId: null, id: 'cafe-details', title: 'Café details' },
33
+ { depth: 2, explicitId: 'stable-id', id: 'stable-id', title: 'Renamed heading' },
34
+ ],
35
+ );
36
+ assert.deepEqual(getHeadingIdentifierIssues(headings), []);
37
+
38
+ const collision = await getMarkdownHeadings(`# Page
39
+
40
+ ## Förstå
41
+
42
+ ### Forsta
43
+ `);
44
+ assert.equal(getHeadingIdentifierIssues(collision.headings)[0]?.code, 'duplicate-heading-id');
45
+ assert.equal(
46
+ formatHeadingIdentifierIssue(getHeadingIdentifierIssues(collision.headings)[0], 'site/pages/000-home/content.md'),
47
+ [
48
+ 'site/pages/000-home/content.md: Two headings resolve to id "forsta".',
49
+ '- line 3: "Förstå"',
50
+ '- line 5: "Forsta"',
51
+ 'Add a unique explicit id to at least one heading, for example {#forsta-details}.',
52
+ ].join('\n'),
53
+ );
54
+
55
+ const invalid = await getMarkdownHeadings(`# Page
56
+
57
+ ## Symbols 🎉
58
+
59
+ ### 日本語
60
+
61
+ ## Explicit {#Not Valid}
62
+ `);
63
+ assert.deepEqual(
64
+ getHeadingIdentifierIssues(invalid.headings).map(({ code }) => code),
65
+ ['empty-heading-id', 'invalid-heading-id'],
66
+ );
67
+
68
+ console.log('Heading id tests passed.');
@@ -0,0 +1,63 @@
1
+ import assert from 'node:assert/strict';
2
+ import {
3
+ getAutomaticNavigationMode,
4
+ navigationModeNames,
5
+ resolveNavigationModel,
6
+ } from './lib/navigation-model.mjs';
7
+
8
+ const home = (headings = []) => ({
9
+ isHome: true,
10
+ listed: true,
11
+ depth: 1,
12
+ headings,
13
+ });
14
+ const page = (headings = [], overrides = {}) => ({
15
+ isHome: false,
16
+ listed: true,
17
+ depth: 1,
18
+ headings,
19
+ ...overrides,
20
+ });
21
+ const h2 = { depth: 2 };
22
+ const h3 = { depth: 3 };
23
+
24
+ assert.deepEqual(navigationModeNames, ['automatic', 'sections', 'top', 'tree']);
25
+ assert.equal(getAutomaticNavigationMode([home([h2, h3])]), 'sections');
26
+ assert.equal(getAutomaticNavigationMode([home([h2]), page([h2])]), 'top');
27
+ assert.equal(getAutomaticNavigationMode([home([h2]), page([h2, h3])]), 'tree');
28
+ assert.equal(
29
+ getAutomaticNavigationMode([home([h2]), page([h2, h3], { listed: false })]),
30
+ 'sections',
31
+ );
32
+ assert.equal(
33
+ getAutomaticNavigationMode([home([h2]), page([], { depth: 2 })]),
34
+ 'top',
35
+ );
36
+ assert.equal(
37
+ getAutomaticNavigationMode([home([h2]), page([h2], { depth: 2 })]),
38
+ 'tree',
39
+ );
40
+
41
+ for (const mode of ['sections', 'top', 'tree']) {
42
+ const resolved = resolveNavigationModel({
43
+ mode,
44
+ pages: [home([h2]), page([h2, h3])],
45
+ });
46
+ assert.equal(resolved.requestedMode, mode);
47
+ assert.equal(resolved.mode, mode);
48
+ }
49
+
50
+ const automatic = resolveNavigationModel({
51
+ pages: [home([h2]), page([h2, h3])],
52
+ });
53
+ assert.equal(automatic.requestedMode, 'automatic');
54
+ assert.equal(automatic.mode, 'tree');
55
+ assert.equal(automatic.listedPageCount, 2);
56
+ assert.equal(automatic.maximumDepth, 3);
57
+
58
+ assert.throws(
59
+ () => resolveNavigationModel({ mode: 'sidebar', pages: [home()] }),
60
+ /Unknown navigation mode "sidebar".*automatic, sections, top, tree/,
61
+ );
62
+
63
+ console.log('Navigation model test passed.');
@@ -5,10 +5,19 @@ import { fileURLToPath } from 'node:url';
5
5
 
6
6
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
7
7
  const cliPath = path.join(root, 'bin', 'norna.mjs');
8
- const navigationDemoSiteDir = path.join(root, 'examples', 'feature-demos', 'media-and-surfaces', 'site');
9
8
  const host = '127.0.0.1';
10
9
  const npmBin = process.platform === 'win32' ? 'npm.cmd' : 'npm';
11
- const testTargets = process.argv.slice(2);
10
+ const cliArguments = process.argv.slice(2);
11
+ const siteDirOptionIndex = cliArguments.indexOf('--site-dir');
12
+ const configuredSiteDir = siteDirOptionIndex === -1
13
+ ? path.join('examples', 'feature-demos', 'media-and-surfaces', 'site')
14
+ : cliArguments[siteDirOptionIndex + 1];
15
+ if (!configuredSiteDir || configuredSiteDir.startsWith('--')) {
16
+ throw new Error('Use --site-dir <path> to select a navigation test site.');
17
+ }
18
+ if (siteDirOptionIndex !== -1) cliArguments.splice(siteDirOptionIndex, 2);
19
+ const navigationDemoSiteDir = path.resolve(root, configuredSiteDir);
20
+ const testTargets = cliArguments;
12
21
  const playwrightTargets = testTargets.length > 0 ? testTargets : ['tests/navigation.spec.ts'];
13
22
 
14
23
  const sleep = (milliseconds) => new Promise((resolve) => {