@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.
- package/README.md +18 -9
- package/astro.config.mjs +35 -3
- package/bin/norna-cli.mjs +1 -1
- package/docs/README.md +16 -14
- package/docs/commands.md +2 -2
- package/docs/configuration.md +25 -2
- package/docs/content.md +92 -103
- package/docs/design/navigation-and-theme-plan.md +186 -0
- package/docs/design/norna-diagram-design.md +9 -9
- package/docs/design/site-examples-structure.md +15 -11
- package/docs/engine-development.md +19 -22
- package/docs/getting-started.md +21 -16
- package/docs/images-and-metadata.md +12 -9
- package/docs/pages.md +190 -0
- package/docs/public-files.md +49 -36
- package/docs/publishing.md +19 -3
- package/docs/site-files.md +46 -38
- package/docs/sitewide-content.md +20 -36
- package/docs/theme.md +109 -143
- package/docs/typography.md +19 -31
- package/examples/README.md +10 -4
- package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/site.schema.json +17 -41
- package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/sitewide.schema.json +26 -67
- package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/theme.schema.json +185 -88
- package/examples/complete-sites/dog-shelter-multi-page/.astro/data-store.json +1 -1
- package/examples/complete-sites/dog-shelter-multi-page/.astro/dev-local.json +4 -4
- package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.json +5 -5
- package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.log +33 -23
- package/examples/complete-sites/dog-shelter-multi-page/README.md +1 -1
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/_metadata.json +8 -8
- package/examples/complete-sites/dog-shelter-multi-page/site/.norna/generated-images.json +20 -20
- package/examples/complete-sites/dog-shelter-multi-page/site/{content.md → pages/000-home/content.md} +3 -4
- package/examples/complete-sites/dog-shelter-multi-page/site/{routes → pages}/010-dogs/content.md +3 -4
- package/examples/complete-sites/dog-shelter-multi-page/site/{routes → pages}/020-adopt/content.md +3 -4
- package/examples/complete-sites/dog-shelter-multi-page/site/sitewide-content.yaml +2 -4
- package/examples/complete-sites/dog-shelter-single-page/.astro/collections/site.schema.json +17 -41
- package/examples/complete-sites/dog-shelter-single-page/.astro/collections/sitewide.schema.json +26 -67
- package/examples/complete-sites/dog-shelter-single-page/.astro/collections/theme.schema.json +185 -88
- package/examples/complete-sites/dog-shelter-single-page/.astro/data-store.json +1 -1
- package/examples/complete-sites/dog-shelter-single-page/.astro/dev-local.json +8 -0
- package/examples/complete-sites/dog-shelter-single-page/.astro/dev.json +5 -5
- package/examples/complete-sites/dog-shelter-single-page/.astro/dev.log +16 -12
- package/examples/complete-sites/dog-shelter-single-page/README.md +1 -1
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/_metadata.json +8 -8
- package/examples/complete-sites/dog-shelter-single-page/site/.norna/generated-images.json +12 -12
- package/examples/complete-sites/dog-shelter-single-page/site/{content.md → pages/000-home/content.md} +5 -2
- package/examples/complete-sites/dog-shelter-single-page/site/sitewide-content.yaml +2 -4
- package/examples/feature-demos/media-and-surfaces/.astro/collections/site.schema.json +17 -41
- package/examples/feature-demos/media-and-surfaces/.astro/collections/sitewide.schema.json +26 -67
- package/examples/feature-demos/media-and-surfaces/.astro/collections/theme.schema.json +180 -91
- package/examples/feature-demos/media-and-surfaces/.astro/data-store.json +1 -1
- package/examples/feature-demos/media-and-surfaces/.astro/dev.log +4 -14
- package/examples/feature-demos/media-and-surfaces/README.md +2 -2
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/_metadata.json +9 -9
- package/examples/feature-demos/media-and-surfaces/site/.norna/generated-images.json +16 -16
- package/examples/feature-demos/media-and-surfaces/site/{content.md → pages/000-home/content.md} +4 -7
- package/examples/feature-demos/media-and-surfaces/site/{routes → pages}/010-media/content.md +6 -5
- package/examples/feature-demos/media-and-surfaces/site/{routes → pages}/020-surfaces/content.md +9 -7
- package/examples/feature-demos/media-and-surfaces/site/pages/020-surfaces/theme.yaml +2 -0
- package/examples/feature-demos/media-and-surfaces/site/sitewide-content.yaml +0 -2
- package/examples/feature-demos/sitewide-content/README.md +3 -3
- package/examples/feature-demos/sitewide-content/site/pages/000-home/content.md +17 -0
- package/examples/feature-demos/sitewide-content/site/pages/010-logo-and-navigation/content.md +17 -0
- package/examples/feature-demos/sitewide-content/site/{routes → pages}/020-notices/content.md +7 -6
- package/examples/feature-demos/sitewide-content/site/{routes → pages}/030-footer/content.md +6 -5
- package/examples/feature-demos/sitewide-content/site/sitewide-content.yaml +4 -13
- package/examples/feature-demos/theme-preset-documentation/README.md +16 -0
- package/examples/feature-demos/theme-preset-documentation/site/.norna/generated-images.json +74 -0
- package/examples/feature-demos/{theme-presets/site/routes/020-documentation → theme-preset-documentation/site/pages/000-home}/content.md +4 -7
- package/examples/feature-demos/theme-preset-documentation/site/sitewide-content.yaml +3 -0
- package/examples/feature-demos/theme-preset-portfolio/README.md +16 -0
- package/examples/feature-demos/theme-preset-portfolio/site/.norna/generated-images.json +74 -0
- package/examples/feature-demos/theme-preset-portfolio/site/config.yaml +1 -0
- package/examples/feature-demos/{theme-presets/site/routes/010-portfolio → theme-preset-portfolio/site/pages/000-home}/content.md +4 -7
- package/examples/feature-demos/theme-preset-portfolio/site/public/robots.txt +2 -0
- package/examples/feature-demos/theme-preset-portfolio/site/sitewide-content.yaml +3 -0
- package/examples/feature-demos/theme-preset-project/README.md +16 -0
- package/examples/feature-demos/theme-preset-project/site/.norna/generated-images.json +74 -0
- package/examples/feature-demos/theme-preset-project/site/config.yaml +1 -0
- package/examples/feature-demos/{theme-presets/site/routes/030-project → theme-preset-project/site/pages/000-home}/content.md +4 -7
- package/examples/feature-demos/theme-preset-project/site/public/robots.txt +2 -0
- package/examples/feature-demos/theme-preset-project/site/sitewide-content.yaml +3 -0
- package/examples/feature-demos/theme-preset-statement/README.md +16 -0
- package/examples/feature-demos/theme-preset-statement/site/.norna/generated-images.json +74 -0
- package/examples/feature-demos/theme-preset-statement/site/config.yaml +1 -0
- package/examples/feature-demos/{theme-presets/site/routes/040-statement → theme-preset-statement/site/pages/000-home}/content.md +4 -7
- package/examples/feature-demos/theme-preset-statement/site/public/robots.txt +2 -0
- package/examples/feature-demos/theme-preset-statement/site/sitewide-content.yaml +3 -0
- package/fixtures/basic/site/{content.md → pages/000-home/content.md} +5 -6
- package/fixtures/basic/site/sitewide-content.yaml +1 -8
- package/fixtures/basic/site/theme.yaml +1 -8
- package/fixtures/content-model-v2/site/{content.md → pages/000-home/content.md} +11 -13
- package/fixtures/content-model-v2/site/pages/010-guide/content.md +18 -0
- package/fixtures/nested-pages/.astro/collections/site.schema.json +35 -0
- package/fixtures/nested-pages/.astro/collections/sitewide.schema.json +97 -0
- package/fixtures/nested-pages/.astro/collections/theme.schema.json +844 -0
- package/fixtures/nested-pages/.astro/content-assets.mjs +4 -0
- package/fixtures/nested-pages/.astro/content-modules.mjs +1 -0
- package/fixtures/nested-pages/.astro/content.d.ts +183 -0
- package/fixtures/nested-pages/.astro/data-store.json +1 -0
- package/fixtures/nested-pages/.astro/dev-local.json +8 -0
- package/fixtures/nested-pages/.astro/dev.log +1128 -0
- package/fixtures/nested-pages/.astro/settings.json +5 -0
- package/fixtures/nested-pages/.astro/types.d.ts +2 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/_metadata.json +74 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/astro-D_GIpJIE.js +434 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/astro-D_GIpJIE.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_axobject-query.js +2702 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/audit-DM9Hkl5B.js +1534 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/highlight-DnZ1ULOL.js +48 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/icons-RyebKi45.js +42 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/icons-RyebKi45.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/package.json +3 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +4 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/settings-DaU-gNnK.js +190 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/settings-DaU-gNnK.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/toolbar-63PA619H.js +458 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/toolbar-63PA619H.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/ui-library--bkcNSYz.js +1016 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/window-jPkpKVcy.js +32 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/window-jPkpKVcy.js.map +1 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/xray-BXCHpEJP.js +111 -0
- package/fixtures/nested-pages/node_modules/.vite/deps/xray-BXCHpEJP.js.map +1 -0
- package/fixtures/nested-pages/site/.norna/generated-images.json +10 -0
- package/fixtures/nested-pages/site/config.yaml +3 -0
- package/fixtures/nested-pages/site/pages/000-home/content.md +15 -0
- package/fixtures/nested-pages/site/pages/010-guides/content.md +15 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/content.md +21 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/images/diagram.svg +21 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/pages/010-macos/content.md +18 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/theme.yaml +2 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/020-workflows/content.md +11 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/030-release-notes/content.md +9 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/040-configuration/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/050-content/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/060-themes/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/070-images/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/080-cards/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/090-notes/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/100-validation/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/110-synchronization/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/120-building/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/130-publishing/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/140-troubleshooting/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/150-migration/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/160-accessibility/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/170-performance/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/180-security/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/190-localization/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/200-upgrading/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/210-faq/content.md +3 -0
- package/fixtures/nested-pages/site/pages/010-guides/theme.yaml +7 -0
- package/fixtures/nested-pages/site/pages/020-reference/content.md +10 -0
- package/fixtures/nested-pages/site/pages/020-reference/pages/010-installation/content.md +11 -0
- package/fixtures/nested-pages/site/theme.yaml +1 -0
- package/package.json +8 -4
- package/schemas/config.schema.json +46 -5
- package/schemas/content-frontmatter.schema.json +28 -63
- package/schemas/manifest.json +4 -2
- package/schemas/page-theme.schema.json +192 -0
- package/schemas/sitewide-content.schema.json +58 -132
- package/schemas/theme.schema.json +434 -152
- package/scripts/check-config.mjs +8 -6
- package/scripts/deploy-site.mjs +6 -8
- package/scripts/doctor.mjs +2 -2
- package/scripts/generate-images.mjs +15 -29
- package/scripts/generate-schemas.mjs +7 -218
- package/scripts/lib/base-path-redirect.mjs +9 -0
- package/scripts/lib/editor-language-service.mjs +125 -79
- package/scripts/lib/example-sites.mjs +15 -3
- package/scripts/lib/heading-ids.mjs +161 -0
- package/scripts/lib/navigation-model.mjs +51 -0
- package/scripts/lib/norna-markdown-blocks.mjs +3 -4
- package/scripts/lib/page-model.mjs +102 -0
- package/scripts/lib/presentation.mjs +25 -17
- package/scripts/lib/project-config.mjs +46 -8
- package/scripts/lib/schema-definitions.mjs +78 -55
- package/scripts/lib/schema-editor-metadata.mjs +519 -0
- package/scripts/lib/schema-value-definitions.mjs +20 -6
- package/scripts/lib/site-content.mjs +159 -97
- package/scripts/lib/site-conventions.mjs +1 -0
- package/scripts/lib/site-paths.mjs +11 -5
- package/scripts/lib/theme-config.mjs +12 -18
- package/scripts/lib/theme-presets.mjs +55 -14
- package/scripts/release.mjs +1 -1
- package/scripts/show-typography.mjs +4 -30
- package/scripts/sync-content-sections.mjs +107 -68
- package/scripts/{test-temporary-visibility.mjs → test-banner-visibility.mjs} +8 -25
- package/scripts/test-client-javascript.mjs +50 -46
- package/scripts/test-content-check.mjs +153 -69
- package/scripts/test-content-model-v2.mjs +566 -285
- package/scripts/test-documentation.mjs +10 -3
- package/scripts/test-editor-language-service.mjs +172 -25
- package/scripts/test-engine-commands.mjs +6 -5
- package/scripts/test-examples.mjs +30 -1
- package/scripts/test-heading-ids.mjs +68 -0
- package/scripts/test-navigation-model.mjs +63 -0
- package/scripts/test-navigation.mjs +11 -2
- package/scripts/test-nested-pages.mjs +125 -0
- package/scripts/test-package-check.mjs +55 -95
- package/scripts/test-page-model.mjs +108 -0
- package/scripts/test-project-config.mjs +22 -3
- package/scripts/test-schemas.mjs +82 -15
- package/scripts/test-theme-presets.mjs +57 -42
- package/src/components/NavigationPageTree.astro +157 -0
- package/src/components/PageContentsLinks.astro +40 -0
- package/src/components/SectionNavigationScript.astro +117 -5
- package/src/components/SiteBreadcrumbs.astro +32 -0
- package/src/components/SiteNavigation.astro +97 -68
- package/src/components/SitePage.astro +93 -55
- package/src/components/SiteSection.astro +29 -27
- package/src/components/SiteTreeNavigation.astro +35 -0
- package/src/components/TreeNavigationScript.astro +83 -0
- package/src/content.config.ts +8 -10
- package/src/layouts/BaseLayout.astro +21 -20
- package/src/lib/pageThemes.ts +44 -0
- package/src/lib/sectionContent.ts +106 -107
- package/src/lib/siteNavigation.ts +49 -0
- package/src/lib/sitePages.ts +96 -38
- package/src/pages/{[slug].astro → [...slug].astro} +2 -2
- package/src/pages/index.astro +2 -1
- package/src/styles/global.css +743 -57
- package/starters/basic/README.md +9 -11
- package/starters/basic/site/{content.md → pages/000-home/content.md} +7 -4
- package/starters/basic/site/sitewide-content.yaml +1 -10
- package/starters/basic/site/theme.yaml +1 -105
- package/starters/project/README.md +14 -14
- package/starters/project/site/{content.md → pages/000-home/content.md} +5 -4
- package/starters/project/site/{routes → pages}/010-guide/content.md +5 -4
- package/starters/project/site/sitewide-content.yaml +1 -10
- package/starters/project/site/theme.yaml +1 -31
- package/docs/routes.md +0 -121
- package/examples/feature-demos/media-and-surfaces/site/routes/020-surfaces/theme.yaml +0 -3
- package/examples/feature-demos/sitewide-content/site/content.md +0 -16
- package/examples/feature-demos/sitewide-content/site/routes/010-identity/content.md +0 -20
- package/examples/feature-demos/theme-presets/README.md +0 -18
- package/examples/feature-demos/theme-presets/site/.norna/generated-images.json +0 -290
- package/examples/feature-demos/theme-presets/site/content.md +0 -17
- package/examples/feature-demos/theme-presets/site/sitewide-content.yaml +0 -12
- package/examples/feature-demos/theme-presets/site/theme.yaml +0 -1
- package/fixtures/content-model-v2/site/routes/010-guide/content.md +0 -17
- package/scripts/lib/route-model.mjs +0 -17
- package/scripts/test-route-model.mjs +0 -32
- package/src/lib/routeThemes.ts +0 -41
- /package/examples/complete-sites/dog-shelter-multi-page/site/{images/intro → pages/000-home/images}/shelter-dog.jpg +0 -0
- /package/examples/complete-sites/dog-shelter-multi-page/site/{routes/010-dogs/images/dogs → pages/010-dogs/images}/rex.jpg +0 -0
- /package/examples/complete-sites/dog-shelter-multi-page/site/{routes/010-dogs/images/dogs → pages/010-dogs/images}/rover.jpg +0 -0
- /package/examples/complete-sites/dog-shelter-multi-page/site/{routes/020-adopt/images/adopt → pages/020-adopt/images}/adoption-dog.jpg +0 -0
- /package/examples/complete-sites/dog-shelter-single-page/site/{images/you-can-help → pages/000-home/images}/foster-dog.jpg +0 -0
- /package/examples/complete-sites/dog-shelter-single-page/site/{images/what-we-do → pages/000-home/images}/rescue-dog.jpg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/cards → pages/010-media/images}/card-carousel.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/cards → pages/010-media/images}/card-stack.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/cards → pages/010-media/images}/card-surfaces.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/carousel → pages/010-media/images}/carousel-one.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/carousel → pages/010-media/images}/carousel-three.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/carousel → pages/010-media/images}/carousel-two.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/stack → pages/010-media/images}/stack-one.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/stack → pages/010-media/images}/stack-two.svg +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-documentation}/site/config.yaml +0 -0
- /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
- /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
- /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
- /package/examples/feature-demos/{theme-presets → theme-preset-documentation}/site/public/robots.txt +0 -0
- /package/examples/feature-demos/{theme-presets/site/routes/020-documentation → theme-preset-documentation/site}/theme.yaml +0 -0
- /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
- /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
- /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
- /package/examples/feature-demos/{theme-presets/site/routes/010-portfolio → theme-preset-portfolio/site}/theme.yaml +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/collections/routeTheme.schema.json +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/collections/site.schema.json +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/collections/sitewide.schema.json +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/collections/theme.schema.json +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/content-assets.mjs +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/content-modules.mjs +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/content.d.ts +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/data-store.json +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/dev-local.json +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/dev.json +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/dev.log +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/settings.json +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/.astro/types.d.ts +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/_metadata.json +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro-D_GIpJIE.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro-D_GIpJIE.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_aria-query.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_axobject-query.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_html-escaper.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/audit-DM9Hkl5B.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/embla-carousel.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/embla-carousel.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/highlight-DnZ1ULOL.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/icons-RyebKi45.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/icons-RyebKi45.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/package.json +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/settings-DaU-gNnK.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/settings-DaU-gNnK.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/toolbar-63PA619H.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/toolbar-63PA619H.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/ui-library--bkcNSYz.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/window-jPkpKVcy.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/window-jPkpKVcy.js.map +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/xray-BXCHpEJP.js +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-project}/node_modules/.vite/deps/xray-BXCHpEJP.js.map +0 -0
- /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
- /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
- /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
- /package/examples/feature-demos/{theme-presets/site/routes/030-project → theme-preset-project/site}/theme.yaml +0 -0
- /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
- /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
- /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
- /package/examples/feature-demos/{theme-presets/site/routes/040-statement → theme-preset-statement/site}/theme.yaml +0 -0
- /package/fixtures/basic/site/{images/work → pages/000-home/images}/.gitkeep +0 -0
- /package/fixtures/content-model-v2/site/{images/card-section → pages/000-home/images}/adopt.svg +0 -0
- /package/fixtures/content-model-v2/site/{images/intro → pages/000-home/images}/detail.jpg +0 -0
- /package/fixtures/content-model-v2/site/{images/intro → pages/000-home/images}/duplicate.jpg +0 -0
- /package/fixtures/content-model-v2/site/{images/card-section → pages/000-home/images}/foster.svg +0 -0
- /package/fixtures/content-model-v2/site/{images/intro → pages/000-home/images}/hero.jpg +0 -0
- /package/fixtures/content-model-v2/site/{images/plain → pages/000-home/images}/image.jpg +0 -0
- /package/fixtures/content-model-v2/site/{images/timed → pages/000-home/images}/slide-one.jpg +0 -0
- /package/fixtures/content-model-v2/site/{images/timed → pages/000-home/images}/slide-three.jpg +0 -0
- /package/fixtures/content-model-v2/site/{images/timed → pages/000-home/images}/slide-two.jpg +0 -0
- /package/fixtures/content-model-v2/site/{routes/010-guide/images/intro → pages/010-guide/images}/duplicate.jpg +0 -0
- /package/starters/basic/site/{images/work → pages/000-home/images}/.gitkeep +0 -0
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
1
|
import {
|
|
4
2
|
defaultTypography,
|
|
5
3
|
resolveTypographyConfig,
|
|
@@ -13,18 +11,12 @@ import {
|
|
|
13
11
|
readSiteFile,
|
|
14
12
|
validateContentFrontmatterStructure,
|
|
15
13
|
validateFrontmatterIndentation,
|
|
16
|
-
validateRouteThemeYamlStructure,
|
|
17
14
|
} from './lib/site-content.mjs';
|
|
18
15
|
import {
|
|
19
|
-
siteContentLabel,
|
|
20
|
-
siteContentPath,
|
|
21
|
-
siteRoutesDir,
|
|
22
16
|
siteThemeLabel,
|
|
23
|
-
siteThemePath,
|
|
24
17
|
} from './lib/site-paths.mjs';
|
|
25
18
|
import { readThemeConfig } from './lib/theme-config.mjs';
|
|
26
19
|
import { resolveThemeConfig } from './lib/theme-presets.mjs';
|
|
27
|
-
import { parseYamlConfig } from './lib/yaml-config.mjs';
|
|
28
20
|
|
|
29
21
|
const mode = process.argv[2] ?? 'show';
|
|
30
22
|
|
|
@@ -197,24 +189,6 @@ const readThemeTypography = async () => {
|
|
|
197
189
|
return resolveAnnotatedTypographyConfig(themeTypographyConfig ?? defaultTypography, siteThemeLabel);
|
|
198
190
|
};
|
|
199
191
|
|
|
200
|
-
const readRouteThemeTypography = async (contentFile, themeTypography) => {
|
|
201
|
-
if (contentFile.isHome) return themeTypography;
|
|
202
|
-
|
|
203
|
-
const routeThemePath = path.join(siteRoutesDir, contentFile.routeDirectory, 'theme.yaml');
|
|
204
|
-
const routeThemeFile = await readFile(routeThemePath, 'utf8').catch((error) => {
|
|
205
|
-
if (error?.code === 'ENOENT') return null;
|
|
206
|
-
throw error;
|
|
207
|
-
});
|
|
208
|
-
if (!routeThemeFile) return themeTypography;
|
|
209
|
-
const routeThemeLabel = `${contentFile.contentLabel.replace(/\/content\.md$/, '')}/theme.yaml`;
|
|
210
|
-
|
|
211
|
-
const routeThemeConfig = resolveThemeConfig(parseYamlConfig(routeThemeFile, routeThemeLabel, {
|
|
212
|
-
validateStructure: validateRouteThemeYamlStructure,
|
|
213
|
-
}), routeThemeLabel);
|
|
214
|
-
const typographyConfig = routeThemeConfig.typography;
|
|
215
|
-
return resolveAnnotatedTypographyConfig(typographyConfig ?? defaultTypography, routeThemeLabel);
|
|
216
|
-
};
|
|
217
|
-
|
|
218
192
|
const readPageTypography = async (contentFile, siteThemeTypography) => {
|
|
219
193
|
const { frontmatter, body } = await readSiteFile(contentFile.contentPath, contentFile.contentLabel);
|
|
220
194
|
const indentationIssues = [];
|
|
@@ -227,8 +201,8 @@ const readPageTypography = async (contentFile, siteThemeTypography) => {
|
|
|
227
201
|
].join('\n'));
|
|
228
202
|
}
|
|
229
203
|
|
|
230
|
-
const pageTypography =
|
|
231
|
-
const sections = getBodySections(body).sections
|
|
204
|
+
const pageTypography = siteThemeTypography;
|
|
205
|
+
const sections = (await getBodySections(body)).sections
|
|
232
206
|
.filter((section) => section.id)
|
|
233
207
|
.map((section) => {
|
|
234
208
|
return {
|
|
@@ -239,7 +213,7 @@ const readPageTypography = async (contentFile, siteThemeTypography) => {
|
|
|
239
213
|
|
|
240
214
|
return {
|
|
241
215
|
source: contentFile.contentLabel,
|
|
242
|
-
|
|
216
|
+
pathname: contentFile.isHome ? '/' : `/${contentFile.pagePath}/`,
|
|
243
217
|
pageTypography,
|
|
244
218
|
sections,
|
|
245
219
|
};
|
|
@@ -259,7 +233,7 @@ if (mode === 'profiles') {
|
|
|
259
233
|
typography: formatAnnotatedTypography(themeTypography),
|
|
260
234
|
},
|
|
261
235
|
pages: Object.fromEntries(pages.map((page) => [
|
|
262
|
-
page.
|
|
236
|
+
page.pathname,
|
|
263
237
|
{
|
|
264
238
|
source: page.source,
|
|
265
239
|
typography: formatAnnotatedTypography(page.pageTypography),
|
|
@@ -13,7 +13,6 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
getBodySections,
|
|
15
15
|
getContentFiles,
|
|
16
|
-
getFrontmatterSections,
|
|
17
16
|
getImageCandidatesByName,
|
|
18
17
|
getDeprecatedInlineStyleReferences,
|
|
19
18
|
readSiteFile,
|
|
@@ -23,6 +22,7 @@ import {
|
|
|
23
22
|
validateFrontmatterIndentation,
|
|
24
23
|
validateThemeYamlStructure,
|
|
25
24
|
} from './lib/site-content.mjs';
|
|
25
|
+
import { getHeadingIdentifierIssues } from './lib/heading-ids.mjs';
|
|
26
26
|
import { readImageDimensions } from './lib/image-dimensions.mjs';
|
|
27
27
|
import {
|
|
28
28
|
siteThemeLabel,
|
|
@@ -65,7 +65,7 @@ const promptForWrite = async () => {
|
|
|
65
65
|
if (!process.stdin.isTTY) return false;
|
|
66
66
|
|
|
67
67
|
const rl = createInterface({ input, output });
|
|
68
|
-
const answer = await rl.question('This will move image files into the page
|
|
68
|
+
const answer = await rl.question('This will move image files into the page image roots where Norna references them. Continue? [y/N] ');
|
|
69
69
|
rl.close();
|
|
70
70
|
|
|
71
71
|
return answer.trim().toLowerCase() === 'y';
|
|
@@ -136,7 +136,7 @@ const getReportLines = (title, unreferencedImages) => {
|
|
|
136
136
|
lines.push(
|
|
137
137
|
'',
|
|
138
138
|
'Unreferenced Images',
|
|
139
|
-
'These files are kept under page
|
|
139
|
+
'These files are kept under page image roots but are not referenced by Norna-managed image references:',
|
|
140
140
|
);
|
|
141
141
|
|
|
142
142
|
for (const imagePath of unreferencedImages) {
|
|
@@ -171,20 +171,18 @@ const getAspectRatioLabel = ({ width, height }) => {
|
|
|
171
171
|
return `${width / divisor}:${height / divisor}`;
|
|
172
172
|
};
|
|
173
173
|
|
|
174
|
-
const getExpectedImagePath = (contentFile,
|
|
175
|
-
path.join(contentFile.imagesDir,
|
|
174
|
+
const getExpectedImagePath = (contentFile, imageName) =>
|
|
175
|
+
path.join(contentFile.imagesDir, imageName);
|
|
176
176
|
|
|
177
|
-
const getExpectedImageLabel = (contentFile,
|
|
178
|
-
`${contentFile.imagesLabel}/${
|
|
177
|
+
const getExpectedImageLabel = (contentFile, imageName) =>
|
|
178
|
+
`${contentFile.imagesLabel}/${imageName}`;
|
|
179
179
|
|
|
180
180
|
const getGlobalImageCandidates = (globalImageCandidatesByName, imageName, expectedPath) =>
|
|
181
181
|
(globalImageCandidatesByName.get(imageName) ?? []).filter(({ imagePath }) => imagePath !== expectedPath);
|
|
182
182
|
|
|
183
|
-
const getReferenceLabel = (contentFile, section) => `${contentFile.contentLabel} [${section.id}]`;
|
|
183
|
+
const getReferenceLabel = (contentFile, section) => `${contentFile.contentLabel} [${section.id ?? 'page title'}]`;
|
|
184
184
|
|
|
185
|
-
const getRootLabel = (contentFile) => contentFile.
|
|
186
|
-
? contentFile.imagesLabel
|
|
187
|
-
: contentFile.imagesLabel.replace(/\/images$/, '');
|
|
185
|
+
const getRootLabel = (contentFile) => contentFile.imagesLabel.replace(/\/images$/, '');
|
|
188
186
|
|
|
189
187
|
const getCandidateLabel = ({ contentFile, imagePath }) =>
|
|
190
188
|
`${contentFile.imagesLabel}/${toPosixPath(path.relative(contentFile.imagesDir, imagePath))}`;
|
|
@@ -217,8 +215,8 @@ const assertCleanGitForCrossRootSync = async (moves) => {
|
|
|
217
215
|
} catch (error) {
|
|
218
216
|
addIssue({
|
|
219
217
|
severity: 'error',
|
|
220
|
-
message: 'Cross-
|
|
221
|
-
fix: 'Run content:sync inside a Git worktree, or move cross-
|
|
218
|
+
message: 'Cross-page content sync requires a clean git working tree, but git status could not be checked.',
|
|
219
|
+
fix: 'Run content:sync inside a Git worktree, or move cross-page image files manually.',
|
|
222
220
|
});
|
|
223
221
|
return;
|
|
224
222
|
}
|
|
@@ -227,7 +225,7 @@ const assertCleanGitForCrossRootSync = async (moves) => {
|
|
|
227
225
|
|
|
228
226
|
addIssue({
|
|
229
227
|
severity: 'error',
|
|
230
|
-
message: 'Cross-
|
|
228
|
+
message: 'Cross-page content sync requires a clean git working tree before moving files between page image roots.',
|
|
231
229
|
fix: `Commit or stash your current changes, then run content:sync again. Current changes:\n${stdout.trim()}`,
|
|
232
230
|
});
|
|
233
231
|
};
|
|
@@ -256,9 +254,9 @@ const addConflictingMoveIssues = () => {
|
|
|
256
254
|
if (destinations.size <= 1) continue;
|
|
257
255
|
|
|
258
256
|
const firstMove = moves[0];
|
|
259
|
-
addSectionIssue(firstMove.contentFile,
|
|
257
|
+
addSectionIssue(firstMove.contentFile, firstMove.section, {
|
|
260
258
|
severity: 'error',
|
|
261
|
-
message: `Cannot relocate "${firstMove.imageName}" because the same source file is referenced from multiple destinations: ${moves.map((move) => getExpectedImageLabel(move.contentFile, move.
|
|
259
|
+
message: `Cannot relocate "${firstMove.imageName}" because the same source file is referenced from multiple destinations: ${moves.map((move) => getExpectedImageLabel(move.contentFile, move.imageName)).join(', ')}.`,
|
|
262
260
|
fix: 'Duplicate the image manually or rename one of the image files so each move has a single destination.',
|
|
263
261
|
});
|
|
264
262
|
}
|
|
@@ -268,9 +266,9 @@ const addConflictingMoveIssues = () => {
|
|
|
268
266
|
if (sources.size <= 1) continue;
|
|
269
267
|
|
|
270
268
|
const firstMove = moves[0];
|
|
271
|
-
addSectionIssue(firstMove.contentFile,
|
|
269
|
+
addSectionIssue(firstMove.contentFile, firstMove.section, {
|
|
272
270
|
severity: 'error',
|
|
273
|
-
message: `Cannot relocate "${firstMove.imageName}" because multiple source files would move to ${getExpectedImageLabel(firstMove.contentFile, firstMove.
|
|
271
|
+
message: `Cannot relocate "${firstMove.imageName}" because multiple source files would move to ${getExpectedImageLabel(firstMove.contentFile, firstMove.imageName)}: ${moves.map((move) => getCandidateLabel(move.sourceContentFile ? { contentFile: move.sourceContentFile, imagePath: move.from } : move)).join(', ')}.`,
|
|
274
272
|
fix: 'Move the intended file manually or rename files so the destination is unambiguous.',
|
|
275
273
|
});
|
|
276
274
|
}
|
|
@@ -284,8 +282,8 @@ const validateImageReference = async (
|
|
|
284
282
|
expectedReferencesByPath,
|
|
285
283
|
) => {
|
|
286
284
|
const imageName = reference.image;
|
|
287
|
-
const expectedPath = getExpectedImagePath(contentFile,
|
|
288
|
-
const expectedLabel = getExpectedImageLabel(contentFile,
|
|
285
|
+
const expectedPath = getExpectedImagePath(contentFile, imageName);
|
|
286
|
+
const expectedLabel = getExpectedImageLabel(contentFile, imageName);
|
|
289
287
|
const expectedExists = await stat(expectedPath).then((entry) => entry.isFile()).catch(() => false);
|
|
290
288
|
|
|
291
289
|
if (expectedExists) {
|
|
@@ -297,8 +295,8 @@ const validateImageReference = async (
|
|
|
297
295
|
if (candidates.length === 0) {
|
|
298
296
|
addSectionIssue(contentFile, section, {
|
|
299
297
|
severity: 'error',
|
|
300
|
-
message: `Image "${imageName}" does not exist at ${expectedLabel} or anywhere under any page
|
|
301
|
-
fix: `Add the source image to ${contentFile.imagesLabel}
|
|
298
|
+
message: `Image "${imageName}" does not exist at ${expectedLabel} or anywhere under any page image root.`,
|
|
299
|
+
fix: `Add the source image directly to ${contentFile.imagesLabel}/ or remove the Norna-managed image reference.`,
|
|
302
300
|
});
|
|
303
301
|
return null;
|
|
304
302
|
}
|
|
@@ -315,7 +313,7 @@ const validateImageReference = async (
|
|
|
315
313
|
const sourceCandidate = candidates[0];
|
|
316
314
|
const sourcePath = sourceCandidate.imagePath;
|
|
317
315
|
const referencesAtCurrentLocation = (expectedReferencesByPath.get(sourcePath) ?? [])
|
|
318
|
-
.filter((expectedReference) => expectedReference.contentFile !== contentFile
|
|
316
|
+
.filter((expectedReference) => expectedReference.contentFile !== contentFile);
|
|
319
317
|
|
|
320
318
|
if (referencesAtCurrentLocation.length > 0) {
|
|
321
319
|
addSectionIssue(contentFile, section, {
|
|
@@ -326,15 +324,17 @@ const validateImageReference = async (
|
|
|
326
324
|
return null;
|
|
327
325
|
}
|
|
328
326
|
|
|
329
|
-
imageMoves.
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
327
|
+
if (!imageMoves.some((move) => move.from === sourcePath && move.to === expectedPath)) {
|
|
328
|
+
imageMoves.push({
|
|
329
|
+
imageName,
|
|
330
|
+
from: sourcePath,
|
|
331
|
+
to: expectedPath,
|
|
332
|
+
contentFile,
|
|
333
|
+
sourceContentFile: sourceCandidate.contentFile,
|
|
334
|
+
section,
|
|
335
|
+
crossRoot: sourceCandidate.contentFile !== contentFile,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
338
|
referencedImagePaths.add(sourcePath);
|
|
339
339
|
|
|
340
340
|
if (!shouldWrite) {
|
|
@@ -342,8 +342,8 @@ const validateImageReference = async (
|
|
|
342
342
|
severity: 'error',
|
|
343
343
|
message: `Image "${imageName}" is used here but is located in ${getCandidateLabel(sourceCandidate)}.`,
|
|
344
344
|
fix: sourceCandidate.contentFile === contentFile
|
|
345
|
-
? 'Run norna content:sync to move it
|
|
346
|
-
: `Run norna content:sync to move it from ${getRootLabel(sourceCandidate.contentFile)} to ${getRootLabel(contentFile)}. Cross-
|
|
345
|
+
? 'Run norna content:sync to move it directly into the current page image root.'
|
|
346
|
+
: `Run norna content:sync to move it from ${getRootLabel(sourceCandidate.contentFile)} to ${getRootLabel(contentFile)}. Cross-page sync requires a clean git working tree when files are moved.`,
|
|
347
347
|
});
|
|
348
348
|
}
|
|
349
349
|
|
|
@@ -412,11 +412,27 @@ for (const contentFile of contentFiles) {
|
|
|
412
412
|
validateFrontmatterIndentation(frontmatter, addIssue);
|
|
413
413
|
validateContentFrontmatterStructure(frontmatter, addIssue);
|
|
414
414
|
|
|
415
|
-
const
|
|
416
|
-
const
|
|
417
|
-
const
|
|
415
|
+
const { headings, pageHeadings, prelude, sections } = await getBodySections(body);
|
|
416
|
+
const headingIdentifierIssues = getHeadingIdentifierIssues(headings);
|
|
417
|
+
const invalidHeadingLines = new Set();
|
|
418
|
+
for (const issue of headingIdentifierIssues) {
|
|
419
|
+
invalidHeadingLines.add(issue.heading.line);
|
|
420
|
+
if (issue.otherHeading) invalidHeadingLines.add(issue.otherHeading.line);
|
|
421
|
+
const line = bodyLineOffset + issue.heading.line;
|
|
422
|
+
const relatedLine = issue.otherHeading
|
|
423
|
+
? ` The other heading is on line ${bodyLineOffset + issue.otherHeading.line}.`
|
|
424
|
+
: '';
|
|
425
|
+
addIssue({
|
|
426
|
+
severity: 'error',
|
|
427
|
+
message: `${contentFile.contentLabel} line ${line}: ${issue.message}${relatedLine}`,
|
|
428
|
+
fix: issue.fix,
|
|
429
|
+
sectionId: `${contentFile.contentLabel}:heading:${issue.heading.line}`,
|
|
430
|
+
sectionLabel: `${contentFile.contentLabel} line ${line}`,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
418
433
|
const sectionsById = new Map();
|
|
419
|
-
const
|
|
434
|
+
const blockResultsBySection = new Map();
|
|
435
|
+
const validSections = new Set();
|
|
420
436
|
const imageCandidatesByName = await getImageCandidatesByName(contentFile.imagesDir);
|
|
421
437
|
|
|
422
438
|
for (const [imageName, candidates] of imageCandidatesByName) {
|
|
@@ -427,36 +443,71 @@ for (const contentFile of contentFiles) {
|
|
|
427
443
|
}
|
|
428
444
|
}
|
|
429
445
|
|
|
446
|
+
if (pageHeadings.length === 0) {
|
|
447
|
+
addContentIssue(contentFile, {
|
|
448
|
+
severity: 'error',
|
|
449
|
+
message: 'The page is missing its Markdown H1 title.',
|
|
450
|
+
fix: 'Add exactly one page title before any sections, for example "# About".',
|
|
451
|
+
});
|
|
452
|
+
} else if (pageHeadings.length > 1) {
|
|
453
|
+
addContentIssue(contentFile, {
|
|
454
|
+
severity: 'error',
|
|
455
|
+
message: `The page contains ${pageHeadings.length} Markdown H1 headings.`,
|
|
456
|
+
fix: 'Keep exactly one H1 page title. Use level 2 headings with explicit ids for sections.',
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
if (sections[0] && !sections[0].isPageTitle) {
|
|
461
|
+
addContentIssue(contentFile, {
|
|
462
|
+
severity: 'error',
|
|
463
|
+
message: `The first content heading is "${sections[0].heading}", but a Norna page must start with its H1 title.`,
|
|
464
|
+
fix: 'Add the page title first, for example "# About".',
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (prelude.trim()) {
|
|
469
|
+
addContentIssue(contentFile, {
|
|
470
|
+
severity: 'error',
|
|
471
|
+
message: 'The page contains content before its first heading.',
|
|
472
|
+
fix: 'Make the Markdown H1 page title the first content after optional frontmatter.',
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
430
476
|
for (const section of sections) {
|
|
431
|
-
if (
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
477
|
+
if (section.isPageTitle) {
|
|
478
|
+
if (
|
|
479
|
+
pageHeadings.length === 1
|
|
480
|
+
&& sections[0] === section
|
|
481
|
+
&& !invalidHeadingLines.has(section.line)
|
|
482
|
+
) {
|
|
483
|
+
validSections.add(section);
|
|
484
|
+
}
|
|
437
485
|
continue;
|
|
438
486
|
}
|
|
439
487
|
|
|
488
|
+
if (invalidHeadingLines.has(section.line) || !section.id) continue;
|
|
489
|
+
|
|
440
490
|
if (sectionsById.has(section.id)) {
|
|
441
491
|
addSectionIssue(contentFile, section, {
|
|
442
492
|
severity: 'error',
|
|
443
|
-
message: `Duplicate Markdown
|
|
444
|
-
fix: '
|
|
493
|
+
message: `Duplicate Markdown heading id "${section.id}".`,
|
|
494
|
+
fix: 'Add a unique explicit id to at least one heading.',
|
|
445
495
|
});
|
|
446
496
|
continue;
|
|
447
497
|
}
|
|
448
498
|
|
|
449
499
|
sectionsById.set(section.id, section);
|
|
500
|
+
validSections.add(section);
|
|
450
501
|
}
|
|
451
502
|
|
|
452
503
|
for (const section of sections) {
|
|
453
|
-
if (!
|
|
504
|
+
if (!validSections.has(section)) continue;
|
|
454
505
|
|
|
455
506
|
const blockResults = extractNornaMarkdownBlockDiagnostics(section.text, {
|
|
456
507
|
label: contentFile.contentLabel,
|
|
457
508
|
lineOffset: bodyLineOffset + section.line - 1,
|
|
458
509
|
});
|
|
459
|
-
|
|
510
|
+
blockResultsBySection.set(section, blockResults);
|
|
460
511
|
|
|
461
512
|
const inlineNoteResults = extractInlineNoteDiagnostics(section.text, {
|
|
462
513
|
label: contentFile.contentLabel,
|
|
@@ -470,17 +521,16 @@ for (const contentFile of contentFiles) {
|
|
|
470
521
|
}
|
|
471
522
|
|
|
472
523
|
for (const reference of getNornaBlockImageReferences(blockResults.blocks)) {
|
|
473
|
-
const expectedPath = getExpectedImagePath(contentFile,
|
|
524
|
+
const expectedPath = getExpectedImagePath(contentFile, reference.image);
|
|
474
525
|
addExpectedReference(globalExpectedReferencesByPath, expectedPath, { contentFile, section, reference });
|
|
475
526
|
}
|
|
476
527
|
}
|
|
477
528
|
|
|
478
529
|
contentFileContexts.push({
|
|
479
530
|
contentFile,
|
|
480
|
-
frontmatterIds,
|
|
481
531
|
sections,
|
|
482
|
-
|
|
483
|
-
|
|
532
|
+
validSections,
|
|
533
|
+
blockResultsBySection,
|
|
484
534
|
body,
|
|
485
535
|
});
|
|
486
536
|
}
|
|
@@ -488,23 +538,12 @@ for (const contentFile of contentFiles) {
|
|
|
488
538
|
for (const context of contentFileContexts) {
|
|
489
539
|
const {
|
|
490
540
|
contentFile,
|
|
491
|
-
frontmatterIds,
|
|
492
541
|
sections,
|
|
493
|
-
|
|
494
|
-
|
|
542
|
+
validSections,
|
|
543
|
+
blockResultsBySection,
|
|
495
544
|
body,
|
|
496
545
|
} = context;
|
|
497
546
|
|
|
498
|
-
for (const id of frontmatterIds) {
|
|
499
|
-
if (!sectionsById.has(id)) {
|
|
500
|
-
addContentIssue(contentFile, {
|
|
501
|
-
severity: 'error',
|
|
502
|
-
message: `Section metadata "${id}" does not match any Markdown section.`,
|
|
503
|
-
fix: `Add a level 2 Markdown heading, for example "## Heading {#${id}}", or remove sections.${id}.`,
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
547
|
for (const styleName of new Set(getDeprecatedInlineStyleReferences(body))) {
|
|
509
548
|
addContentIssue(contentFile, {
|
|
510
549
|
severity: 'error',
|
|
@@ -514,7 +553,7 @@ for (const context of contentFileContexts) {
|
|
|
514
553
|
}
|
|
515
554
|
|
|
516
555
|
for (const section of sections) {
|
|
517
|
-
if (!
|
|
556
|
+
if (!validSections.has(section)) continue;
|
|
518
557
|
|
|
519
558
|
const markdownImages = extractMarkdownImageReferences(section.text);
|
|
520
559
|
for (const reference of markdownImages) {
|
|
@@ -525,7 +564,7 @@ for (const context of contentFileContexts) {
|
|
|
525
564
|
});
|
|
526
565
|
}
|
|
527
566
|
|
|
528
|
-
const { blocks, errors: blockErrors } =
|
|
567
|
+
const { blocks, errors: blockErrors } = blockResultsBySection.get(section) ?? { blocks: [], errors: [] };
|
|
529
568
|
for (const error of blockErrors) {
|
|
530
569
|
addSectionIssue(contentFile, section, {
|
|
531
570
|
severity: 'error',
|
|
@@ -601,5 +640,5 @@ if (!canWrite) {
|
|
|
601
640
|
for (const move of imageMoves) {
|
|
602
641
|
await mkdir(path.dirname(move.to), { recursive: true });
|
|
603
642
|
await rename(move.from, move.to);
|
|
604
|
-
console.log(`Moved image "${move.imageName}" to ${move.contentFile.imagesLabel}
|
|
643
|
+
console.log(`Moved image "${move.imageName}" to ${move.contentFile.imagesLabel}/.`);
|
|
605
644
|
}
|
|
@@ -8,7 +8,7 @@ const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'
|
|
|
8
8
|
const nornaBin = path.join(repoRoot, 'bin', 'norna.mjs');
|
|
9
9
|
const tempParent = path.join(repoRoot, 'node_modules', '.cache');
|
|
10
10
|
await mkdir(tempParent, { recursive: true });
|
|
11
|
-
const tempRoot = await mkdtemp(path.join(tempParent, 'norna-
|
|
11
|
+
const tempRoot = await mkdtemp(path.join(tempParent, 'norna-banner-visibility-'));
|
|
12
12
|
const siteDir = path.join(tempRoot, 'site');
|
|
13
13
|
|
|
14
14
|
const runCli = (args, env = {}) => {
|
|
@@ -34,7 +34,7 @@ const runCli = (args, env = {}) => {
|
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
try {
|
|
37
|
-
await mkdir(path.join(siteDir, '
|
|
37
|
+
await mkdir(path.join(siteDir, 'pages', '000-home'), { recursive: true });
|
|
38
38
|
await mkdir(path.join(siteDir, 'public'), { recursive: true });
|
|
39
39
|
await writeFile(path.join(siteDir, 'public', 'robots.txt'), 'User-agent: *\nAllow: /\n');
|
|
40
40
|
await writeFile(path.join(siteDir, 'config.yaml'), 'url: https://example.com/\n');
|
|
@@ -55,29 +55,14 @@ palette: dark
|
|
|
55
55
|
title: Active banner
|
|
56
56
|
text: Active banner text.
|
|
57
57
|
`);
|
|
58
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
59
|
-
|
|
60
|
-
description: Test site for temporary
|
|
61
|
-
sections:
|
|
62
|
-
expired:
|
|
63
|
-
visible:
|
|
64
|
-
until: "2026-01-01"
|
|
65
|
-
active:
|
|
66
|
-
visible:
|
|
67
|
-
from: "2026-01-01"
|
|
68
|
-
until: "2026-12-31"
|
|
69
|
-
always: {}
|
|
70
|
-
|
|
58
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
59
|
+
page:
|
|
60
|
+
description: Test site for temporary banners.
|
|
71
61
|
---
|
|
72
|
-
## Expired {#expired}
|
|
73
|
-
|
|
74
|
-
Expired section text.
|
|
75
|
-
|
|
76
|
-
## Active {#active}
|
|
77
62
|
|
|
78
|
-
|
|
63
|
+
# Temporary Visibility Test
|
|
79
64
|
|
|
80
|
-
##
|
|
65
|
+
## Content {#content}
|
|
81
66
|
|
|
82
67
|
Always visible section text.
|
|
83
68
|
`);
|
|
@@ -85,13 +70,11 @@ Always visible section text.
|
|
|
85
70
|
runCli(['build'], { NORNA_TODAY: '2026-06-15' });
|
|
86
71
|
|
|
87
72
|
const html = await readFile(path.join(tempRoot, 'dist', 'index.html'), 'utf8');
|
|
88
|
-
assert.match(html, /Active section text/);
|
|
89
73
|
assert.match(html, /Always visible section text/);
|
|
90
|
-
assert.doesNotMatch(html, /Expired section text/);
|
|
91
74
|
assert.match(html, /Active banner text/);
|
|
92
75
|
assert.doesNotMatch(html, /Expired banner text/);
|
|
93
76
|
|
|
94
|
-
console.log('Temporary visibility test passed.');
|
|
77
|
+
console.log('Temporary banner visibility test passed.');
|
|
95
78
|
} finally {
|
|
96
79
|
await rm(tempRoot, { force: true, recursive: true });
|
|
97
80
|
}
|
|
@@ -10,7 +10,8 @@ const tempParent = path.join(repoRoot, 'node_modules', '.cache');
|
|
|
10
10
|
await mkdir(tempParent, { recursive: true });
|
|
11
11
|
const tempRoot = await mkdtemp(path.join(tempParent, 'norna-client-javascript-'));
|
|
12
12
|
const siteDir = path.join(tempRoot, 'site');
|
|
13
|
-
const
|
|
13
|
+
const homeDir = path.join(siteDir, 'pages', '000-home');
|
|
14
|
+
const pageDir = path.join(siteDir, 'pages', '010-details');
|
|
14
15
|
const configPath = path.join(siteDir, 'config.yaml');
|
|
15
16
|
|
|
16
17
|
const runBuild = () => {
|
|
@@ -54,22 +55,20 @@ const writeSvg = (filePath, color) => writeFile(filePath, [
|
|
|
54
55
|
].join('\n'));
|
|
55
56
|
|
|
56
57
|
try {
|
|
58
|
+
await mkdir(homeDir, { recursive: true });
|
|
57
59
|
await mkdir(path.join(siteDir, 'public'), { recursive: true });
|
|
58
60
|
await writeConfig();
|
|
59
61
|
await writeFile(path.join(siteDir, 'theme.yaml'), `typography:
|
|
60
62
|
profile: reading
|
|
61
63
|
palette: paper
|
|
62
64
|
`);
|
|
63
|
-
await writeFile(path.join(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
67
|
-
title: Section navigation
|
|
68
|
-
description: A single page with sticky section navigation.
|
|
69
|
-
sections:
|
|
70
|
-
intro: {}
|
|
71
|
-
more: {}
|
|
65
|
+
await writeFile(path.join(homeDir, 'content.md'), `---
|
|
66
|
+
page:
|
|
67
|
+
description: A single page with sticky section navigation.
|
|
72
68
|
---
|
|
69
|
+
|
|
70
|
+
# Section navigation
|
|
71
|
+
|
|
73
72
|
## Intro {#intro}
|
|
74
73
|
|
|
75
74
|
The section heading is a native anchor target.
|
|
@@ -89,6 +88,11 @@ The navigation enhancement keeps native anchors clear of the sticky header.
|
|
|
89
88
|
assert.match(getScripts(instantHtml)[0], /site-top-anchor-offset/);
|
|
90
89
|
assertScrollBehavior(instantHtml, 'auto', 'The default instant scroll mode');
|
|
91
90
|
assert.match(instantHtml, /<nav class="page-nav"/);
|
|
91
|
+
assert.match(instantHtml, /<nav class="page-nav" aria-label="Page contents">/);
|
|
92
|
+
assert.doesNotMatch(instantHtml, /class="page-nav-label"/);
|
|
93
|
+
assert.doesNotMatch(instantHtml, /<h2 tabindex="-1">Page contents<\/h2>/);
|
|
94
|
+
assert.match(instantHtml, /class="page-nav-page-top" data-page-top href="\/">Section navigation<\/a>/);
|
|
95
|
+
assert.match(instantHtml, /class="mobile-nav-destination mobile-nav-page-top" data-page-top href="\/">/);
|
|
92
96
|
assert.match(instantHtml, /href="#intro"/);
|
|
93
97
|
assert.match(instantHtml, /href="#more"/);
|
|
94
98
|
|
|
@@ -104,27 +108,26 @@ The navigation enhancement keeps native anchors clear of the sticky header.
|
|
|
104
108
|
|
|
105
109
|
await writeConfig();
|
|
106
110
|
|
|
107
|
-
await mkdir(path.join(
|
|
108
|
-
await writeSvg(path.join(
|
|
109
|
-
await writeSvg(path.join(
|
|
110
|
-
await writeFile(path.join(
|
|
111
|
-
|
|
112
|
-
description: A page without interactive features.
|
|
113
|
-
sections:
|
|
114
|
-
intro: {}
|
|
111
|
+
await mkdir(path.join(pageDir, 'images'), { recursive: true });
|
|
112
|
+
await writeSvg(path.join(pageDir, 'images', 'first.svg'), '#7b8f78');
|
|
113
|
+
await writeSvg(path.join(pageDir, 'images', 'second.svg'), '#a7664b');
|
|
114
|
+
await writeFile(path.join(homeDir, 'content.md'), `---
|
|
115
|
+
page:
|
|
116
|
+
description: A page without interactive features.
|
|
115
117
|
---
|
|
118
|
+
|
|
119
|
+
# Static baseline
|
|
120
|
+
|
|
116
121
|
## Intro {#intro}
|
|
117
122
|
|
|
118
123
|
Ordinary Markdown needs no client-side JavaScript.
|
|
119
124
|
`);
|
|
120
|
-
await writeFile(path.join(
|
|
121
|
-
|
|
122
|
-
description: Static managed images.
|
|
123
|
-
sections:
|
|
124
|
-
details: {}
|
|
125
|
+
await writeFile(path.join(pageDir, 'content.md'), `---
|
|
126
|
+
page:
|
|
127
|
+
description: Static managed images.
|
|
125
128
|
---
|
|
126
|
-
## Details {#details}
|
|
127
129
|
|
|
130
|
+
# Details
|
|
128
131
|
Static image stacks remain static.
|
|
129
132
|
|
|
130
133
|
\`\`\`norna-image-stack
|
|
@@ -147,24 +150,26 @@ Static image stacks remain static.
|
|
|
147
150
|
'A page with managed image stacks and cards',
|
|
148
151
|
);
|
|
149
152
|
|
|
150
|
-
await writeFile(path.join(
|
|
151
|
-
|
|
152
|
-
description: A page with an enhanced sidenote.
|
|
153
|
-
sections:
|
|
154
|
-
intro: {}
|
|
153
|
+
await writeFile(path.join(homeDir, 'content.md'), `---
|
|
154
|
+
page:
|
|
155
|
+
description: A page with an enhanced sidenote.
|
|
155
156
|
---
|
|
157
|
+
|
|
158
|
+
# Notes
|
|
159
|
+
|
|
156
160
|
## Intro {#intro}
|
|
157
161
|
|
|
158
162
|
This sentence has additional context.{note-ref}
|
|
159
163
|
|
|
160
164
|
{note: The note remains readable without JavaScript, while alignment is enhanced when JavaScript runs.}
|
|
161
165
|
`);
|
|
162
|
-
await writeFile(path.join(
|
|
163
|
-
|
|
164
|
-
description: An interactive image carousel.
|
|
165
|
-
sections:
|
|
166
|
-
details: {}
|
|
166
|
+
await writeFile(path.join(pageDir, 'content.md'), `---
|
|
167
|
+
page:
|
|
168
|
+
description: An interactive image carousel.
|
|
167
169
|
---
|
|
170
|
+
|
|
171
|
+
# Carousel
|
|
172
|
+
|
|
168
173
|
## Details {#details}
|
|
169
174
|
|
|
170
175
|
\`\`\`norna-image-carousel
|
|
@@ -184,25 +189,24 @@ sections:
|
|
|
184
189
|
assert.match(carouselHtml, /data-carousel/);
|
|
185
190
|
assert.match(getScripts(carouselHtml)[0], /\ssrc=/, 'Carousel JavaScript should be emitted as a module asset.');
|
|
186
191
|
|
|
187
|
-
await writeFile(path.join(
|
|
188
|
-
|
|
189
|
-
description: A page with a dismissible banner.
|
|
190
|
-
sections:
|
|
191
|
-
intro: {}
|
|
192
|
+
await writeFile(path.join(homeDir, 'content.md'), `---
|
|
193
|
+
page:
|
|
194
|
+
description: A page with a dismissible banner.
|
|
192
195
|
---
|
|
196
|
+
|
|
197
|
+
# Banner
|
|
198
|
+
|
|
193
199
|
## Intro {#intro}
|
|
194
200
|
|
|
195
201
|
The only interactive feature is the banner.
|
|
196
202
|
`);
|
|
197
|
-
await writeFile(path.join(
|
|
198
|
-
|
|
199
|
-
description: A second plain page.
|
|
200
|
-
sections:
|
|
201
|
-
details: {}
|
|
203
|
+
await writeFile(path.join(pageDir, 'content.md'), `---
|
|
204
|
+
page:
|
|
205
|
+
description: A second plain page.
|
|
202
206
|
---
|
|
203
|
-
## Details {#details}
|
|
204
207
|
|
|
205
|
-
|
|
208
|
+
# Details
|
|
209
|
+
Plain page content.
|
|
206
210
|
`);
|
|
207
211
|
await writeFile(path.join(siteDir, 'sitewide-content.yaml'), `banners:
|
|
208
212
|
- id: test-banner
|