@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
|
@@ -50,7 +50,7 @@ const createTempSite = async ({ underRepoCache = false } = {}) => {
|
|
|
50
50
|
await mkdir(tempParent, { recursive: true });
|
|
51
51
|
const root = await mkdtemp(path.join(tempParent, 'norna-content-model-v2-'));
|
|
52
52
|
const siteDir = path.join(root, 'site');
|
|
53
|
-
await mkdir(siteDir, { recursive: true });
|
|
53
|
+
await mkdir(path.join(siteDir, 'pages', '000-home'), { recursive: true });
|
|
54
54
|
await writeFile(path.join(siteDir, 'config.yaml'), 'url: https://example.com/\n');
|
|
55
55
|
await writeFile(path.join(siteDir, 'theme.yaml'), `typography:
|
|
56
56
|
profile: reading
|
|
@@ -109,89 +109,255 @@ test('content model v2 fixture checks and builds', async () => {
|
|
|
109
109
|
await runNorna(['--site-dir', siteDir, 'build']);
|
|
110
110
|
|
|
111
111
|
const manifest = JSON.parse(await readFile(path.join(siteDir, '.norna', 'generated-images.json'), 'utf8'));
|
|
112
|
-
assert.ok(manifest['images/
|
|
113
|
-
assert.ok(manifest['
|
|
112
|
+
assert.ok(manifest['pages/000-home/images/duplicate.jpg']);
|
|
113
|
+
assert.ok(manifest['pages/010-guide/images/duplicate.jpg']);
|
|
114
|
+
|
|
115
|
+
const homepageHtml = await readFile(path.join(path.dirname(siteDir), 'dist', 'index.html'), 'utf8');
|
|
116
|
+
assert.match(homepageHtml, /site-section-page-title/);
|
|
117
|
+
assert.match(homepageHtml, /site-section-page-title-empty/);
|
|
118
|
+
assert.match(homepageHtml, /fetchpriority="high"/);
|
|
114
119
|
} finally {
|
|
115
120
|
await rm(root, { recursive: true, force: true });
|
|
116
121
|
}
|
|
117
122
|
});
|
|
118
123
|
|
|
119
|
-
test('
|
|
120
|
-
const { root, siteDir } = await createTempSite(
|
|
124
|
+
test('the removed root page structure is rejected with a migration hint', async () => {
|
|
125
|
+
const { root, siteDir } = await createTempSite();
|
|
121
126
|
try {
|
|
122
|
-
await writeFile(path.join(siteDir, '
|
|
123
|
-
|
|
127
|
+
await writeFile(path.join(siteDir, 'content.md'), `# Home
|
|
128
|
+
Home content.
|
|
124
129
|
`);
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
|
|
131
|
+
await assert.rejects(
|
|
132
|
+
runNorna(['--site-dir', siteDir, 'content:check']),
|
|
133
|
+
(error) => {
|
|
134
|
+
assert.match(error.output, /The old root-page structure is no longer supported: site\/content\.md\./);
|
|
135
|
+
assert.match(error.output, /Move the homepage content to site\/pages\/000-home\/content\.md/);
|
|
136
|
+
return true;
|
|
137
|
+
},
|
|
138
|
+
);
|
|
139
|
+
} finally {
|
|
140
|
+
await rm(root, { recursive: true, force: true });
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('the removed routes directory is rejected with a page migration hint', async () => {
|
|
145
|
+
const { root, siteDir } = await createTempSite();
|
|
146
|
+
try {
|
|
147
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `# Home
|
|
148
|
+
Home content.
|
|
127
149
|
`);
|
|
128
150
|
await mkdir(path.join(siteDir, 'routes', '010-guide'), { recursive: true });
|
|
129
|
-
await writeFile(path.join(siteDir, 'content.md'),
|
|
130
|
-
|
|
131
|
-
|
|
151
|
+
await writeFile(path.join(siteDir, 'routes', '010-guide', 'content.md'), 'Old page content.\n');
|
|
152
|
+
|
|
153
|
+
await assert.rejects(
|
|
154
|
+
runNorna(['--site-dir', siteDir, 'content:check']),
|
|
155
|
+
(error) => {
|
|
156
|
+
assert.match(error.output, /site\/routes is no longer supported\. Rename it to site\/pages and use NNN-page-id directory names\./);
|
|
157
|
+
return true;
|
|
158
|
+
},
|
|
159
|
+
);
|
|
160
|
+
} finally {
|
|
161
|
+
await rm(root, { recursive: true, force: true });
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('the required home page cannot own child pages', async () => {
|
|
166
|
+
const { root, siteDir } = await createTempSite();
|
|
167
|
+
try {
|
|
168
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `# Home
|
|
169
|
+
|
|
170
|
+
## Welcome {#welcome}
|
|
171
|
+
|
|
172
|
+
Home content.
|
|
173
|
+
`);
|
|
174
|
+
const homeChildDir = path.join(siteDir, 'pages', '000-home', 'pages', '010-news');
|
|
175
|
+
await mkdir(homeChildDir, { recursive: true });
|
|
176
|
+
await writeFile(path.join(homeChildDir, 'content.md'), '# News\n\nNews content.\n');
|
|
177
|
+
|
|
178
|
+
await assert.rejects(
|
|
179
|
+
runNorna(['--site-dir', siteDir, 'content:check']),
|
|
180
|
+
(error) => {
|
|
181
|
+
assert.match(error.output, /site\/pages\/000-home is the homepage and cannot contain child pages\./);
|
|
182
|
+
assert.match(error.output, /Move these page directories beside 000-home under site\/pages\//);
|
|
183
|
+
assert.match(error.output, /site\/pages\/000-home\/pages\/010-news/);
|
|
184
|
+
return true;
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
} finally {
|
|
188
|
+
await rm(root, { recursive: true, force: true });
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('page theme changes page presentation while preserving site visual identity', async () => {
|
|
193
|
+
const { root, siteDir } = await createTempSite({ underRepoCache: true });
|
|
194
|
+
try {
|
|
195
|
+
await writeFile(path.join(siteDir, 'theme.yaml'), `preset: documentation
|
|
196
|
+
palette: dark
|
|
197
|
+
`);
|
|
198
|
+
await mkdir(path.join(siteDir, 'pages', '010-guide'), { recursive: true });
|
|
199
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
200
|
+
page:
|
|
201
|
+
description: Root page
|
|
132
202
|
---
|
|
133
203
|
|
|
204
|
+
# Root
|
|
205
|
+
|
|
134
206
|
## Home {#home}
|
|
135
207
|
|
|
136
208
|
Root content.
|
|
137
209
|
`);
|
|
138
|
-
await writeFile(path.join(siteDir, '
|
|
139
|
-
|
|
140
|
-
description: Guide page
|
|
141
|
-
navigation:
|
|
142
|
-
label: Guide
|
|
210
|
+
await writeFile(path.join(siteDir, 'pages', '010-guide', 'content.md'), `---
|
|
211
|
+
page:
|
|
212
|
+
description: Guide page
|
|
143
213
|
---
|
|
144
214
|
|
|
145
|
-
|
|
215
|
+
# Guide
|
|
216
|
+
Page content.
|
|
217
|
+
`);
|
|
218
|
+
await mkdir(path.join(siteDir, 'pages', '010-guide', 'pages', '010-detail'), { recursive: true });
|
|
219
|
+
await writeFile(path.join(siteDir, 'pages', '010-guide', 'pages', '010-detail', 'content.md'), `# Detail
|
|
146
220
|
|
|
147
|
-
|
|
221
|
+
Inherited page content.
|
|
222
|
+
`);
|
|
223
|
+
await writeFile(path.join(siteDir, 'pages', '010-guide', 'theme.yaml'), `layout:
|
|
224
|
+
contentSpacing: spacious
|
|
225
|
+
textWidth: wide
|
|
226
|
+
sections:
|
|
227
|
+
backgroundPattern: cycling
|
|
148
228
|
`);
|
|
149
|
-
await writeFile(path.join(siteDir, '
|
|
150
|
-
|
|
151
|
-
palette: light
|
|
229
|
+
await writeFile(path.join(siteDir, 'pages', '010-guide', 'pages', '010-detail', 'theme.yaml'), `layout:
|
|
230
|
+
textWidth: narrow
|
|
152
231
|
`);
|
|
153
232
|
|
|
154
233
|
await runNorna(['--site-dir', siteDir, 'build']);
|
|
155
234
|
const rootHtml = await readFile(path.join(path.dirname(siteDir), 'dist', 'index.html'), 'utf8');
|
|
156
|
-
const
|
|
235
|
+
const pageHtml = await readFile(path.join(path.dirname(siteDir), 'dist', 'guide', 'index.html'), 'utf8');
|
|
236
|
+
const detailHtml = await readFile(path.join(path.dirname(siteDir), 'dist', 'guide', 'detail', 'index.html'), 'utf8');
|
|
157
237
|
assert.match(rootHtml, /--color-page: #000000/);
|
|
158
|
-
assert.match(
|
|
159
|
-
assert.match(
|
|
238
|
+
assert.match(pageHtml, /--color-page: #000000/);
|
|
239
|
+
assert.match(rootHtml, /--font-sans: Georgia, 'Times New Roman', serif/);
|
|
240
|
+
assert.match(pageHtml, /--font-sans: Georgia, 'Times New Roman', serif/);
|
|
241
|
+
assert.match(pageHtml, /--space-section-to-section-desktop: clamp\(2\.25rem, 5vw, 4\.5rem\)/);
|
|
242
|
+
assert.match(pageHtml, /--section-body-width-desktop: min\(72ch,/);
|
|
243
|
+
assert.match(detailHtml, /--space-section-to-section-desktop: clamp\(2\.25rem, 5vw, 4\.5rem\)/);
|
|
244
|
+
assert.match(detailHtml, /--section-body-width-desktop: min\(60ch,/);
|
|
245
|
+
assert.match(pageHtml, /<a href="\/">\s*Root\s*<\/a>/);
|
|
160
246
|
} finally {
|
|
161
247
|
await rm(root, { recursive: true, force: true });
|
|
162
248
|
}
|
|
163
249
|
});
|
|
164
250
|
|
|
165
|
-
test('
|
|
251
|
+
test('page metadata, navigation logo, and page listing have separate roles', async () => {
|
|
166
252
|
const { root, siteDir } = await createTempSite({ underRepoCache: true });
|
|
167
253
|
try {
|
|
168
|
-
await mkdir(path.join(siteDir, '
|
|
169
|
-
await writeFile(path.join(siteDir, '
|
|
170
|
-
|
|
171
|
-
|
|
254
|
+
await mkdir(path.join(siteDir, 'public'), { recursive: true });
|
|
255
|
+
await writeFile(path.join(siteDir, 'public', 'logo.svg'), '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"/>');
|
|
256
|
+
await writeFile(path.join(siteDir, 'sitewide-content.yaml'), `logo:
|
|
257
|
+
height: 2rem
|
|
258
|
+
`);
|
|
259
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `# Welcome
|
|
260
|
+
|
|
261
|
+
## Intro {#intro}
|
|
262
|
+
|
|
263
|
+
Homepage content.
|
|
264
|
+
`);
|
|
265
|
+
await mkdir(path.join(siteDir, 'pages', '010-guide'), { recursive: true });
|
|
266
|
+
await writeFile(path.join(siteDir, 'pages', '010-guide', 'content.md'), `---
|
|
267
|
+
page:
|
|
268
|
+
description: The visible guide page.
|
|
269
|
+
---
|
|
270
|
+
|
|
271
|
+
# Guide
|
|
272
|
+
Guide content.
|
|
273
|
+
`);
|
|
274
|
+
await mkdir(path.join(siteDir, 'pages', '020-private'), { recursive: true });
|
|
275
|
+
await writeFile(path.join(siteDir, 'pages', '020-private', 'content.md'), `---
|
|
276
|
+
navigation:
|
|
277
|
+
listed: false
|
|
278
|
+
---
|
|
279
|
+
|
|
280
|
+
# Unlisted
|
|
281
|
+
Public but unlisted content.
|
|
282
|
+
`);
|
|
283
|
+
|
|
284
|
+
await runNorna(['--site-dir', siteDir, 'build']);
|
|
285
|
+
const distDir = path.join(path.dirname(siteDir), 'dist');
|
|
286
|
+
const homeHtml = await readFile(path.join(distDir, 'index.html'), 'utf8');
|
|
287
|
+
const guideHtml = await readFile(path.join(distDir, 'guide', 'index.html'), 'utf8');
|
|
288
|
+
const unlistedHtml = await readFile(path.join(distDir, 'private', 'index.html'), 'utf8');
|
|
289
|
+
|
|
290
|
+
assert.match(homeHtml, /<title>Welcome<\/title>/);
|
|
291
|
+
assert.doesNotMatch(homeHtml, /<meta name="description"/);
|
|
292
|
+
assert.match(homeHtml, /<img class="site-brand-logo"[^>]+alt="Welcome"/);
|
|
293
|
+
assert.match(homeHtml, /<a href="\/" aria-current="page">\s*Welcome\s*<\/a>/);
|
|
294
|
+
assert.match(homeHtml, /<a href="\/guide\/">\s*Guide\s*<\/a>/);
|
|
295
|
+
assert.doesNotMatch(homeHtml, />\s*Unlisted\s*<\/a>/);
|
|
296
|
+
assert.match(guideHtml, /<meta name="description" content="The visible guide page\."/);
|
|
297
|
+
assert.match(unlistedHtml, /Public but unlisted content/);
|
|
298
|
+
} finally {
|
|
299
|
+
await rm(root, { recursive: true, force: true });
|
|
300
|
+
}
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test('footer build information uses the site language and fixed formatting', async () => {
|
|
304
|
+
const { root, siteDir } = await createTempSite({ underRepoCache: true });
|
|
305
|
+
try {
|
|
306
|
+
await writeFile(path.join(siteDir, 'config.yaml'), 'url: https://example.com/\nlanguage: sv-SE\n');
|
|
307
|
+
await writeFile(path.join(siteDir, 'sitewide-content.yaml'), `footer:
|
|
308
|
+
buildInfo: true
|
|
309
|
+
`);
|
|
310
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
311
|
+
page:
|
|
312
|
+
description: Footer build information.
|
|
172
313
|
---
|
|
173
314
|
|
|
315
|
+
# Footer
|
|
316
|
+
|
|
174
317
|
## Home {#home}
|
|
175
318
|
|
|
176
|
-
|
|
319
|
+
Content.
|
|
177
320
|
`);
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
321
|
+
|
|
322
|
+
await runNorna(['--site-dir', siteDir, 'build']);
|
|
323
|
+
const html = await readFile(path.join(path.dirname(siteDir), 'dist', 'index.html'), 'utf8');
|
|
324
|
+
assert.match(html, /<span>Byggd [^<]+<\/span>/);
|
|
325
|
+
} finally {
|
|
326
|
+
await rm(root, { recursive: true, force: true });
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
test('page theme cannot define navigation logo settings', async () => {
|
|
331
|
+
const { root, siteDir } = await createTempSite({ underRepoCache: true });
|
|
332
|
+
try {
|
|
333
|
+
await mkdir(path.join(siteDir, 'pages', '010-guide'), { recursive: true });
|
|
334
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
335
|
+
page:
|
|
336
|
+
description: Root page
|
|
181
337
|
---
|
|
182
338
|
|
|
183
|
-
|
|
339
|
+
# Root
|
|
340
|
+
|
|
341
|
+
## Home {#home}
|
|
184
342
|
|
|
185
|
-
|
|
343
|
+
Root content.
|
|
186
344
|
`);
|
|
187
|
-
await writeFile(path.join(siteDir, '
|
|
188
|
-
|
|
345
|
+
await writeFile(path.join(siteDir, 'pages', '010-guide', 'content.md'), `---
|
|
346
|
+
page:
|
|
347
|
+
description: Guide page
|
|
348
|
+
---
|
|
349
|
+
|
|
350
|
+
# Guide
|
|
351
|
+
Page content.
|
|
352
|
+
`);
|
|
353
|
+
await writeFile(path.join(siteDir, 'pages', '010-guide', 'theme.yaml'), `logo:
|
|
354
|
+
height: 3rem
|
|
189
355
|
`);
|
|
190
356
|
|
|
191
357
|
await assert.rejects(
|
|
192
358
|
runNorna(['--site-dir', siteDir, 'config:check']),
|
|
193
359
|
(error) => {
|
|
194
|
-
assert.match(error.output, /may not define navigation
|
|
360
|
+
assert.match(error.output, /may not define navigation logo settings\. Optional logo display settings belong under "logo:" in site\/sitewide-content\.yaml/);
|
|
195
361
|
return true;
|
|
196
362
|
},
|
|
197
363
|
);
|
|
@@ -203,16 +369,14 @@ Route content.
|
|
|
203
369
|
test('navigation logo settings require one conventional logo file', async () => {
|
|
204
370
|
const { root, siteDir } = await createTempSite();
|
|
205
371
|
try {
|
|
206
|
-
await writeFile(path.join(siteDir, 'sitewide-content.yaml'), `
|
|
207
|
-
|
|
208
|
-
logo:
|
|
209
|
-
height: 2rem
|
|
372
|
+
await writeFile(path.join(siteDir, 'sitewide-content.yaml'), `logo:
|
|
373
|
+
height: 2rem
|
|
210
374
|
`);
|
|
211
375
|
|
|
212
376
|
await assert.rejects(
|
|
213
377
|
runNorna(['--site-dir', siteDir, 'config:check']),
|
|
214
378
|
(error) => {
|
|
215
|
-
assert.match(error.output, /
|
|
379
|
+
assert.match(error.output, /Site-wide logo is configured, but no logo file was found/);
|
|
216
380
|
assert.match(error.output, /site\/public\/logo\.svg/);
|
|
217
381
|
return true;
|
|
218
382
|
},
|
|
@@ -225,9 +389,6 @@ test('navigation logo settings require one conventional logo file', async () =>
|
|
|
225
389
|
test('multiple conventional navigation logo files stop config validation', async () => {
|
|
226
390
|
const { root, siteDir } = await createTempSite();
|
|
227
391
|
try {
|
|
228
|
-
await writeFile(path.join(siteDir, 'sitewide-content.yaml'), `navigation:
|
|
229
|
-
label: Conflicting logos
|
|
230
|
-
`);
|
|
231
392
|
await mkdir(path.join(siteDir, 'public'), { recursive: true });
|
|
232
393
|
await writeFile(path.join(siteDir, 'public', 'logo.svg'), '<svg/>');
|
|
233
394
|
await writeFile(path.join(siteDir, 'public', 'logo.png'), 'png');
|
|
@@ -249,11 +410,13 @@ test('multiple conventional navigation logo files stop config validation', async
|
|
|
249
410
|
test('content:check warns for local Markdown images but not external Markdown images', async () => {
|
|
250
411
|
const { root, siteDir } = await createTempSite();
|
|
251
412
|
try {
|
|
252
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
253
|
-
|
|
254
|
-
description: Fixture
|
|
413
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
414
|
+
page:
|
|
415
|
+
description: Fixture
|
|
255
416
|
---
|
|
256
417
|
|
|
418
|
+
# Markdown Image Warning
|
|
419
|
+
|
|
257
420
|
## Intro {#intro}
|
|
258
421
|
|
|
259
422
|

|
|
@@ -275,11 +438,13 @@ description: Fixture
|
|
|
275
438
|
test('managed SVG images are copied as static image output', async () => {
|
|
276
439
|
const { root, siteDir } = await createTempSite();
|
|
277
440
|
try {
|
|
278
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
279
|
-
|
|
280
|
-
description: Fixture
|
|
441
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
442
|
+
page:
|
|
443
|
+
description: Fixture
|
|
281
444
|
---
|
|
282
445
|
|
|
446
|
+
# SVG Fixture
|
|
447
|
+
|
|
283
448
|
## Intro {#intro}
|
|
284
449
|
|
|
285
450
|
\`\`\`norna-image-stack
|
|
@@ -287,18 +452,18 @@ description: Fixture
|
|
|
287
452
|
alt: Diagram
|
|
288
453
|
\`\`\`
|
|
289
454
|
`);
|
|
290
|
-
await mkdir(path.join(siteDir, '
|
|
291
|
-
await writeFile(path.join(siteDir, '
|
|
455
|
+
await mkdir(path.join(siteDir, 'pages', '000-home', 'images'), { recursive: true });
|
|
456
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'diagram.svg'), '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 620"><rect width="900" height="620"/></svg>\n');
|
|
292
457
|
|
|
293
458
|
await runContentScript(siteDir, ['--check']);
|
|
294
459
|
await runNorna(['--site-dir', siteDir, 'images']);
|
|
295
460
|
|
|
296
461
|
const manifest = JSON.parse(await readFile(path.join(siteDir, '.norna', 'generated-images.json'), 'utf8'));
|
|
297
|
-
const entry = manifest['images/
|
|
462
|
+
const entry = manifest['pages/000-home/images/diagram.svg'];
|
|
298
463
|
assert.equal(entry.kind, 'static');
|
|
299
464
|
assert.equal(entry.width, 900);
|
|
300
465
|
assert.equal(entry.height, 620);
|
|
301
|
-
assert.match(entry.src, /^\/images\/original\/images\/
|
|
466
|
+
assert.match(entry.src, /^\/images\/original\/pages\/000-home\/images\/diagram-[a-f0-9]{8}\.svg$/);
|
|
302
467
|
assert.equal(entry.variants, undefined);
|
|
303
468
|
assert.equal(await fileExists(path.join(siteDir, '.norna', 'public', entry.src.replace(/^\//, ''))), true);
|
|
304
469
|
} finally {
|
|
@@ -309,11 +474,13 @@ description: Fixture
|
|
|
309
474
|
test('inline notes render as linked numbered margin notes', async () => {
|
|
310
475
|
const { root, siteDir } = await createTempSite({ underRepoCache: true });
|
|
311
476
|
try {
|
|
312
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
313
|
-
|
|
314
|
-
description: Fixture
|
|
477
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
478
|
+
page:
|
|
479
|
+
description: Fixture
|
|
315
480
|
---
|
|
316
481
|
|
|
482
|
+
# Inline notes
|
|
483
|
+
|
|
317
484
|
## Intro {#intro}
|
|
318
485
|
|
|
319
486
|
The first paragraph points to an explanation.{note-ref}
|
|
@@ -347,11 +514,13 @@ The second paragraph has its own explanation.{note-ref}
|
|
|
347
514
|
test('updated managed SVG images get updated static output', async () => {
|
|
348
515
|
const { root, siteDir } = await createTempSite();
|
|
349
516
|
try {
|
|
350
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
351
|
-
|
|
352
|
-
description: Fixture
|
|
517
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
518
|
+
page:
|
|
519
|
+
description: Fixture
|
|
353
520
|
---
|
|
354
521
|
|
|
522
|
+
# SVG Update Fixture
|
|
523
|
+
|
|
355
524
|
## Intro {#intro}
|
|
356
525
|
|
|
357
526
|
\`\`\`norna-image-stack
|
|
@@ -359,22 +528,22 @@ description: Fixture
|
|
|
359
528
|
alt: Diagram
|
|
360
529
|
\`\`\`
|
|
361
530
|
`);
|
|
362
|
-
await mkdir(path.join(siteDir, '
|
|
363
|
-
const sourcePath = path.join(siteDir, '
|
|
531
|
+
await mkdir(path.join(siteDir, 'pages', '000-home', 'images'), { recursive: true });
|
|
532
|
+
const sourcePath = path.join(siteDir, 'pages', '000-home', 'images', 'diagram.svg');
|
|
364
533
|
await writeFile(sourcePath, '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 620"><rect width="900" height="620" fill="red"/></svg>\n');
|
|
365
534
|
|
|
366
535
|
await runContentScript(siteDir, ['--check']);
|
|
367
536
|
await runNorna(['--site-dir', siteDir, 'images']);
|
|
368
537
|
|
|
369
538
|
const firstManifest = JSON.parse(await readFile(path.join(siteDir, '.norna', 'generated-images.json'), 'utf8'));
|
|
370
|
-
const firstEntry = firstManifest['images/
|
|
539
|
+
const firstEntry = firstManifest['pages/000-home/images/diagram.svg'];
|
|
371
540
|
const firstOutputPath = path.join(siteDir, '.norna', 'public', firstEntry.src.replace(/^\//, ''));
|
|
372
541
|
|
|
373
542
|
await writeFile(sourcePath, '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 620"><rect width="900" height="620" fill="blue"/></svg>\n');
|
|
374
543
|
await runNorna(['--site-dir', siteDir, 'images']);
|
|
375
544
|
|
|
376
545
|
const secondManifest = JSON.parse(await readFile(path.join(siteDir, '.norna', 'generated-images.json'), 'utf8'));
|
|
377
|
-
const secondEntry = secondManifest['images/
|
|
546
|
+
const secondEntry = secondManifest['pages/000-home/images/diagram.svg'];
|
|
378
547
|
const secondOutputPath = path.join(siteDir, '.norna', 'public', secondEntry.src.replace(/^\//, ''));
|
|
379
548
|
|
|
380
549
|
assert.notEqual(secondEntry.sourceHash, firstEntry.sourceHash);
|
|
@@ -389,11 +558,13 @@ description: Fixture
|
|
|
389
558
|
test('content:check warns when carousel SVG images have no intrinsic aspect ratio', async () => {
|
|
390
559
|
const { root, siteDir } = await createTempSite();
|
|
391
560
|
try {
|
|
392
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
393
|
-
|
|
394
|
-
description: Fixture
|
|
561
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
562
|
+
page:
|
|
563
|
+
description: Fixture
|
|
395
564
|
---
|
|
396
565
|
|
|
566
|
+
# SVG Carousel Fixture
|
|
567
|
+
|
|
397
568
|
## Intro {#intro}
|
|
398
569
|
|
|
399
570
|
\`\`\`norna-image-carousel
|
|
@@ -403,9 +574,9 @@ description: Fixture
|
|
|
403
574
|
alt: Second
|
|
404
575
|
\`\`\`
|
|
405
576
|
`);
|
|
406
|
-
await mkdir(path.join(siteDir, '
|
|
407
|
-
await writeFile(path.join(siteDir, '
|
|
408
|
-
await writeFile(path.join(siteDir, '
|
|
577
|
+
await mkdir(path.join(siteDir, 'pages', '000-home', 'images'), { recursive: true });
|
|
578
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'first.svg'), '<svg xmlns="http://www.w3.org/2000/svg"><rect width="100" height="100"/></svg>\n');
|
|
579
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'second.svg'), '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><rect width="100" height="100"/></svg>\n');
|
|
409
580
|
|
|
410
581
|
const { stdout } = await runContentScript(siteDir, ['--check']);
|
|
411
582
|
assert.match(stdout, /Content check completed with warnings\./);
|
|
@@ -418,11 +589,13 @@ description: Fixture
|
|
|
418
589
|
test('norna-card-list images are managed image references', async () => {
|
|
419
590
|
const { root, siteDir } = await createTempSite({ underRepoCache: true });
|
|
420
591
|
try {
|
|
421
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
422
|
-
|
|
423
|
-
description: Fixture
|
|
592
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
593
|
+
page:
|
|
594
|
+
description: Fixture
|
|
424
595
|
---
|
|
425
596
|
|
|
597
|
+
# Card List Fixture
|
|
598
|
+
|
|
426
599
|
## Help {#help}
|
|
427
600
|
|
|
428
601
|
\`\`\`norna-card-list
|
|
@@ -440,15 +613,15 @@ width: narrow
|
|
|
440
613
|
text: Support the shelter.
|
|
441
614
|
\`\`\`
|
|
442
615
|
`);
|
|
443
|
-
await mkdir(path.join(siteDir, '
|
|
444
|
-
await writeFile(path.join(siteDir, '
|
|
616
|
+
await mkdir(path.join(siteDir, 'pages', '000-home', 'images'), { recursive: true });
|
|
617
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'adopt.svg'), '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 100"><rect width="160" height="100"/></svg>\n');
|
|
445
618
|
|
|
446
619
|
const { stdout } = await runContentScript(siteDir, ['--check']);
|
|
447
620
|
assert.match(stdout, /Content check passed\./);
|
|
448
621
|
|
|
449
622
|
await runNorna(['--site-dir', siteDir, 'images']);
|
|
450
623
|
const manifest = JSON.parse(await readFile(path.join(siteDir, '.norna', 'generated-images.json'), 'utf8'));
|
|
451
|
-
assert.equal(manifest['images/
|
|
624
|
+
assert.equal(manifest['pages/000-home/images/adopt.svg'].kind, 'static');
|
|
452
625
|
|
|
453
626
|
await runNorna(['--site-dir', siteDir, 'build']);
|
|
454
627
|
const html = await readFile(path.join(root, 'dist', 'index.html'), 'utf8');
|
|
@@ -460,7 +633,7 @@ width: narrow
|
|
|
460
633
|
assert.match(html, /class="card-list-badge">Recommended<\/p>/);
|
|
461
634
|
assert.match(html, /Give a dog a new home\./);
|
|
462
635
|
assert.match(html, /href="\/adopt\/"/);
|
|
463
|
-
assert.match(html, /src="\/images\/original\/images\/
|
|
636
|
+
assert.match(html, /src="\/images\/original\/pages\/000-home\/images\/adopt-[a-f0-9]{8}\.svg"/);
|
|
464
637
|
} finally {
|
|
465
638
|
await rm(root, { recursive: true, force: true });
|
|
466
639
|
}
|
|
@@ -469,11 +642,13 @@ width: narrow
|
|
|
469
642
|
test('content:check fails when a norna-card-list image file is missing', async () => {
|
|
470
643
|
const { root, siteDir } = await createTempSite();
|
|
471
644
|
try {
|
|
472
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
473
|
-
|
|
474
|
-
description: Fixture
|
|
645
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
646
|
+
page:
|
|
647
|
+
description: Fixture
|
|
475
648
|
---
|
|
476
649
|
|
|
650
|
+
# Missing Card Image
|
|
651
|
+
|
|
477
652
|
## Help {#help}
|
|
478
653
|
|
|
479
654
|
\`\`\`norna-card-list
|
|
@@ -485,7 +660,7 @@ description: Fixture
|
|
|
485
660
|
await assert.rejects(
|
|
486
661
|
() => runContentScript(siteDir, ['--check']),
|
|
487
662
|
(error) => {
|
|
488
|
-
assert.match(error.output, /Image "missing\.svg" does not exist at .*site\/images\/
|
|
663
|
+
assert.match(error.output, /Image "missing\.svg" does not exist at .*site\/pages\/000-home\/images\/missing\.svg or anywhere under any page image root\./);
|
|
489
664
|
return true;
|
|
490
665
|
},
|
|
491
666
|
);
|
|
@@ -497,11 +672,13 @@ description: Fixture
|
|
|
497
672
|
test('content:check fails for malformed norna-card-list blocks', async () => {
|
|
498
673
|
const { root, siteDir } = await createTempSite();
|
|
499
674
|
try {
|
|
500
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
501
|
-
|
|
502
|
-
description: Fixture
|
|
675
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
676
|
+
page:
|
|
677
|
+
description: Fixture
|
|
503
678
|
---
|
|
504
679
|
|
|
680
|
+
# Malformed Card List
|
|
681
|
+
|
|
505
682
|
## Help {#help}
|
|
506
683
|
|
|
507
684
|
\`\`\`norna-card-list
|
|
@@ -524,11 +701,13 @@ description: Fixture
|
|
|
524
701
|
test('content:check fails for invalid norna-card-list options', async () => {
|
|
525
702
|
const { root, siteDir } = await createTempSite();
|
|
526
703
|
try {
|
|
527
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
528
|
-
|
|
529
|
-
description: Fixture
|
|
704
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
705
|
+
page:
|
|
706
|
+
description: Fixture
|
|
530
707
|
---
|
|
531
708
|
|
|
709
|
+
# Invalid Card List Options
|
|
710
|
+
|
|
532
711
|
## Help {#help}
|
|
533
712
|
|
|
534
713
|
\`\`\`norna-card-list
|
|
@@ -557,11 +736,13 @@ width: full
|
|
|
557
736
|
test('content:check fails for invalid norna-card-list width', async () => {
|
|
558
737
|
const { root, siteDir } = await createTempSite();
|
|
559
738
|
try {
|
|
560
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
561
|
-
|
|
562
|
-
description: Fixture
|
|
739
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
740
|
+
page:
|
|
741
|
+
description: Fixture
|
|
563
742
|
---
|
|
564
743
|
|
|
744
|
+
# Invalid Card List Width
|
|
745
|
+
|
|
565
746
|
## Help {#help}
|
|
566
747
|
|
|
567
748
|
\`\`\`norna-card-list
|
|
@@ -587,11 +768,13 @@ width: full
|
|
|
587
768
|
test('content:check fails for invalid norna-card-list size', async () => {
|
|
588
769
|
const { root, siteDir } = await createTempSite();
|
|
589
770
|
try {
|
|
590
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
591
|
-
|
|
592
|
-
description: Fixture
|
|
771
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
772
|
+
page:
|
|
773
|
+
description: Fixture
|
|
593
774
|
---
|
|
594
775
|
|
|
776
|
+
# Invalid Card List Size
|
|
777
|
+
|
|
595
778
|
## Help {#help}
|
|
596
779
|
|
|
597
780
|
\`\`\`norna-card-list
|
|
@@ -617,11 +800,13 @@ size: huge
|
|
|
617
800
|
test('content:sync moves Norna-managed images inside the same page image root', async () => {
|
|
618
801
|
const { root, siteDir } = await createTempSite();
|
|
619
802
|
try {
|
|
620
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
621
|
-
|
|
622
|
-
description: Fixture
|
|
803
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
804
|
+
page:
|
|
805
|
+
description: Fixture
|
|
623
806
|
---
|
|
624
807
|
|
|
808
|
+
# Sync Fixture
|
|
809
|
+
|
|
625
810
|
## Work {#work}
|
|
626
811
|
|
|
627
812
|
\`\`\`norna-image-stack
|
|
@@ -629,12 +814,12 @@ description: Fixture
|
|
|
629
814
|
alt: Moved image
|
|
630
815
|
\`\`\`
|
|
631
816
|
`);
|
|
632
|
-
await mkdir(path.join(siteDir, 'images', 'old'), { recursive: true });
|
|
633
|
-
await writeFile(path.join(siteDir, 'images', 'old', 'moved.jpg'), 'fixture image');
|
|
817
|
+
await mkdir(path.join(siteDir, 'pages', '000-home', 'images', 'old'), { recursive: true });
|
|
818
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'old', 'moved.jpg'), 'fixture image');
|
|
634
819
|
|
|
635
820
|
await runContentScript(siteDir, ['--write', '--yes']);
|
|
636
821
|
|
|
637
|
-
const moved = await readFile(path.join(siteDir, '
|
|
822
|
+
const moved = await readFile(path.join(siteDir, 'pages', '000-home', 'images', 'moved.jpg'), 'utf8');
|
|
638
823
|
assert.equal(moved, 'fixture image');
|
|
639
824
|
} finally {
|
|
640
825
|
await rm(root, { recursive: true, force: true });
|
|
@@ -644,11 +829,13 @@ description: Fixture
|
|
|
644
829
|
test('content:sync moves managed SVG images inside the same page image root', async () => {
|
|
645
830
|
const { root, siteDir } = await createTempSite();
|
|
646
831
|
try {
|
|
647
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
648
|
-
|
|
649
|
-
description: Fixture
|
|
832
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
833
|
+
page:
|
|
834
|
+
description: Fixture
|
|
650
835
|
---
|
|
651
836
|
|
|
837
|
+
# SVG Sync Fixture
|
|
838
|
+
|
|
652
839
|
## Work {#work}
|
|
653
840
|
|
|
654
841
|
\`\`\`norna-image-stack
|
|
@@ -656,166 +843,184 @@ description: Fixture
|
|
|
656
843
|
alt: Moved diagram
|
|
657
844
|
\`\`\`
|
|
658
845
|
`);
|
|
659
|
-
await mkdir(path.join(siteDir, 'images', 'old'), { recursive: true });
|
|
660
|
-
await writeFile(path.join(siteDir, 'images', 'old', 'moved.svg'), '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"></svg>\n');
|
|
846
|
+
await mkdir(path.join(siteDir, 'pages', '000-home', 'images', 'old'), { recursive: true });
|
|
847
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'old', 'moved.svg'), '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 10 10"></svg>\n');
|
|
661
848
|
|
|
662
849
|
await runContentScript(siteDir, ['--write', '--yes']);
|
|
663
850
|
|
|
664
|
-
const moved = await readFile(path.join(siteDir, '
|
|
851
|
+
const moved = await readFile(path.join(siteDir, 'pages', '000-home', 'images', 'moved.svg'), 'utf8');
|
|
665
852
|
assert.match(moved, /viewBox="0 0 10 10"/);
|
|
666
853
|
} finally {
|
|
667
854
|
await rm(root, { recursive: true, force: true });
|
|
668
855
|
}
|
|
669
856
|
});
|
|
670
857
|
|
|
671
|
-
test('content:sync moves Norna-managed images across
|
|
858
|
+
test('content:sync moves Norna-managed images across page image roots when git is clean', async () => {
|
|
672
859
|
const { root, siteDir } = await createTempSite();
|
|
673
860
|
try {
|
|
674
|
-
const
|
|
675
|
-
const
|
|
676
|
-
await mkdir(path.join(
|
|
677
|
-
await mkdir(
|
|
678
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
679
|
-
|
|
680
|
-
description: Fixture
|
|
861
|
+
const sourcePageDir = path.join(siteDir, 'pages', '010-source');
|
|
862
|
+
const targetPageDir = path.join(siteDir, 'pages', '020-target');
|
|
863
|
+
await mkdir(path.join(sourcePageDir, 'images'), { recursive: true });
|
|
864
|
+
await mkdir(targetPageDir, { recursive: true });
|
|
865
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
866
|
+
page:
|
|
867
|
+
description: Fixture
|
|
681
868
|
---
|
|
682
869
|
|
|
870
|
+
# Cross Page Sync
|
|
871
|
+
|
|
683
872
|
## Home {#home}
|
|
684
873
|
|
|
685
874
|
Text.
|
|
686
875
|
`);
|
|
687
|
-
await writeFile(path.join(
|
|
688
|
-
|
|
689
|
-
description: Fixture
|
|
876
|
+
await writeFile(path.join(sourcePageDir, 'content.md'), `---
|
|
877
|
+
page:
|
|
878
|
+
description: Fixture
|
|
690
879
|
---
|
|
691
880
|
|
|
881
|
+
# Source
|
|
882
|
+
|
|
692
883
|
## Old {#old}
|
|
693
884
|
|
|
694
885
|
Text.
|
|
695
886
|
`);
|
|
696
|
-
await writeFile(path.join(
|
|
697
|
-
|
|
698
|
-
description: Fixture
|
|
887
|
+
await writeFile(path.join(targetPageDir, 'content.md'), `---
|
|
888
|
+
page:
|
|
889
|
+
description: Fixture
|
|
699
890
|
---
|
|
700
891
|
|
|
892
|
+
# Target
|
|
893
|
+
|
|
701
894
|
## Work {#work}
|
|
702
895
|
|
|
703
896
|
\`\`\`norna-image-stack
|
|
704
897
|
- image: moved.jpg
|
|
705
898
|
\`\`\`
|
|
706
899
|
`);
|
|
707
|
-
await writeFile(path.join(
|
|
900
|
+
await writeFile(path.join(sourcePageDir, 'images', 'moved.jpg'), 'page image');
|
|
708
901
|
await initCleanGitWorktree(root);
|
|
709
902
|
|
|
710
903
|
await runContentScript(siteDir, ['--write', '--yes']);
|
|
711
904
|
|
|
712
|
-
assert.equal(await fileExists(path.join(
|
|
713
|
-
assert.equal(await fileExists(path.join(
|
|
905
|
+
assert.equal(await fileExists(path.join(targetPageDir, 'images', 'moved.jpg')), true);
|
|
906
|
+
assert.equal(await fileExists(path.join(sourcePageDir, 'images', 'moved.jpg')), false);
|
|
714
907
|
} finally {
|
|
715
908
|
await rm(root, { recursive: true, force: true });
|
|
716
909
|
}
|
|
717
910
|
});
|
|
718
911
|
|
|
719
|
-
test('content:sync refuses cross-
|
|
912
|
+
test('content:sync refuses cross-page image moves when git status is dirty', async () => {
|
|
720
913
|
const { root, siteDir } = await createTempSite();
|
|
721
914
|
try {
|
|
722
|
-
const
|
|
723
|
-
const
|
|
724
|
-
await mkdir(path.join(
|
|
725
|
-
await mkdir(
|
|
726
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
727
|
-
|
|
728
|
-
description: Fixture
|
|
915
|
+
const sourcePageDir = path.join(siteDir, 'pages', '010-source');
|
|
916
|
+
const targetPageDir = path.join(siteDir, 'pages', '020-target');
|
|
917
|
+
await mkdir(path.join(sourcePageDir, 'images'), { recursive: true });
|
|
918
|
+
await mkdir(targetPageDir, { recursive: true });
|
|
919
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
920
|
+
page:
|
|
921
|
+
description: Fixture
|
|
729
922
|
---
|
|
730
923
|
|
|
924
|
+
# Dirty Cross Page Sync
|
|
925
|
+
|
|
731
926
|
## Home {#home}
|
|
732
927
|
|
|
733
928
|
Text.
|
|
734
929
|
`);
|
|
735
|
-
await writeFile(path.join(
|
|
736
|
-
|
|
737
|
-
description: Fixture
|
|
930
|
+
await writeFile(path.join(sourcePageDir, 'content.md'), `---
|
|
931
|
+
page:
|
|
932
|
+
description: Fixture
|
|
738
933
|
---
|
|
739
934
|
|
|
935
|
+
# Source
|
|
936
|
+
|
|
740
937
|
## Old {#old}
|
|
741
938
|
|
|
742
939
|
Text.
|
|
743
940
|
`);
|
|
744
|
-
await writeFile(path.join(
|
|
745
|
-
|
|
746
|
-
description: Fixture
|
|
941
|
+
await writeFile(path.join(targetPageDir, 'content.md'), `---
|
|
942
|
+
page:
|
|
943
|
+
description: Fixture
|
|
747
944
|
---
|
|
748
945
|
|
|
946
|
+
# Target
|
|
947
|
+
|
|
749
948
|
## Work {#work}
|
|
750
949
|
|
|
751
950
|
\`\`\`norna-image-stack
|
|
752
951
|
- image: moved.jpg
|
|
753
952
|
\`\`\`
|
|
754
953
|
`);
|
|
755
|
-
await writeFile(path.join(
|
|
954
|
+
await writeFile(path.join(sourcePageDir, 'images', 'moved.jpg'), 'page image');
|
|
756
955
|
await initCleanGitWorktree(root);
|
|
757
956
|
await writeFile(path.join(root, 'dirty.txt'), 'dirty');
|
|
758
957
|
|
|
759
958
|
await assert.rejects(
|
|
760
959
|
() => runContentScript(siteDir, ['--write', '--yes']),
|
|
761
960
|
(error) => {
|
|
762
|
-
assert.match(error.output, /Cross-
|
|
961
|
+
assert.match(error.output, /Cross-page content sync requires a clean git working tree before moving files between page image roots\./);
|
|
763
962
|
assert.match(error.output, /dirty\.txt/);
|
|
764
963
|
return true;
|
|
765
964
|
},
|
|
766
965
|
);
|
|
767
966
|
|
|
768
|
-
assert.equal(await fileExists(path.join(
|
|
769
|
-
assert.equal(await fileExists(path.join(
|
|
967
|
+
assert.equal(await fileExists(path.join(targetPageDir, 'images', 'moved.jpg')), false);
|
|
968
|
+
assert.equal(await fileExists(path.join(sourcePageDir, 'images', 'moved.jpg')), true);
|
|
770
969
|
} finally {
|
|
771
970
|
await rm(root, { recursive: true, force: true });
|
|
772
971
|
}
|
|
773
972
|
});
|
|
774
973
|
|
|
775
|
-
test('content:check reports cross-
|
|
974
|
+
test('content:check reports cross-page image moves without requiring clean git status', async () => {
|
|
776
975
|
const { root, siteDir } = await createTempSite();
|
|
777
976
|
try {
|
|
778
|
-
const
|
|
779
|
-
const
|
|
780
|
-
await mkdir(path.join(
|
|
781
|
-
await mkdir(
|
|
782
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
783
|
-
|
|
784
|
-
description: Fixture
|
|
977
|
+
const sourcePageDir = path.join(siteDir, 'pages', '010-source');
|
|
978
|
+
const targetPageDir = path.join(siteDir, 'pages', '020-target');
|
|
979
|
+
await mkdir(path.join(sourcePageDir, 'images'), { recursive: true });
|
|
980
|
+
await mkdir(targetPageDir, { recursive: true });
|
|
981
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
982
|
+
page:
|
|
983
|
+
description: Fixture
|
|
785
984
|
---
|
|
786
985
|
|
|
986
|
+
# Cross Page Check
|
|
987
|
+
|
|
787
988
|
## Home {#home}
|
|
788
989
|
|
|
789
990
|
Text.
|
|
790
991
|
`);
|
|
791
|
-
await writeFile(path.join(
|
|
792
|
-
|
|
793
|
-
description: Fixture
|
|
992
|
+
await writeFile(path.join(sourcePageDir, 'content.md'), `---
|
|
993
|
+
page:
|
|
994
|
+
description: Fixture
|
|
794
995
|
---
|
|
795
996
|
|
|
997
|
+
# Source
|
|
998
|
+
|
|
796
999
|
## Old {#old}
|
|
797
1000
|
|
|
798
1001
|
Text.
|
|
799
1002
|
`);
|
|
800
|
-
await writeFile(path.join(
|
|
801
|
-
|
|
802
|
-
description: Fixture
|
|
1003
|
+
await writeFile(path.join(targetPageDir, 'content.md'), `---
|
|
1004
|
+
page:
|
|
1005
|
+
description: Fixture
|
|
803
1006
|
---
|
|
804
1007
|
|
|
1008
|
+
# Target
|
|
1009
|
+
|
|
805
1010
|
## Work {#work}
|
|
806
1011
|
|
|
807
1012
|
\`\`\`norna-image-stack
|
|
808
1013
|
- image: moved.jpg
|
|
809
1014
|
\`\`\`
|
|
810
1015
|
`);
|
|
811
|
-
await writeFile(path.join(
|
|
1016
|
+
await writeFile(path.join(sourcePageDir, 'images', 'moved.jpg'), 'page image');
|
|
812
1017
|
await writeFile(path.join(root, 'dirty.txt'), 'dirty');
|
|
813
1018
|
|
|
814
1019
|
await assert.rejects(
|
|
815
1020
|
() => runContentScript(siteDir, ['--check']),
|
|
816
1021
|
(error) => {
|
|
817
|
-
assert.match(error.output, /Image "moved\.jpg" is used here but is located in .*site\/
|
|
818
|
-
assert.match(error.output, /Run norna content:sync to move it from .*site\/
|
|
1022
|
+
assert.match(error.output, /Image "moved\.jpg" is used here but is located in .*site\/pages\/010-source\/images\/moved\.jpg\./);
|
|
1023
|
+
assert.match(error.output, /Run norna content:sync to move it from .*site\/pages\/010-source to .*site\/pages\/020-target\./);
|
|
819
1024
|
assert.doesNotMatch(error.output, /requires a clean git working tree before moving files/);
|
|
820
1025
|
return true;
|
|
821
1026
|
},
|
|
@@ -825,56 +1030,63 @@ description: Fixture
|
|
|
825
1030
|
}
|
|
826
1031
|
});
|
|
827
1032
|
|
|
828
|
-
test('content:sync refuses to move a cross-
|
|
1033
|
+
test('content:sync refuses to move a cross-page image still referenced by its current section', async () => {
|
|
829
1034
|
const { root, siteDir } = await createTempSite();
|
|
830
1035
|
try {
|
|
831
|
-
const
|
|
832
|
-
const
|
|
833
|
-
await mkdir(path.join(
|
|
834
|
-
await mkdir(
|
|
835
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
836
|
-
|
|
837
|
-
description: Fixture
|
|
1036
|
+
const sourcePageDir = path.join(siteDir, 'pages', '010-source');
|
|
1037
|
+
const targetPageDir = path.join(siteDir, 'pages', '020-target');
|
|
1038
|
+
await mkdir(path.join(sourcePageDir, 'images'), { recursive: true });
|
|
1039
|
+
await mkdir(targetPageDir, { recursive: true });
|
|
1040
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
1041
|
+
page:
|
|
1042
|
+
description: Fixture
|
|
838
1043
|
---
|
|
839
1044
|
|
|
1045
|
+
# Cross Page Copy
|
|
1046
|
+
|
|
840
1047
|
## Home {#home}
|
|
841
1048
|
|
|
842
1049
|
Text.
|
|
843
1050
|
`);
|
|
844
|
-
await writeFile(path.join(
|
|
845
|
-
|
|
846
|
-
description: Fixture
|
|
1051
|
+
await writeFile(path.join(sourcePageDir, 'content.md'), `---
|
|
1052
|
+
page:
|
|
1053
|
+
description: Fixture
|
|
847
1054
|
---
|
|
848
1055
|
|
|
1056
|
+
# Source
|
|
1057
|
+
|
|
849
1058
|
## Old {#old}
|
|
850
1059
|
|
|
851
1060
|
\`\`\`norna-image-stack
|
|
852
1061
|
- image: shared.jpg
|
|
853
1062
|
\`\`\`
|
|
854
1063
|
`);
|
|
855
|
-
await writeFile(path.join(
|
|
856
|
-
|
|
857
|
-
description: Fixture
|
|
1064
|
+
await writeFile(path.join(targetPageDir, 'content.md'), `---
|
|
1065
|
+
page:
|
|
1066
|
+
description: Fixture
|
|
858
1067
|
---
|
|
859
1068
|
|
|
1069
|
+
# Target
|
|
1070
|
+
|
|
860
1071
|
## Work {#work}
|
|
861
1072
|
|
|
862
1073
|
\`\`\`norna-image-stack
|
|
863
1074
|
- image: shared.jpg
|
|
864
1075
|
\`\`\`
|
|
865
1076
|
`);
|
|
866
|
-
await writeFile(path.join(
|
|
1077
|
+
await writeFile(path.join(sourcePageDir, 'images', 'shared.jpg'), 'page image');
|
|
1078
|
+
await initCleanGitWorktree(root);
|
|
867
1079
|
|
|
868
1080
|
await assert.rejects(
|
|
869
1081
|
() => runContentScript(siteDir, ['--write', '--yes']),
|
|
870
1082
|
(error) => {
|
|
871
|
-
assert.match(error.output, /Cannot relocate "shared\.jpg" from .*site\/
|
|
1083
|
+
assert.match(error.output, /Cannot relocate "shared\.jpg" from .*site\/pages\/010-source\/images\/shared\.jpg because it is still referenced from .*site\/pages\/010-source\/content\.md \[old\]\./);
|
|
872
1084
|
return true;
|
|
873
1085
|
},
|
|
874
1086
|
);
|
|
875
1087
|
|
|
876
|
-
assert.equal(await fileExists(path.join(
|
|
877
|
-
assert.equal(await fileExists(path.join(
|
|
1088
|
+
assert.equal(await fileExists(path.join(targetPageDir, 'images', 'shared.jpg')), false);
|
|
1089
|
+
assert.equal(await fileExists(path.join(sourcePageDir, 'images', 'shared.jpg')), true);
|
|
878
1090
|
} finally {
|
|
879
1091
|
await rm(root, { recursive: true, force: true });
|
|
880
1092
|
}
|
|
@@ -883,11 +1095,13 @@ description: Fixture
|
|
|
883
1095
|
test('CLI content:sync refreshes generated images after moving a carousel image', async () => {
|
|
884
1096
|
const { root, siteDir } = await createTempSite();
|
|
885
1097
|
try {
|
|
886
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
887
|
-
|
|
888
|
-
description: Fixture
|
|
1098
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
1099
|
+
page:
|
|
1100
|
+
description: Fixture
|
|
889
1101
|
---
|
|
890
1102
|
|
|
1103
|
+
# Sync Fixture
|
|
1104
|
+
|
|
891
1105
|
## Work {#work}
|
|
892
1106
|
|
|
893
1107
|
\`\`\`norna-image-carousel
|
|
@@ -897,42 +1111,49 @@ description: Fixture
|
|
|
897
1111
|
alt: Slide two
|
|
898
1112
|
\`\`\`
|
|
899
1113
|
`);
|
|
900
|
-
await mkdir(path.join(siteDir, 'images', 'old'), { recursive: true });
|
|
901
|
-
await
|
|
902
|
-
await cp(path.join(fixtureSiteDir, '
|
|
903
|
-
await cp(path.join(fixtureSiteDir, 'images', 'intro', 'detail.jpg'), path.join(siteDir, 'images', 'work', 'slide-two.jpg'), { force: true });
|
|
1114
|
+
await mkdir(path.join(siteDir, 'pages', '000-home', 'images', 'old'), { recursive: true });
|
|
1115
|
+
await cp(path.join(fixtureSiteDir, 'pages', '000-home', 'images', 'hero.jpg'), path.join(siteDir, 'pages', '000-home', 'images', 'old', 'slide-one.jpg'));
|
|
1116
|
+
await cp(path.join(fixtureSiteDir, 'pages', '000-home', 'images', 'detail.jpg'), path.join(siteDir, 'pages', '000-home', 'images', 'slide-two.jpg'), { force: true });
|
|
904
1117
|
|
|
905
1118
|
await runNorna(['--site-dir', siteDir, 'content:sync', '--yes']);
|
|
906
1119
|
|
|
907
1120
|
const manifest = JSON.parse(await readFile(path.join(siteDir, '.norna', 'generated-images.json'), 'utf8'));
|
|
908
|
-
assert.equal(await fileExists(path.join(siteDir, '
|
|
909
|
-
assert.ok(manifest['images/
|
|
910
|
-
assert.ok(manifest['images/
|
|
1121
|
+
assert.equal(await fileExists(path.join(siteDir, 'pages', '000-home', 'images', 'slide-one.jpg')), true);
|
|
1122
|
+
assert.ok(manifest['pages/000-home/images/slide-one.jpg']);
|
|
1123
|
+
assert.ok(manifest['pages/000-home/images/slide-two.jpg']);
|
|
911
1124
|
} finally {
|
|
912
1125
|
await rm(root, { recursive: true, force: true });
|
|
913
1126
|
}
|
|
914
1127
|
});
|
|
915
1128
|
|
|
916
|
-
test('
|
|
917
|
-
const { root, siteDir } = await createTempSite();
|
|
1129
|
+
test('automatic H2 and H3 ids render while explicit ids remain stable', async () => {
|
|
1130
|
+
const { root, siteDir } = await createTempSite({ underRepoCache: true });
|
|
918
1131
|
try {
|
|
919
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
920
|
-
|
|
921
|
-
description: Fixture
|
|
1132
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
1133
|
+
page:
|
|
1134
|
+
description: Fixture
|
|
922
1135
|
---
|
|
923
1136
|
|
|
924
|
-
|
|
1137
|
+
# Heading IDs
|
|
1138
|
+
|
|
1139
|
+
## Café and **code**
|
|
1140
|
+
|
|
1141
|
+
### More details
|
|
1142
|
+
|
|
1143
|
+
Automatic identifiers.
|
|
925
1144
|
|
|
926
|
-
|
|
1145
|
+
## Renamed heading {#stable-id}
|
|
1146
|
+
|
|
1147
|
+
Explicit identifier.
|
|
927
1148
|
`);
|
|
928
1149
|
|
|
929
|
-
await
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
);
|
|
1150
|
+
await runContentScript(siteDir, ['--check']);
|
|
1151
|
+
await runNorna(['--site-dir', siteDir, 'build']);
|
|
1152
|
+
|
|
1153
|
+
const html = await readFile(path.join(root, 'dist', 'index.html'), 'utf8');
|
|
1154
|
+
assert.match(html, /<h2 id="cafe-and-code">Café and <strong>code<\/strong><\/h2>/);
|
|
1155
|
+
assert.match(html, /<h3 id="more-details">More details<\/h3>/);
|
|
1156
|
+
assert.match(html, /<h2 id="stable-id">Renamed heading<\/h2>/);
|
|
936
1157
|
} finally {
|
|
937
1158
|
await rm(root, { recursive: true, force: true });
|
|
938
1159
|
}
|
|
@@ -941,11 +1162,13 @@ This section has no explicit id.
|
|
|
941
1162
|
test('content:check fails for malformed Norna image blocks', async () => {
|
|
942
1163
|
const { root, siteDir } = await createTempSite();
|
|
943
1164
|
try {
|
|
944
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
945
|
-
|
|
946
|
-
description: Fixture
|
|
1165
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
1166
|
+
page:
|
|
1167
|
+
description: Fixture
|
|
947
1168
|
---
|
|
948
1169
|
|
|
1170
|
+
# Malformed Block
|
|
1171
|
+
|
|
949
1172
|
## Intro {#intro}
|
|
950
1173
|
|
|
951
1174
|
\`\`\`norna-image-stack
|
|
@@ -969,13 +1192,15 @@ image hero.jpg
|
|
|
969
1192
|
test('content:check fails for unknown Norna block names', async () => {
|
|
970
1193
|
const { root, siteDir } = await createTempSite();
|
|
971
1194
|
try {
|
|
972
|
-
await mkdir(path.join(siteDir, 'images', 'plain'), { recursive: true });
|
|
973
|
-
await writeFile(path.join(siteDir, 'images', 'plain', 'image.jpg'), 'fixture image');
|
|
974
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
975
|
-
|
|
976
|
-
description: Fixture
|
|
1195
|
+
await mkdir(path.join(siteDir, 'pages', '000-home', 'images', 'plain'), { recursive: true });
|
|
1196
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'plain', 'image.jpg'), 'fixture image');
|
|
1197
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
1198
|
+
page:
|
|
1199
|
+
description: Fixture
|
|
977
1200
|
---
|
|
978
1201
|
|
|
1202
|
+
# Unknown Block
|
|
1203
|
+
|
|
979
1204
|
## Plain {#plain}
|
|
980
1205
|
|
|
981
1206
|
\`\`\`norna-gallery-stack
|
|
@@ -1000,11 +1225,13 @@ description: Fixture
|
|
|
1000
1225
|
test('content:check rejects the removed norna-note block', async () => {
|
|
1001
1226
|
const { root, siteDir } = await createTempSite();
|
|
1002
1227
|
try {
|
|
1003
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1004
|
-
|
|
1005
|
-
description: Fixture
|
|
1228
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
1229
|
+
page:
|
|
1230
|
+
description: Fixture
|
|
1006
1231
|
---
|
|
1007
1232
|
|
|
1233
|
+
# Removed note block
|
|
1234
|
+
|
|
1008
1235
|
## Intro {#intro}
|
|
1009
1236
|
|
|
1010
1237
|
\`\`\`norna-note
|
|
@@ -1027,13 +1254,15 @@ This block is no longer supported.
|
|
|
1027
1254
|
test('content:check supports tilde fenced Norna blocks', async () => {
|
|
1028
1255
|
const { root, siteDir } = await createTempSite();
|
|
1029
1256
|
try {
|
|
1030
|
-
await mkdir(path.join(siteDir, '
|
|
1031
|
-
await writeFile(path.join(siteDir, '
|
|
1032
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1033
|
-
|
|
1034
|
-
description: Fixture
|
|
1257
|
+
await mkdir(path.join(siteDir, 'pages', '000-home', 'images'), { recursive: true });
|
|
1258
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'hero.jpg'), 'fixture image');
|
|
1259
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
1260
|
+
page:
|
|
1261
|
+
description: Fixture
|
|
1035
1262
|
---
|
|
1036
1263
|
|
|
1264
|
+
# Tilde Fence
|
|
1265
|
+
|
|
1037
1266
|
## Intro {#intro}
|
|
1038
1267
|
|
|
1039
1268
|
~~~norna-image-stack
|
|
@@ -1051,11 +1280,13 @@ description: Fixture
|
|
|
1051
1280
|
test('content:check ignores Norna blocks shown inside longer code fences', async () => {
|
|
1052
1281
|
const { root, siteDir } = await createTempSite();
|
|
1053
1282
|
try {
|
|
1054
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1055
|
-
|
|
1056
|
-
description: Fixture
|
|
1283
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
1284
|
+
page:
|
|
1285
|
+
description: Fixture
|
|
1057
1286
|
---
|
|
1058
1287
|
|
|
1288
|
+
# Code Example
|
|
1289
|
+
|
|
1059
1290
|
## Intro {#intro}
|
|
1060
1291
|
|
|
1061
1292
|
\`\`\`\`md
|
|
@@ -1075,11 +1306,13 @@ description: Fixture
|
|
|
1075
1306
|
test('content:check fails for likely Norna block names outside fences', async () => {
|
|
1076
1307
|
const { root, siteDir } = await createTempSite();
|
|
1077
1308
|
try {
|
|
1078
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1079
|
-
|
|
1080
|
-
description: Fixture
|
|
1309
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
1310
|
+
page:
|
|
1311
|
+
description: Fixture
|
|
1081
1312
|
---
|
|
1082
1313
|
|
|
1314
|
+
# Missing Fence
|
|
1315
|
+
|
|
1083
1316
|
## Intro {#intro}
|
|
1084
1317
|
|
|
1085
1318
|
norna-image-stack
|
|
@@ -1102,11 +1335,13 @@ norna-image-stack
|
|
|
1102
1335
|
test('content:check fails for short Norna block fences', async () => {
|
|
1103
1336
|
const { root, siteDir } = await createTempSite();
|
|
1104
1337
|
try {
|
|
1105
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1106
|
-
|
|
1107
|
-
description: Fixture
|
|
1338
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
1339
|
+
page:
|
|
1340
|
+
description: Fixture
|
|
1108
1341
|
---
|
|
1109
1342
|
|
|
1343
|
+
# Short Fence
|
|
1344
|
+
|
|
1110
1345
|
## Intro {#intro}
|
|
1111
1346
|
|
|
1112
1347
|
\`\`norna-image-stack
|
|
@@ -1130,11 +1365,13 @@ description: Fixture
|
|
|
1130
1365
|
test('content:check fails for unclosed Norna blocks', async () => {
|
|
1131
1366
|
const { root, siteDir } = await createTempSite();
|
|
1132
1367
|
try {
|
|
1133
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1134
|
-
|
|
1135
|
-
description: Fixture
|
|
1368
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
1369
|
+
page:
|
|
1370
|
+
description: Fixture
|
|
1136
1371
|
---
|
|
1137
1372
|
|
|
1373
|
+
# Unclosed Fence
|
|
1374
|
+
|
|
1138
1375
|
## Intro {#intro}
|
|
1139
1376
|
|
|
1140
1377
|
\`\`\`norna-image-stack
|
|
@@ -1158,11 +1395,13 @@ description: Fixture
|
|
|
1158
1395
|
test('content:check continues after malformed Norna blocks', async () => {
|
|
1159
1396
|
const { root, siteDir } = await createTempSite();
|
|
1160
1397
|
try {
|
|
1161
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1162
|
-
|
|
1163
|
-
description: Fixture
|
|
1398
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
1399
|
+
page:
|
|
1400
|
+
description: Fixture
|
|
1164
1401
|
---
|
|
1165
1402
|
|
|
1403
|
+
# Multiple Issues
|
|
1404
|
+
|
|
1166
1405
|
## Intro {#intro}
|
|
1167
1406
|
|
|
1168
1407
|
\`\`\`norna-image-stack
|
|
@@ -1187,8 +1426,8 @@ image broken.jpg
|
|
|
1187
1426
|
() => runContentScript(siteDir, ['--check']),
|
|
1188
1427
|
(error) => {
|
|
1189
1428
|
assert.match(error.output, /Invalid norna-image-stack entry "image broken\.jpg"\. Start each image with "- image: filename\.jpg"\./);
|
|
1190
|
-
assert.match(error.output, /Image "missing-intro\.jpg" does not exist at .*site\/images\/
|
|
1191
|
-
assert.match(error.output, /Image "missing-more\.jpg" does not exist at .*site\/images\/
|
|
1429
|
+
assert.match(error.output, /Image "missing-intro\.jpg" does not exist at .*site\/pages\/000-home\/images\/missing-intro\.jpg or anywhere under any page image root\./);
|
|
1430
|
+
assert.match(error.output, /Image "missing-more\.jpg" does not exist at .*site\/pages\/000-home\/images\/missing-more\.jpg or anywhere under any page image root\./);
|
|
1192
1431
|
return true;
|
|
1193
1432
|
},
|
|
1194
1433
|
);
|
|
@@ -1200,13 +1439,15 @@ image broken.jpg
|
|
|
1200
1439
|
test('content:check allows Norna images without alt text', async () => {
|
|
1201
1440
|
const { root, siteDir } = await createTempSite();
|
|
1202
1441
|
try {
|
|
1203
|
-
await mkdir(path.join(siteDir, '
|
|
1204
|
-
await writeFile(path.join(siteDir, '
|
|
1205
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1206
|
-
|
|
1207
|
-
description: Fixture
|
|
1442
|
+
await mkdir(path.join(siteDir, 'pages', '000-home', 'images'), { recursive: true });
|
|
1443
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'hero.jpg'), 'fixture image');
|
|
1444
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
1445
|
+
page:
|
|
1446
|
+
description: Fixture
|
|
1208
1447
|
---
|
|
1209
1448
|
|
|
1449
|
+
# Missing Alt
|
|
1450
|
+
|
|
1210
1451
|
## Intro {#intro}
|
|
1211
1452
|
|
|
1212
1453
|
\`\`\`norna-image-stack
|
|
@@ -1224,11 +1465,13 @@ description: Fixture
|
|
|
1224
1465
|
test('content:check reports single-image carousel and missing file together', async () => {
|
|
1225
1466
|
const { root, siteDir } = await createTempSite();
|
|
1226
1467
|
try {
|
|
1227
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1228
|
-
|
|
1229
|
-
description: Fixture
|
|
1468
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
1469
|
+
page:
|
|
1470
|
+
description: Fixture
|
|
1230
1471
|
---
|
|
1231
1472
|
|
|
1473
|
+
# Beginner Errors
|
|
1474
|
+
|
|
1232
1475
|
## Plain {#plain}
|
|
1233
1476
|
|
|
1234
1477
|
\`\`\`norna-image-carousel
|
|
@@ -1240,7 +1483,7 @@ description: Fixture
|
|
|
1240
1483
|
() => runContentScript(siteDir, ['--check']),
|
|
1241
1484
|
(error) => {
|
|
1242
1485
|
assert.match(error.output, /norna-image-carousel on line \d+ contains 1 image\. A carousel needs at least two images\./);
|
|
1243
|
-
assert.match(error.output, /Image "foo\.jpg" does not exist at .*site\/images\/
|
|
1486
|
+
assert.match(error.output, /Image "foo\.jpg" does not exist at .*site\/pages\/000-home\/images\/foo\.jpg or anywhere under any page image root\./);
|
|
1244
1487
|
return true;
|
|
1245
1488
|
},
|
|
1246
1489
|
);
|
|
@@ -1252,11 +1495,13 @@ description: Fixture
|
|
|
1252
1495
|
test('content:check fails when a Norna-managed image file is missing', async () => {
|
|
1253
1496
|
const { root, siteDir } = await createTempSite();
|
|
1254
1497
|
try {
|
|
1255
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1256
|
-
|
|
1257
|
-
description: Fixture
|
|
1498
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
1499
|
+
page:
|
|
1500
|
+
description: Fixture
|
|
1258
1501
|
---
|
|
1259
1502
|
|
|
1503
|
+
# Missing Image
|
|
1504
|
+
|
|
1260
1505
|
## Intro {#intro}
|
|
1261
1506
|
|
|
1262
1507
|
\`\`\`norna-image-stack
|
|
@@ -1268,7 +1513,7 @@ description: Fixture
|
|
|
1268
1513
|
await assert.rejects(
|
|
1269
1514
|
() => runContentScript(siteDir, ['--check']),
|
|
1270
1515
|
(error) => {
|
|
1271
|
-
assert.match(error.output, /Image "missing\.jpg" does not exist at .*site\/images\/
|
|
1516
|
+
assert.match(error.output, /Image "missing\.jpg" does not exist at .*site\/pages\/000-home\/images\/missing\.jpg or anywhere under any page image root\./);
|
|
1272
1517
|
return true;
|
|
1273
1518
|
},
|
|
1274
1519
|
);
|
|
@@ -1280,11 +1525,13 @@ description: Fixture
|
|
|
1280
1525
|
test('content:check fails when a page repeats a section id', async () => {
|
|
1281
1526
|
const { root, siteDir } = await createTempSite();
|
|
1282
1527
|
try {
|
|
1283
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1284
|
-
|
|
1285
|
-
description: Fixture
|
|
1528
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
1529
|
+
page:
|
|
1530
|
+
description: Fixture
|
|
1286
1531
|
---
|
|
1287
1532
|
|
|
1533
|
+
# Duplicate Section
|
|
1534
|
+
|
|
1288
1535
|
## Intro {#intro}
|
|
1289
1536
|
|
|
1290
1537
|
First.
|
|
@@ -1297,7 +1544,7 @@ Second.
|
|
|
1297
1544
|
await assert.rejects(
|
|
1298
1545
|
() => runContentScript(siteDir, ['--check']),
|
|
1299
1546
|
(error) => {
|
|
1300
|
-
assert.match(error.output, /
|
|
1547
|
+
assert.match(error.output, /Two headings resolve to id "intro"\./);
|
|
1301
1548
|
return true;
|
|
1302
1549
|
},
|
|
1303
1550
|
);
|
|
@@ -1306,16 +1553,51 @@ Second.
|
|
|
1306
1553
|
}
|
|
1307
1554
|
});
|
|
1308
1555
|
|
|
1309
|
-
test('content:check
|
|
1556
|
+
test('content:check reports collisions between automatic H2 and H3 ids', async () => {
|
|
1310
1557
|
const { root, siteDir } = await createTempSite();
|
|
1311
1558
|
try {
|
|
1312
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1313
|
-
|
|
1314
|
-
description: Fixture
|
|
1559
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
1560
|
+
page:
|
|
1561
|
+
description: Fixture
|
|
1562
|
+
---
|
|
1563
|
+
|
|
1564
|
+
# Automatic collision
|
|
1565
|
+
|
|
1566
|
+
## Förstå
|
|
1567
|
+
|
|
1568
|
+
Section text.
|
|
1569
|
+
|
|
1570
|
+
### Forsta
|
|
1571
|
+
|
|
1572
|
+
Topic text.
|
|
1573
|
+
`);
|
|
1574
|
+
|
|
1575
|
+
await assert.rejects(
|
|
1576
|
+
() => runContentScript(siteDir, ['--check']),
|
|
1577
|
+
(error) => {
|
|
1578
|
+
assert.match(error.output, /Two headings resolve to id "forsta"\./);
|
|
1579
|
+
assert.match(error.output, /The other heading is on line \d+\./);
|
|
1580
|
+
assert.match(error.output, /Add a unique explicit id to at least one heading/);
|
|
1581
|
+
return true;
|
|
1582
|
+
},
|
|
1583
|
+
);
|
|
1584
|
+
} finally {
|
|
1585
|
+
await rm(root, { recursive: true, force: true });
|
|
1586
|
+
}
|
|
1587
|
+
});
|
|
1588
|
+
|
|
1589
|
+
test('content:check rejects removed sections frontmatter', async () => {
|
|
1590
|
+
const { root, siteDir } = await createTempSite();
|
|
1591
|
+
try {
|
|
1592
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
1593
|
+
page:
|
|
1594
|
+
description: Fixture
|
|
1315
1595
|
sections:
|
|
1316
|
-
|
|
1596
|
+
intro: {}
|
|
1317
1597
|
---
|
|
1318
1598
|
|
|
1599
|
+
# Removed Section Metadata
|
|
1600
|
+
|
|
1319
1601
|
## Intro {#intro}
|
|
1320
1602
|
|
|
1321
1603
|
Text.
|
|
@@ -1324,7 +1606,7 @@ Text.
|
|
|
1324
1606
|
await assert.rejects(
|
|
1325
1607
|
() => runContentScript(siteDir, ['--check']),
|
|
1326
1608
|
(error) => {
|
|
1327
|
-
assert.match(error.output, /
|
|
1609
|
+
assert.match(error.output, /defines "sections" at the top level, but it is not a valid top-level content field\./);
|
|
1328
1610
|
return true;
|
|
1329
1611
|
},
|
|
1330
1612
|
);
|
|
@@ -1336,11 +1618,13 @@ Text.
|
|
|
1336
1618
|
test('content:sync refuses ambiguous image relocation', async () => {
|
|
1337
1619
|
const { root, siteDir } = await createTempSite();
|
|
1338
1620
|
try {
|
|
1339
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1340
|
-
|
|
1341
|
-
description: Fixture
|
|
1621
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
1622
|
+
page:
|
|
1623
|
+
description: Fixture
|
|
1342
1624
|
---
|
|
1343
1625
|
|
|
1626
|
+
# Ambiguous Move
|
|
1627
|
+
|
|
1344
1628
|
## Work {#work}
|
|
1345
1629
|
|
|
1346
1630
|
\`\`\`norna-image-stack
|
|
@@ -1348,37 +1632,39 @@ description: Fixture
|
|
|
1348
1632
|
alt: Shared
|
|
1349
1633
|
\`\`\`
|
|
1350
1634
|
`);
|
|
1351
|
-
await mkdir(path.join(siteDir, 'images', 'old-a'), { recursive: true });
|
|
1352
|
-
await mkdir(path.join(siteDir, 'images', 'old-b'), { recursive: true });
|
|
1353
|
-
await writeFile(path.join(siteDir, 'images', 'old-a', 'shared.jpg'), 'a');
|
|
1354
|
-
await writeFile(path.join(siteDir, 'images', 'old-b', 'shared.jpg'), 'b');
|
|
1635
|
+
await mkdir(path.join(siteDir, 'pages', '000-home', 'images', 'old-a'), { recursive: true });
|
|
1636
|
+
await mkdir(path.join(siteDir, 'pages', '000-home', 'images', 'old-b'), { recursive: true });
|
|
1637
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'old-a', 'shared.jpg'), 'a');
|
|
1638
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'old-b', 'shared.jpg'), 'b');
|
|
1355
1639
|
|
|
1356
1640
|
await assert.rejects(
|
|
1357
1641
|
() => runContentScript(siteDir, ['--write', '--yes']),
|
|
1358
1642
|
(error) => {
|
|
1359
1643
|
assert.match(error.output, /Cannot relocate "shared\.jpg"\. Multiple files with this filename were found:/);
|
|
1360
|
-
assert.match(error.output, /site\/images\/old-a\/shared\.jpg/);
|
|
1361
|
-
assert.match(error.output, /site\/images\/old-b\/shared\.jpg/);
|
|
1644
|
+
assert.match(error.output, /site\/pages\/000-home\/images\/old-a\/shared\.jpg/);
|
|
1645
|
+
assert.match(error.output, /site\/pages\/000-home\/images\/old-b\/shared\.jpg/);
|
|
1362
1646
|
return true;
|
|
1363
1647
|
},
|
|
1364
1648
|
);
|
|
1365
1649
|
|
|
1366
|
-
assert.equal(await fileExists(path.join(siteDir, '
|
|
1367
|
-
assert.equal(await fileExists(path.join(siteDir, 'images', 'old-a', 'shared.jpg')), true);
|
|
1368
|
-
assert.equal(await fileExists(path.join(siteDir, 'images', 'old-b', 'shared.jpg')), true);
|
|
1650
|
+
assert.equal(await fileExists(path.join(siteDir, 'pages', '000-home', 'images', 'shared.jpg')), false);
|
|
1651
|
+
assert.equal(await fileExists(path.join(siteDir, 'pages', '000-home', 'images', 'old-a', 'shared.jpg')), true);
|
|
1652
|
+
assert.equal(await fileExists(path.join(siteDir, 'pages', '000-home', 'images', 'old-b', 'shared.jpg')), true);
|
|
1369
1653
|
} finally {
|
|
1370
1654
|
await rm(root, { recursive: true, force: true });
|
|
1371
1655
|
}
|
|
1372
1656
|
});
|
|
1373
1657
|
|
|
1374
|
-
test('content:sync
|
|
1658
|
+
test('content:sync moves one page-local image once when multiple sections reference it', async () => {
|
|
1375
1659
|
const { root, siteDir } = await createTempSite();
|
|
1376
1660
|
try {
|
|
1377
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
1378
|
-
|
|
1379
|
-
description: Fixture
|
|
1661
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
1662
|
+
page:
|
|
1663
|
+
description: Fixture
|
|
1380
1664
|
---
|
|
1381
1665
|
|
|
1666
|
+
# Copied Image Reference
|
|
1667
|
+
|
|
1382
1668
|
## Intro {#intro}
|
|
1383
1669
|
|
|
1384
1670
|
\`\`\`norna-image-stack
|
|
@@ -1393,19 +1679,14 @@ description: Fixture
|
|
|
1393
1679
|
alt: Shared copy
|
|
1394
1680
|
\`\`\`
|
|
1395
1681
|
`);
|
|
1396
|
-
await mkdir(path.join(siteDir, 'images', 'intro'), { recursive: true });
|
|
1397
|
-
await writeFile(path.join(siteDir, 'images', 'intro', 'shared.jpg'), 'shared image');
|
|
1682
|
+
await mkdir(path.join(siteDir, 'pages', '000-home', 'images', 'intro'), { recursive: true });
|
|
1683
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'images', 'intro', 'shared.jpg'), 'shared image');
|
|
1398
1684
|
|
|
1399
|
-
await
|
|
1400
|
-
() => runContentScript(siteDir, ['--write', '--yes']),
|
|
1401
|
-
(error) => {
|
|
1402
|
-
assert.match(error.output, /Cannot relocate "shared\.jpg" from .*site\/images\/intro\/shared\.jpg because it is still referenced from .*site\/content\.md \[intro\]\./);
|
|
1403
|
-
return true;
|
|
1404
|
-
},
|
|
1405
|
-
);
|
|
1685
|
+
await runContentScript(siteDir, ['--write', '--yes']);
|
|
1406
1686
|
|
|
1407
|
-
assert.equal(await fileExists(path.join(siteDir, 'images', 'intro', 'shared.jpg')),
|
|
1408
|
-
assert.equal(await fileExists(path.join(siteDir, '
|
|
1687
|
+
assert.equal(await fileExists(path.join(siteDir, 'pages', '000-home', 'images', 'intro', 'shared.jpg')), false);
|
|
1688
|
+
assert.equal(await fileExists(path.join(siteDir, 'pages', '000-home', 'images', 'shared.jpg')), true);
|
|
1689
|
+
await runContentScript(siteDir, ['--check']);
|
|
1409
1690
|
} finally {
|
|
1410
1691
|
await rm(root, { recursive: true, force: true });
|
|
1411
1692
|
}
|