@janga/norna 0.7.17 → 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 +15 -6
- package/bin/norna-cli.mjs +1 -1
- package/docs/README.md +11 -9
- package/docs/configuration.md +25 -2
- package/docs/content.md +21 -12
- package/docs/design/navigation-and-theme-plan.md +90 -19
- package/docs/design/site-examples-structure.md +7 -3
- package/docs/engine-development.md +19 -22
- package/docs/getting-started.md +3 -3
- package/docs/images-and-metadata.md +6 -4
- package/docs/pages.md +120 -64
- package/docs/public-files.md +1 -1
- package/docs/site-files.md +36 -29
- package/docs/sitewide-content.md +1 -1
- package/docs/theme.md +107 -141
- package/docs/typography.md +15 -27
- package/examples/README.md +8 -2
- package/examples/complete-sites/dog-shelter-multi-page/site/.norna/generated-images.json +5 -5
- package/examples/complete-sites/dog-shelter-single-page/site/.norna/generated-images.json +10 -10
- package/examples/feature-demos/media-and-surfaces/.astro/collections/theme.schema.json +35 -43
- package/examples/feature-demos/media-and-surfaces/.astro/data-store.json +1 -1
- package/examples/feature-demos/media-and-surfaces/site/pages/020-surfaces/content.md +2 -1
- package/examples/feature-demos/media-and-surfaces/site/pages/020-surfaces/theme.yaml +2 -3
- 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/pages/020-documentation → theme-preset-documentation/site/pages/000-home}/content.md +1 -1
- 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/pages/010-portfolio → theme-preset-portfolio/site/pages/000-home}/content.md +1 -1
- 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/pages/030-project → theme-preset-project/site/pages/000-home}/content.md +1 -1
- 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/pages/040-statement → theme-preset-statement/site/pages/000-home}/content.md +1 -1
- 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/theme.yaml +1 -8
- 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/config.yaml +2 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/pages/010-macos/content.md +4 -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/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 -3
- package/fixtures/nested-pages/site/theme.yaml +1 -3
- package/package.json +2 -1
- package/schemas/config.schema.json +45 -4
- package/schemas/content-frontmatter.schema.json +5 -5
- package/schemas/manifest.json +3 -2
- package/schemas/page-theme.schema.json +192 -0
- package/schemas/sitewide-content.schema.json +17 -17
- package/schemas/theme.schema.json +188 -216
- package/scripts/check-config.mjs +2 -1
- package/scripts/deploy-site.mjs +1 -1
- package/scripts/generate-images.mjs +7 -14
- package/scripts/generate-schemas.mjs +4 -1
- package/scripts/lib/editor-language-service.mjs +0 -14
- package/scripts/lib/example-sites.mjs +15 -3
- package/scripts/lib/page-model.mjs +26 -7
- package/scripts/lib/presentation.mjs +25 -17
- package/scripts/lib/project-config.mjs +26 -16
- package/scripts/lib/schema-definitions.mjs +42 -14
- package/scripts/lib/schema-editor-metadata.mjs +70 -26
- package/scripts/lib/schema-value-definitions.mjs +14 -0
- package/scripts/lib/site-content.mjs +50 -25
- package/scripts/lib/site-conventions.mjs +1 -0
- package/scripts/lib/site-paths.mjs +9 -3
- package/scripts/lib/theme-config.mjs +3 -2
- package/scripts/lib/theme-presets.mjs +54 -21
- package/scripts/show-typography.mjs +1 -33
- package/scripts/sync-content-sections.mjs +1 -3
- package/scripts/test-banner-visibility.mjs +2 -2
- package/scripts/test-client-javascript.mjs +11 -4
- package/scripts/test-content-check.mjs +25 -25
- package/scripts/test-content-model-v2.mjs +165 -101
- package/scripts/test-documentation.mjs +4 -2
- package/scripts/test-editor-language-service.mjs +11 -8
- package/scripts/test-engine-commands.mjs +5 -5
- package/scripts/test-examples.mjs +30 -1
- package/scripts/test-heading-ids.mjs +2 -2
- package/scripts/test-navigation.mjs +11 -2
- package/scripts/test-nested-pages.mjs +25 -10
- package/scripts/test-package-check.mjs +29 -60
- package/scripts/test-page-model.mjs +30 -2
- package/scripts/test-project-config.mjs +5 -10
- package/scripts/test-schemas.mjs +18 -3
- package/scripts/test-theme-presets.mjs +33 -27
- package/src/components/NavigationPageTree.astro +114 -25
- package/src/components/PageContentsLinks.astro +40 -0
- package/src/components/SectionNavigationScript.astro +12 -5
- package/src/components/SiteBreadcrumbs.astro +17 -14
- package/src/components/SiteNavigation.astro +45 -12
- package/src/components/SitePage.astro +37 -23
- package/src/components/SiteSection.astro +2 -7
- package/src/components/SiteTreeNavigation.astro +35 -0
- package/src/components/TreeNavigationScript.astro +83 -0
- package/src/content.config.ts +2 -8
- package/src/layouts/BaseLayout.astro +10 -0
- package/src/lib/pageThemes.ts +11 -9
- package/src/lib/sectionContent.ts +1 -5
- package/src/lib/siteNavigation.ts +2 -1
- package/src/lib/sitePages.ts +25 -14
- package/src/pages/index.astro +2 -1
- package/src/styles/global.css +411 -43
- package/starters/basic/README.md +7 -7
- package/starters/basic/site/theme.yaml +1 -105
- package/starters/project/README.md +8 -8
- package/starters/project/site/theme.yaml +1 -31
- 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 -14
- package/examples/feature-demos/theme-presets/site/theme.yaml +0 -1
- /package/examples/complete-sites/dog-shelter-multi-page/site/{content.md → pages/000-home/content.md} +0 -0
- /package/examples/complete-sites/dog-shelter-multi-page/site/{images → pages/000-home/images}/shelter-dog.jpg +0 -0
- /package/examples/complete-sites/dog-shelter-single-page/site/{content.md → pages/000-home/content.md} +0 -0
- /package/examples/complete-sites/dog-shelter-single-page/site/{images → pages/000-home/images}/foster-dog.jpg +0 -0
- /package/examples/complete-sites/dog-shelter-single-page/site/{images → pages/000-home/images}/rescue-dog.jpg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{content.md → pages/000-home/content.md} +0 -0
- /package/examples/feature-demos/sitewide-content/site/{content.md → pages/000-home/content.md} +0 -0
- /package/examples/feature-demos/{theme-presets → theme-preset-documentation}/site/config.yaml +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/020-documentation → theme-preset-documentation/site/pages/000-home}/images/black-schnauzer.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/020-documentation → theme-preset-documentation/site/pages/000-home}/images/retriever-portrait.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/020-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 → theme-preset-documentation}/site/sitewide-content.yaml +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/020-documentation → theme-preset-documentation/site}/theme.yaml +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/010-portfolio → theme-preset-portfolio/site/pages/000-home}/images/puppy-portrait.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/010-portfolio → theme-preset-portfolio/site/pages/000-home}/images/shepherd-portrait.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/010-portfolio → theme-preset-portfolio/site/pages/000-home}/images/white-puppy.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/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/pages/030-project → theme-preset-project/site/pages/000-home}/images/boxer-portrait.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/030-project → theme-preset-project/site/pages/000-home}/images/grass-puppy.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/030-project → theme-preset-project/site/pages/000-home}/images/grey-street-dog.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/030-project → theme-preset-project/site}/theme.yaml +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/040-statement → theme-preset-statement/site/pages/000-home}/images/black-pedigree.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/040-statement → theme-preset-statement/site/pages/000-home}/images/sheepdog-grass.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/040-statement → theme-preset-statement/site/pages/000-home}/images/sheepdog-wide.jpg +0 -0
- /package/examples/feature-demos/{theme-presets/site/pages/040-statement → theme-preset-statement/site}/theme.yaml +0 -0
- /package/fixtures/basic/site/{content.md → pages/000-home/content.md} +0 -0
- /package/fixtures/basic/site/{images → pages/000-home/images}/.gitkeep +0 -0
- /package/fixtures/content-model-v2/site/{content.md → pages/000-home/content.md} +0 -0
- /package/fixtures/content-model-v2/site/{images → pages/000-home/images}/adopt.svg +0 -0
- /package/fixtures/content-model-v2/site/{images → pages/000-home/images}/detail.jpg +0 -0
- /package/fixtures/content-model-v2/site/{images → pages/000-home/images}/duplicate.jpg +0 -0
- /package/fixtures/content-model-v2/site/{images → pages/000-home/images}/foster.svg +0 -0
- /package/fixtures/content-model-v2/site/{images → pages/000-home/images}/hero.jpg +0 -0
- /package/fixtures/content-model-v2/site/{images → pages/000-home/images}/image.jpg +0 -0
- /package/fixtures/content-model-v2/site/{images → pages/000-home/images}/slide-one.jpg +0 -0
- /package/fixtures/content-model-v2/site/{images → pages/000-home/images}/slide-three.jpg +0 -0
- /package/fixtures/content-model-v2/site/{images → pages/000-home/images}/slide-two.jpg +0 -0
- /package/fixtures/nested-pages/site/{content.md → pages/000-home/content.md} +0 -0
- /package/starters/basic/site/{content.md → pages/000-home/content.md} +0 -0
- /package/starters/basic/site/{images → pages/000-home/images}/.gitkeep +0 -0
- /package/starters/project/site/{content.md → pages/000-home/content.md} +0 -0
|
@@ -151,6 +151,19 @@ const addConfigHelp = (jsonSchema) => {
|
|
|
151
151
|
'Sets the page language and selects Norna\'s built-in English or Swedish interface text. The default is `en`.',
|
|
152
152
|
documentationLink('Language reference', 'configuration.md', 'language'),
|
|
153
153
|
], ['en', 'sv', 'en-GB', 'sv-SE']);
|
|
154
|
+
addHelp(jsonSchema, 'navigation', [
|
|
155
|
+
yamlExample('navigation:\n mode: automatic'),
|
|
156
|
+
'Sets one navigation model for the complete site. `automatic` selects from the page hierarchy.',
|
|
157
|
+
documentationLink('Navigation reference', 'pages.md', 'navigation'),
|
|
158
|
+
]);
|
|
159
|
+
addFieldHelp(
|
|
160
|
+
jsonSchema,
|
|
161
|
+
'navigation.mode',
|
|
162
|
+
'navigation:\n mode: automatic',
|
|
163
|
+
'pages.md',
|
|
164
|
+
'navigation',
|
|
165
|
+
['automatic', 'sections', 'top', 'tree'],
|
|
166
|
+
);
|
|
154
167
|
addHelp(jsonSchema, 'scrollBehavior', [
|
|
155
168
|
yamlExample('scrollBehavior: smooth'),
|
|
156
169
|
'Controls same-page anchor movement. `instant` is the default; `smooth` uses the browser\'s native smooth scrolling.',
|
|
@@ -166,25 +179,17 @@ const addThemeHelp = (jsonSchema) => {
|
|
|
166
179
|
].join('\n\n');
|
|
167
180
|
addHelp(jsonSchema, 'preset', [
|
|
168
181
|
yamlExample('preset: documentation'),
|
|
169
|
-
'Selects coordinated
|
|
182
|
+
'Selects coordinated shapes, layout, image sizing, typography, palette and section backgrounds. This is the normal starting point for a theme.',
|
|
170
183
|
documentationLink('Compare theme presets', 'theme.md', 'theme-presets'),
|
|
171
184
|
]);
|
|
172
|
-
addHelp(jsonSchema, '
|
|
173
|
-
yamlExample('
|
|
174
|
-
'
|
|
185
|
+
addHelp(jsonSchema, 'shape', [
|
|
186
|
+
yamlExample('shape: soft'),
|
|
187
|
+
'Chooses one consistent corner treatment for navigation, cards and framed content throughout the site.',
|
|
175
188
|
documentationLink('Theme reference', 'theme.md'),
|
|
176
|
-
]);
|
|
177
|
-
addFieldHelp(
|
|
178
|
-
jsonSchema,
|
|
179
|
-
'navigation.mode',
|
|
180
|
-
'navigation:\n mode: automatic',
|
|
181
|
-
'theme.md',
|
|
182
|
-
undefined,
|
|
183
|
-
['automatic', 'sections', 'top', 'tree'],
|
|
184
|
-
);
|
|
189
|
+
], ['square', 'soft']);
|
|
185
190
|
addHelp(jsonSchema, 'layout', [
|
|
186
|
-
yamlExample('layout:\n
|
|
187
|
-
'Overrides
|
|
191
|
+
yamlExample('layout:\n contentSpacing: compact\n textWidth: narrow'),
|
|
192
|
+
'Overrides text width, content spacing, page width and gutters after the selected preset.',
|
|
188
193
|
documentationLink('Layout reference', 'theme.md', 'layout'),
|
|
189
194
|
]);
|
|
190
195
|
addHelp(jsonSchema, 'images', [
|
|
@@ -202,14 +207,16 @@ const addThemeHelp = (jsonSchema) => {
|
|
|
202
207
|
'Chooses a coordinated color system for the page frame, navigation, footer and section surfaces.',
|
|
203
208
|
documentationLink('Palette and section surfaces', 'theme.md', 'palette-and-section-surfaces'),
|
|
204
209
|
], ['dark', 'light', 'paper']);
|
|
205
|
-
addHelp(jsonSchema, '
|
|
206
|
-
yamlExample('
|
|
207
|
-
'
|
|
210
|
+
addHelp(jsonSchema, 'sections', [
|
|
211
|
+
yamlExample('sections:\n backgroundPattern: alternating'),
|
|
212
|
+
'Chooses whether section backgrounds stay uniform, alternate between two surfaces or cycle through three.',
|
|
208
213
|
documentationLink('Palette and section surfaces', 'theme.md', 'palette-and-section-surfaces'),
|
|
209
|
-
]
|
|
214
|
+
]);
|
|
215
|
+
addFieldHelp(jsonSchema, 'sections.backgroundPattern', 'sections:\n backgroundPattern: alternating', 'theme.md', 'palette-and-section-surfaces', ['uniform', 'alternating', 'cycling']);
|
|
210
216
|
|
|
211
217
|
const layoutFields = [
|
|
212
|
-
['layout.
|
|
218
|
+
['layout.contentSpacing', 'layout:\n contentSpacing: compact'],
|
|
219
|
+
['layout.textWidth', 'layout:\n textWidth: narrow'],
|
|
213
220
|
['layout.pageWidth', 'layout:\n pageWidth: 72rem'],
|
|
214
221
|
['layout.gutter', 'layout:\n gutter:\n desktop: clamp(1.25rem, 4vw, 3rem)\n mobile: 1rem'],
|
|
215
222
|
['layout.gutter.desktop', 'layout:\n gutter:\n desktop: clamp(1.25rem, 4vw, 3rem)'],
|
|
@@ -301,9 +308,9 @@ const addThemeHelp = (jsonSchema) => {
|
|
|
301
308
|
['typography.fontFamily', 'typography:\n fontFamily: "Arial, sans-serif"'],
|
|
302
309
|
['typography.profile', 'typography:\n profile: reading'],
|
|
303
310
|
['typography.rhythm', 'typography:\n rhythm: normal'],
|
|
304
|
-
['typography.overrides', 'typography:\n overrides:\n body:\n
|
|
311
|
+
['typography.overrides', 'typography:\n overrides:\n body:\n lineHeight: 1.55'],
|
|
305
312
|
['typography.overrides.headings', 'typography:\n overrides:\n headings:\n h2:\n size: medium'],
|
|
306
|
-
['typography.overrides.body', 'typography:\n overrides:\n body:\n
|
|
313
|
+
['typography.overrides.body', 'typography:\n overrides:\n body:\n lineHeight: 1.55'],
|
|
307
314
|
['typography.overrides.caption', 'typography:\n overrides:\n caption:\n size: small'],
|
|
308
315
|
];
|
|
309
316
|
for (const [propertyPath, example] of typographyFields) {
|
|
@@ -346,7 +353,6 @@ const addThemeHelp = (jsonSchema) => {
|
|
|
346
353
|
textOverrideHelp(propertyPath, `${prefix}\n `, true);
|
|
347
354
|
}
|
|
348
355
|
textOverrideHelp('typography.overrides.body', 'typography:\n overrides:\n body:');
|
|
349
|
-
addFieldHelp(jsonSchema, 'typography.overrides.body.width', 'typography:\n overrides:\n body:\n width: narrow', 'typography.md', 'configuration-shape');
|
|
350
356
|
addFieldHelp(jsonSchema, 'typography.overrides.body.paragraphSpacing', 'typography:\n overrides:\n body:\n paragraphSpacing: 1em', 'typography.md', 'configuration-shape');
|
|
351
357
|
textOverrideHelp('typography.overrides.caption', 'typography:\n overrides:\n caption:');
|
|
352
358
|
addFieldHelp(jsonSchema, 'typography.overrides.caption.spacingBefore', 'typography:\n overrides:\n caption:\n spacingBefore: 0.5em', 'typography.md', 'configuration-shape');
|
|
@@ -359,9 +365,8 @@ const addThemeHelp = (jsonSchema) => {
|
|
|
359
365
|
weight: '${2:600}',
|
|
360
366
|
},
|
|
361
367
|
},
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
lineHeight: '${4:1.55}',
|
|
368
|
+
body: {
|
|
369
|
+
lineHeight: '${3:1.55}',
|
|
365
370
|
},
|
|
366
371
|
},
|
|
367
372
|
description: 'Start a focused set of heading and body-text overrides.',
|
|
@@ -370,6 +375,44 @@ const addThemeHelp = (jsonSchema) => {
|
|
|
370
375
|
})]);
|
|
371
376
|
};
|
|
372
377
|
|
|
378
|
+
const addPageThemeHelp = (jsonSchema) => {
|
|
379
|
+
jsonSchema.markdownDescription = [
|
|
380
|
+
yamlExample('layout:\n textWidth: narrow\n contentSpacing: compact'),
|
|
381
|
+
'A page theme may adjust only page layout, managed-image sizing and the section background pattern. Descendant pages inherit these values. Site colors, shapes and typography stay consistent.',
|
|
382
|
+
documentationLink('Page theme reference', 'theme.md', 'page-themes'),
|
|
383
|
+
].join('\n\n');
|
|
384
|
+
addHelp(jsonSchema, 'layout', [
|
|
385
|
+
yamlExample('layout:\n textWidth: narrow\n contentSpacing: compact'),
|
|
386
|
+
'Adjusts body-text line length and vertical content spacing for this page and its descendants.',
|
|
387
|
+
documentationLink('Page theme reference', 'theme.md', 'page-themes'),
|
|
388
|
+
]);
|
|
389
|
+
addFieldHelp(jsonSchema, 'layout.textWidth', 'layout:\n textWidth: narrow', 'theme.md', 'page-themes', ['narrow', 'normal', 'wide']);
|
|
390
|
+
addFieldHelp(jsonSchema, 'layout.contentSpacing', 'layout:\n contentSpacing: compact', 'theme.md', 'page-themes', ['compact', 'normal', 'spacious']);
|
|
391
|
+
addHelp(jsonSchema, 'images', [
|
|
392
|
+
yamlExample('images:\n width: 900px'),
|
|
393
|
+
'Adjusts managed-image sizing for this page and its descendants.',
|
|
394
|
+
documentationLink('Image sizing reference', 'theme.md', 'image-sizing'),
|
|
395
|
+
]);
|
|
396
|
+
const imageFields = [
|
|
397
|
+
['images.width', 'images:\n width: 900px'],
|
|
398
|
+
['images.maxAvailableWidthPercent', 'images:\n maxAvailableWidthPercent:\n desktop: 100\n mobile: 100'],
|
|
399
|
+
['images.maxAvailableWidthPercent.desktop', 'images:\n maxAvailableWidthPercent:\n desktop: 100'],
|
|
400
|
+
['images.maxAvailableWidthPercent.mobile', 'images:\n maxAvailableWidthPercent:\n mobile: 100'],
|
|
401
|
+
['images.maxAvailableHeightPercent', 'images:\n maxAvailableHeightPercent:\n desktop: 74\n mobile: 68'],
|
|
402
|
+
['images.maxAvailableHeightPercent.desktop', 'images:\n maxAvailableHeightPercent:\n desktop: 74'],
|
|
403
|
+
['images.maxAvailableHeightPercent.mobile', 'images:\n maxAvailableHeightPercent:\n mobile: 68'],
|
|
404
|
+
];
|
|
405
|
+
for (const [propertyPath, example] of imageFields) {
|
|
406
|
+
addFieldHelp(jsonSchema, propertyPath, example, 'theme.md', 'image-sizing');
|
|
407
|
+
}
|
|
408
|
+
addHelp(jsonSchema, 'sections', [
|
|
409
|
+
yamlExample('sections:\n backgroundPattern: alternating'),
|
|
410
|
+
'Adjusts the section background sequence for this page and its descendants.',
|
|
411
|
+
documentationLink('Page theme reference', 'theme.md', 'page-themes'),
|
|
412
|
+
]);
|
|
413
|
+
addFieldHelp(jsonSchema, 'sections.backgroundPattern', 'sections:\n backgroundPattern: alternating', 'theme.md', 'page-themes', ['uniform', 'alternating', 'cycling']);
|
|
414
|
+
};
|
|
415
|
+
|
|
373
416
|
const addSitewideHelp = (jsonSchema) => {
|
|
374
417
|
jsonSchema.markdownDescription = [
|
|
375
418
|
yamlExample('footer:\n copyrightMessage: Copyright Example Owner.'),
|
|
@@ -469,6 +512,7 @@ export const applySchemaEditorMetadata = (filename, jsonSchema) => {
|
|
|
469
512
|
addConfigHelp(jsonSchema);
|
|
470
513
|
}
|
|
471
514
|
if (filename === 'theme.schema.json') addThemeHelp(jsonSchema);
|
|
515
|
+
if (filename === 'page-theme.schema.json') addPageThemeHelp(jsonSchema);
|
|
472
516
|
if (filename === 'sitewide-content.schema.json') addSitewideHelp(jsonSchema);
|
|
473
517
|
if (filename === 'content-frontmatter.schema.json') addContentHelp(jsonSchema);
|
|
474
518
|
return jsonSchema;
|
|
@@ -61,6 +61,20 @@ export const schemaValueDefinitions = Object.freeze([
|
|
|
61
61
|
light: option('Light', 'White and cool light surfaces with dark text.'),
|
|
62
62
|
paper: option('Paper', 'Warm off-white surfaces with dark text.'),
|
|
63
63
|
}),
|
|
64
|
+
definition(['square', 'soft'], {
|
|
65
|
+
square: option('Square', 'Use square corners for navigation, cards and framed content.'),
|
|
66
|
+
soft: option('Soft', 'Use restrained rounded corners consistently across the site.'),
|
|
67
|
+
}),
|
|
68
|
+
definition(['uniform', 'alternating', 'cycling'], {
|
|
69
|
+
uniform: option('Uniform', 'Use the palette base surface for every section.'),
|
|
70
|
+
alternating: option('Alternating', 'Alternate between the palette base and soft surfaces.'),
|
|
71
|
+
cycling: option('Cycling', 'Cycle through the palette base, soft and emphasis surfaces.'),
|
|
72
|
+
}),
|
|
73
|
+
definition(['compact', 'normal', 'spacious'], {
|
|
74
|
+
compact: option('Compact', 'Use restrained spacing between sections and structured blocks.'),
|
|
75
|
+
normal: option('Normal', 'Use balanced content spacing.'),
|
|
76
|
+
spacious: option('Spacious', 'Use more expansive spacing between sections and structured blocks.'),
|
|
77
|
+
}),
|
|
64
78
|
definition(['base', 'soft', 'emphasis'], {
|
|
65
79
|
base: option('Base', 'Use the palette\'s normal page surface.'),
|
|
66
80
|
soft: option('Soft', 'Use the palette\'s subtle contrasting surface.'),
|
|
@@ -2,10 +2,9 @@ import { access, readdir, readFile } from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import {
|
|
4
4
|
siteContentLabel,
|
|
5
|
-
siteContentPath,
|
|
6
5
|
siteDir,
|
|
7
6
|
siteDirLabel,
|
|
8
|
-
|
|
7
|
+
homePageDirectory,
|
|
9
8
|
siteImagesLabel,
|
|
10
9
|
sitePagesDir,
|
|
11
10
|
sitePagesLabel,
|
|
@@ -25,7 +24,7 @@ const knownConfigTopLevelFrontmatterKeys = new Set(schemaTopLevelKeys.config);
|
|
|
25
24
|
const knownContentTopLevelFrontmatterKeys = new Set(schemaTopLevelKeys.content);
|
|
26
25
|
const knownThemeTopLevelFrontmatterKeys = new Set(schemaTopLevelKeys.theme);
|
|
27
26
|
const knownPageThemeTopLevelFrontmatterKeys = new Set(
|
|
28
|
-
|
|
27
|
+
schemaTopLevelKeys.pageTheme,
|
|
29
28
|
);
|
|
30
29
|
const knownSitewideTopLevelFrontmatterKeys = new Set(schemaTopLevelKeys.sitewide);
|
|
31
30
|
const knownNestedFrontmatterKeys = new Set([
|
|
@@ -35,7 +34,7 @@ const knownNestedFrontmatterKeys = new Set([
|
|
|
35
34
|
'blockGap',
|
|
36
35
|
'caption',
|
|
37
36
|
'carousel',
|
|
38
|
-
'
|
|
37
|
+
'contentSpacing',
|
|
39
38
|
'desktop',
|
|
40
39
|
'fontFamily',
|
|
41
40
|
'from',
|
|
@@ -67,11 +66,14 @@ const knownNestedFrontmatterKeys = new Set([
|
|
|
67
66
|
'profile',
|
|
68
67
|
'rhythm',
|
|
69
68
|
'sectionGap',
|
|
70
|
-
'
|
|
69
|
+
'backgroundPattern',
|
|
70
|
+
'sections',
|
|
71
|
+
'shape',
|
|
71
72
|
'size',
|
|
72
73
|
'spacingAfter',
|
|
73
74
|
'spacingBefore',
|
|
74
75
|
'theme',
|
|
76
|
+
'textWidth',
|
|
75
77
|
'typography',
|
|
76
78
|
'until',
|
|
77
79
|
'visible',
|
|
@@ -83,6 +85,18 @@ export const toPosixPath = (filePath) => filePath.split(path.sep).join('/');
|
|
|
83
85
|
const fileExists = async (filePath) => access(filePath).then(() => true, () => false);
|
|
84
86
|
|
|
85
87
|
export const getContentFiles = async () => {
|
|
88
|
+
const legacyContentPath = path.join(siteDir, 'content.md');
|
|
89
|
+
const legacyImagesPath = path.join(siteDir, 'images');
|
|
90
|
+
const legacyPaths = [];
|
|
91
|
+
if (await fileExists(legacyContentPath)) legacyPaths.push(`${siteDirLabel}/content.md`);
|
|
92
|
+
if (await fileExists(legacyImagesPath)) legacyPaths.push(`${siteDirLabel}/images`);
|
|
93
|
+
if (legacyPaths.length > 0) {
|
|
94
|
+
throw new Error([
|
|
95
|
+
`The old root-page structure is no longer supported: ${legacyPaths.join(', ')}.`,
|
|
96
|
+
`Move the homepage content to ${sitePagesLabel}/${homePageDirectory}/content.md and its images to ${sitePagesLabel}/${homePageDirectory}/images/.`,
|
|
97
|
+
].join('\n'));
|
|
98
|
+
}
|
|
99
|
+
|
|
86
100
|
const legacyRoutesDir = path.join(siteDir, 'routes');
|
|
87
101
|
const legacyRoutes = await readdir(legacyRoutesDir).catch((error) => {
|
|
88
102
|
if (error?.code === 'ENOENT') return null;
|
|
@@ -92,22 +106,7 @@ export const getContentFiles = async () => {
|
|
|
92
106
|
throw new Error(`${siteDirLabel}/routes is no longer supported. Rename it to ${sitePagesLabel} and use NNN-page-id directory names.`);
|
|
93
107
|
}
|
|
94
108
|
|
|
95
|
-
const contentFiles = [
|
|
96
|
-
contentLabel: siteContentLabel,
|
|
97
|
-
contentPath: siteContentPath,
|
|
98
|
-
imagesDir: siteImagesDir,
|
|
99
|
-
imagesLabel: siteImagesLabel,
|
|
100
|
-
isHome: true,
|
|
101
|
-
pageDirectory: null,
|
|
102
|
-
pageDirectories: [],
|
|
103
|
-
pageId: null,
|
|
104
|
-
pageIds: [],
|
|
105
|
-
pageOrder: 0,
|
|
106
|
-
pageOrders: [],
|
|
107
|
-
pagePath: '',
|
|
108
|
-
parentPagePath: null,
|
|
109
|
-
depth: 0,
|
|
110
|
-
}];
|
|
109
|
+
const contentFiles = [];
|
|
111
110
|
const collectPageContentFiles = async (pagesDir, pagesLabel, parentPageDirectory = '') => {
|
|
112
111
|
const pageEntries = await readdir(pagesDir, { withFileTypes: true }).catch((error) => {
|
|
113
112
|
if (error?.code === 'ENOENT') {
|
|
@@ -138,16 +137,36 @@ export const getContentFiles = async () => {
|
|
|
138
137
|
continue;
|
|
139
138
|
}
|
|
140
139
|
const pageMetadata = parsePageDirectoryPath(pageDirectory, pageLabel);
|
|
140
|
+
const isHome = pageDirectory === homePageDirectory;
|
|
141
141
|
|
|
142
142
|
contentFiles.push({
|
|
143
143
|
contentLabel: `${pageLabel}/content.md`,
|
|
144
144
|
contentPath: pageContentPath,
|
|
145
145
|
imagesDir: path.join(pageDir, 'images'),
|
|
146
146
|
imagesLabel: `${pageLabel}/images`,
|
|
147
|
-
isHome
|
|
147
|
+
isHome,
|
|
148
148
|
...pageMetadata,
|
|
149
|
+
pagePath: isHome ? '' : pageMetadata.pagePath,
|
|
150
|
+
parentPagePath: isHome ? null : pageMetadata.parentPagePath,
|
|
149
151
|
});
|
|
150
152
|
|
|
153
|
+
if (isHome) {
|
|
154
|
+
const homePagesDir = path.join(pageDir, 'pages');
|
|
155
|
+
const homeChildEntries = await readdir(homePagesDir, { withFileTypes: true }).catch((error) => {
|
|
156
|
+
if (error?.code === 'ENOENT') return [];
|
|
157
|
+
throw error;
|
|
158
|
+
});
|
|
159
|
+
const homeChildDirectories = homeChildEntries.filter((childEntry) => childEntry.isDirectory());
|
|
160
|
+
if (homeChildDirectories.length > 0) {
|
|
161
|
+
throw new Error([
|
|
162
|
+
`${pageLabel} is the homepage and cannot contain child pages.`,
|
|
163
|
+
`Move these page directories beside ${homePageDirectory} under ${sitePagesLabel}/, or below another non-home page:`,
|
|
164
|
+
...homeChildDirectories.map(({ name }) => `- ${pageLabel}/pages/${name}`),
|
|
165
|
+
].join('\n'));
|
|
166
|
+
}
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
151
170
|
await collectPageContentFiles(
|
|
152
171
|
path.join(pageDir, 'pages'),
|
|
153
172
|
`${pageLabel}/pages`,
|
|
@@ -157,6 +176,9 @@ export const getContentFiles = async () => {
|
|
|
157
176
|
};
|
|
158
177
|
|
|
159
178
|
await collectPageContentFiles(sitePagesDir, sitePagesLabel);
|
|
179
|
+
if (!contentFiles.some(({ isHome }) => isHome)) {
|
|
180
|
+
throw new Error(`Homepage content is missing. Create ${siteContentLabel}.`);
|
|
181
|
+
}
|
|
160
182
|
|
|
161
183
|
return contentFiles;
|
|
162
184
|
};
|
|
@@ -303,9 +325,12 @@ export const validateFrontmatterStructure = (frontmatter, addIssue, {
|
|
|
303
325
|
fix = 'Put local image references in norna-image-stack or norna-image-carousel blocks in the Markdown body.';
|
|
304
326
|
} else if (fileKind === 'content' && knownThemeTopLevelFrontmatterKeys.has(key)) {
|
|
305
327
|
fix = `Move "${key}:" to theme.yaml. Visual settings do not belong in content frontmatter.`;
|
|
306
|
-
} else if (fileKind === 'page theme' && key === 'navigation') {
|
|
307
|
-
message = `Frontmatter line ${lineNumber}:
|
|
308
|
-
fix = 'Set the
|
|
328
|
+
} else if ((fileKind === 'theme' || fileKind === 'page theme') && key === 'navigation') {
|
|
329
|
+
message = `Frontmatter line ${lineNumber}: navigation is technical, site-wide configuration and does not belong in ${fileKind}. Set it in config.yaml.`;
|
|
330
|
+
fix = 'Set the navigation mode under "navigation:" in config.yaml.';
|
|
331
|
+
} else if (fileKind === 'page theme' && ['preset', 'shape', 'palette', 'typography'].includes(key)) {
|
|
332
|
+
message = `Frontmatter line ${lineNumber}: page themes may not define site-wide visual identity through "${key}".`;
|
|
333
|
+
fix = `Move "${key}:" to the root theme.yaml. Page themes may set only layout.textWidth, layout.contentSpacing, images, and sections.backgroundPattern.`;
|
|
309
334
|
} else if ((fileKind === 'theme' || fileKind === 'page theme') && ['logo', 'site'].includes(key)) {
|
|
310
335
|
message = `Frontmatter line ${lineNumber}: ${fileKind} may not define navigation logo settings. Optional logo display settings belong under "logo:" in ${sitewideContentLabel}.`;
|
|
311
336
|
fix = `Move only the logo display settings under "logo:" in ${sitewideContentLabel}; the homepage Markdown H1 supplies navigation text and logo alternative text.`;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const homePageDirectory = '000-home';
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { homePageDirectory } from './site-conventions.mjs';
|
|
4
5
|
|
|
5
6
|
const siteDirectoryEnvName = 'NORNA_SITE_DIR';
|
|
6
7
|
const invocationRootEnvName = 'NORNA_INVOCATION_ROOT';
|
|
7
8
|
const defaultSiteDirectory = 'site';
|
|
9
|
+
export { homePageDirectory };
|
|
8
10
|
|
|
9
11
|
const currentFile = fileURLToPath(import.meta.url);
|
|
10
12
|
const currentDirectory = path.dirname(currentFile);
|
|
@@ -23,7 +25,10 @@ export const invocationRoot = configuredInvocationRoot
|
|
|
23
25
|
const hasSiteFilesInDirectory = (siteDir) => {
|
|
24
26
|
return (
|
|
25
27
|
existsSync(path.join(siteDir, 'config.yaml'))
|
|
26
|
-
&&
|
|
28
|
+
&& (
|
|
29
|
+
existsSync(path.join(siteDir, 'pages', homePageDirectory, 'content.md'))
|
|
30
|
+
|| existsSync(path.join(siteDir, 'content.md'))
|
|
31
|
+
)
|
|
27
32
|
);
|
|
28
33
|
};
|
|
29
34
|
|
|
@@ -116,10 +121,11 @@ const getPathLabel = (filePath) => {
|
|
|
116
121
|
export const siteDir = resolvedSitePaths.siteDir;
|
|
117
122
|
export const siteConfigPath = path.join(siteDir, 'config.yaml');
|
|
118
123
|
export const siteThemePath = path.join(siteDir, 'theme.yaml');
|
|
119
|
-
export const siteContentPath = path.join(siteDir, 'content.md');
|
|
120
124
|
export const sitewideContentPath = path.join(siteDir, 'sitewide-content.yaml');
|
|
121
|
-
export const siteImagesDir = path.join(siteDir, 'images');
|
|
122
125
|
export const sitePagesDir = path.join(siteDir, 'pages');
|
|
126
|
+
export const siteHomePageDir = path.join(sitePagesDir, homePageDirectory);
|
|
127
|
+
export const siteContentPath = path.join(siteHomePageDir, 'content.md');
|
|
128
|
+
export const siteImagesDir = path.join(siteHomePageDir, 'images');
|
|
123
129
|
export const sitePublicDir = path.join(siteDir, 'public');
|
|
124
130
|
export const siteStateDir = path.join(siteDir, '.norna');
|
|
125
131
|
export const astroPublicDir = path.join(siteStateDir, 'public');
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
} from './site-paths.mjs';
|
|
13
13
|
import { resolveThemePresentation } from './presentation.mjs';
|
|
14
14
|
import { resolveThemeConfig } from './theme-presets.mjs';
|
|
15
|
+
import { pageThemeSchema, themeVisualSchema } from './schema-definitions.mjs';
|
|
15
16
|
import { parseYamlConfig } from './yaml-config.mjs';
|
|
16
17
|
|
|
17
18
|
export const readThemeConfig = async () => {
|
|
@@ -23,6 +24,7 @@ export const readThemeConfig = async () => {
|
|
|
23
24
|
throw error;
|
|
24
25
|
});
|
|
25
26
|
const config = parseYamlConfig(themeFile, siteThemeLabel, {
|
|
27
|
+
schema: themeVisualSchema,
|
|
26
28
|
validateStructure: validateThemeYamlStructure,
|
|
27
29
|
});
|
|
28
30
|
resolveThemeConfig(config, siteThemeLabel);
|
|
@@ -64,10 +66,9 @@ export const validatePageThemeFiles = async () => {
|
|
|
64
66
|
for (const file of files) {
|
|
65
67
|
const source = await readFile(file.path, 'utf8');
|
|
66
68
|
const config = parseYamlConfig(source, file.label, {
|
|
69
|
+
schema: pageThemeSchema,
|
|
67
70
|
validateStructure: validatePageThemeYamlStructure,
|
|
68
71
|
});
|
|
69
|
-
resolveThemeConfig(config, file.label);
|
|
70
|
-
resolveThemePresentation(config, file.label);
|
|
71
72
|
|
|
72
73
|
configs.push({ ...file, config });
|
|
73
74
|
}
|
|
@@ -2,10 +2,12 @@ import { mergeDeep } from './typography.mjs';
|
|
|
2
2
|
|
|
3
3
|
const themePresetConfigurations = Object.freeze({
|
|
4
4
|
portfolio: Object.freeze({
|
|
5
|
-
|
|
5
|
+
shape: 'square',
|
|
6
6
|
layout: {
|
|
7
|
-
|
|
7
|
+
contentSpacing: 'normal',
|
|
8
|
+
textWidth: 'wide',
|
|
8
9
|
pageWidth: '1240px',
|
|
10
|
+
localNavigationGap: 'clamp(1.5rem, 3vw, 3rem)',
|
|
9
11
|
gutter: {
|
|
10
12
|
desktop: 'clamp(1.25rem, 4vw, 3rem)',
|
|
11
13
|
mobile: '1rem',
|
|
@@ -22,13 +24,15 @@ const themePresetConfigurations = Object.freeze({
|
|
|
22
24
|
rhythm: 'normal',
|
|
23
25
|
},
|
|
24
26
|
palette: 'dark',
|
|
25
|
-
|
|
27
|
+
sections: { backgroundPattern: 'uniform' },
|
|
26
28
|
}),
|
|
27
29
|
documentation: Object.freeze({
|
|
28
|
-
|
|
30
|
+
shape: 'soft',
|
|
29
31
|
layout: {
|
|
30
|
-
|
|
32
|
+
contentSpacing: 'compact',
|
|
33
|
+
textWidth: 'narrow',
|
|
31
34
|
pageWidth: '1240px',
|
|
35
|
+
localNavigationGap: 'clamp(1.5rem, 3vw, 3rem)',
|
|
32
36
|
gutter: {
|
|
33
37
|
desktop: 'clamp(1.25rem, 4vw, 3rem)',
|
|
34
38
|
mobile: '1rem',
|
|
@@ -45,13 +49,15 @@ const themePresetConfigurations = Object.freeze({
|
|
|
45
49
|
rhythm: 'compact',
|
|
46
50
|
},
|
|
47
51
|
palette: 'paper',
|
|
48
|
-
|
|
52
|
+
sections: { backgroundPattern: 'alternating' },
|
|
49
53
|
}),
|
|
50
54
|
project: Object.freeze({
|
|
51
|
-
|
|
55
|
+
shape: 'soft',
|
|
52
56
|
layout: {
|
|
53
|
-
|
|
57
|
+
contentSpacing: 'compact',
|
|
58
|
+
textWidth: 'normal',
|
|
54
59
|
pageWidth: '1120px',
|
|
60
|
+
localNavigationGap: 'clamp(1.5rem, 3vw, 3rem)',
|
|
55
61
|
gutter: {
|
|
56
62
|
desktop: 'clamp(1.25rem, 4vw, 3rem)',
|
|
57
63
|
mobile: '1rem',
|
|
@@ -68,13 +74,15 @@ const themePresetConfigurations = Object.freeze({
|
|
|
68
74
|
rhythm: 'compact',
|
|
69
75
|
},
|
|
70
76
|
palette: 'light',
|
|
71
|
-
|
|
77
|
+
sections: { backgroundPattern: 'alternating' },
|
|
72
78
|
}),
|
|
73
79
|
statement: Object.freeze({
|
|
74
|
-
|
|
80
|
+
shape: 'square',
|
|
75
81
|
layout: {
|
|
76
|
-
|
|
82
|
+
contentSpacing: 'spacious',
|
|
83
|
+
textWidth: 'normal',
|
|
77
84
|
pageWidth: '1280px',
|
|
85
|
+
localNavigationGap: 'clamp(1.5rem, 3vw, 3rem)',
|
|
78
86
|
gutter: {
|
|
79
87
|
desktop: 'clamp(1.5rem, 5vw, 4rem)',
|
|
80
88
|
mobile: '1rem',
|
|
@@ -91,7 +99,7 @@ const themePresetConfigurations = Object.freeze({
|
|
|
91
99
|
rhythm: 'airy',
|
|
92
100
|
},
|
|
93
101
|
palette: 'paper',
|
|
94
|
-
|
|
102
|
+
sections: { backgroundPattern: 'cycling' },
|
|
95
103
|
}),
|
|
96
104
|
});
|
|
97
105
|
|
|
@@ -158,6 +166,30 @@ export const resolveThemeConfig = (theme = {}, sourceLabel = 'theme.yaml') => {
|
|
|
158
166
|
};
|
|
159
167
|
};
|
|
160
168
|
|
|
169
|
+
const mergePageThemePart = (base, override, keys) => Object.fromEntries(keys
|
|
170
|
+
.filter((key) => override?.[key] !== undefined || base?.[key] !== undefined)
|
|
171
|
+
.map((key) => [key, override?.[key] ?? base?.[key]]));
|
|
172
|
+
|
|
173
|
+
export const mergePageThemeConfig = (base = {}, override = {}) => ({
|
|
174
|
+
...base,
|
|
175
|
+
layout: {
|
|
176
|
+
...(base.layout ?? {}),
|
|
177
|
+
...mergePageThemePart(base.layout, override.layout, ['contentSpacing', 'textWidth']),
|
|
178
|
+
},
|
|
179
|
+
images: {
|
|
180
|
+
...(base.images ?? {}),
|
|
181
|
+
...mergePageThemePart(base.images, override.images, [
|
|
182
|
+
'width',
|
|
183
|
+
'maxAvailableWidthPercent',
|
|
184
|
+
'maxAvailableHeightPercent',
|
|
185
|
+
]),
|
|
186
|
+
},
|
|
187
|
+
sections: {
|
|
188
|
+
...(base.sections ?? {}),
|
|
189
|
+
...mergePageThemePart(base.sections, override.sections, ['backgroundPattern']),
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
|
|
161
193
|
const quote = (value) => JSON.stringify(value);
|
|
162
194
|
const responsiveValueLines = (label, value, indent = 2) => {
|
|
163
195
|
const prefix = ' '.repeat(indent);
|
|
@@ -171,7 +203,7 @@ const responsiveValueLines = (label, value, indent = 2) => {
|
|
|
171
203
|
export const renderThemePresetReference = (presetName, sourceLabel = 'theme.yaml') => {
|
|
172
204
|
const preset = getThemePreset(presetName, sourceLabel);
|
|
173
205
|
const metadata = getThemePresetMetadata(presetName);
|
|
174
|
-
const {
|
|
206
|
+
const { shape, layout, images, typography, palette, sections } = preset;
|
|
175
207
|
|
|
176
208
|
return [
|
|
177
209
|
`# Original values for Norna's "${presetName}" theme preset.`,
|
|
@@ -181,13 +213,14 @@ export const renderThemePresetReference = (presetName, sourceLabel = 'theme.yaml
|
|
|
181
213
|
`# Available theme presets: ${themePresetNames.join(', ')}.`,
|
|
182
214
|
`preset: ${presetName}`,
|
|
183
215
|
'',
|
|
184
|
-
'
|
|
185
|
-
|
|
186
|
-
` mode: ${navigation.mode}`,
|
|
216
|
+
'# Alternatives: square, soft.',
|
|
217
|
+
`shape: ${shape}`,
|
|
187
218
|
'',
|
|
188
219
|
'layout:',
|
|
189
|
-
' # Alternatives: compact, normal,
|
|
190
|
-
`
|
|
220
|
+
' # Alternatives: compact, normal, spacious.',
|
|
221
|
+
` contentSpacing: ${layout.contentSpacing}`,
|
|
222
|
+
' # Alternatives: narrow, normal, wide.',
|
|
223
|
+
` textWidth: ${layout.textWidth}`,
|
|
191
224
|
' # Any positive CSS length, including px, rem, em, %, or clamp(...).',
|
|
192
225
|
` pageWidth: ${layout.pageWidth}`,
|
|
193
226
|
' gutter:',
|
|
@@ -219,9 +252,9 @@ export const renderThemePresetReference = (presetName, sourceLabel = 'theme.yaml
|
|
|
219
252
|
'',
|
|
220
253
|
'# Alternatives: dark, light, paper.',
|
|
221
254
|
`palette: ${palette}`,
|
|
222
|
-
'
|
|
223
|
-
'#
|
|
224
|
-
`
|
|
255
|
+
'sections:',
|
|
256
|
+
' # Alternatives: uniform, alternating, cycling.',
|
|
257
|
+
` backgroundPattern: ${sections.backgroundPattern}`,
|
|
225
258
|
'',
|
|
226
259
|
].join('\n');
|
|
227
260
|
};
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { readFile } from 'node:fs/promises';
|
|
2
|
-
import path from 'node:path';
|
|
3
1
|
import {
|
|
4
2
|
defaultTypography,
|
|
5
3
|
resolveTypographyConfig,
|
|
@@ -13,20 +11,12 @@ import {
|
|
|
13
11
|
readSiteFile,
|
|
14
12
|
validateContentFrontmatterStructure,
|
|
15
13
|
validateFrontmatterIndentation,
|
|
16
|
-
validatePageThemeYamlStructure,
|
|
17
14
|
} from './lib/site-content.mjs';
|
|
18
15
|
import {
|
|
19
|
-
siteContentLabel,
|
|
20
|
-
siteContentPath,
|
|
21
|
-
sitePagesDir,
|
|
22
|
-
sitePagesLabel,
|
|
23
16
|
siteThemeLabel,
|
|
24
|
-
siteThemePath,
|
|
25
17
|
} from './lib/site-paths.mjs';
|
|
26
|
-
import { getPageDirectoryAncestors } from './lib/page-model.mjs';
|
|
27
18
|
import { readThemeConfig } from './lib/theme-config.mjs';
|
|
28
19
|
import { resolveThemeConfig } from './lib/theme-presets.mjs';
|
|
29
|
-
import { parseYamlConfig } from './lib/yaml-config.mjs';
|
|
30
20
|
|
|
31
21
|
const mode = process.argv[2] ?? 'show';
|
|
32
22
|
|
|
@@ -199,28 +189,6 @@ const readThemeTypography = async () => {
|
|
|
199
189
|
return resolveAnnotatedTypographyConfig(themeTypographyConfig ?? defaultTypography, siteThemeLabel);
|
|
200
190
|
};
|
|
201
191
|
|
|
202
|
-
const readPageThemeTypography = async (contentFile, themeTypography) => {
|
|
203
|
-
if (contentFile.isHome) return themeTypography;
|
|
204
|
-
|
|
205
|
-
for (const pageDirectory of getPageDirectoryAncestors(contentFile.pageDirectory).reverse()) {
|
|
206
|
-
const pageThemePath = path.join(sitePagesDir, pageDirectory, 'theme.yaml');
|
|
207
|
-
const pageThemeFile = await readFile(pageThemePath, 'utf8').catch((error) => {
|
|
208
|
-
if (error?.code === 'ENOENT') return null;
|
|
209
|
-
throw error;
|
|
210
|
-
});
|
|
211
|
-
if (!pageThemeFile) continue;
|
|
212
|
-
const pageThemeLabel = `${sitePagesLabel}/${pageDirectory}/theme.yaml`;
|
|
213
|
-
|
|
214
|
-
const pageThemeConfig = resolveThemeConfig(parseYamlConfig(pageThemeFile, pageThemeLabel, {
|
|
215
|
-
validateStructure: validatePageThemeYamlStructure,
|
|
216
|
-
}), pageThemeLabel);
|
|
217
|
-
const typographyConfig = pageThemeConfig.typography;
|
|
218
|
-
return resolveAnnotatedTypographyConfig(typographyConfig ?? defaultTypography, pageThemeLabel);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
return themeTypography;
|
|
222
|
-
};
|
|
223
|
-
|
|
224
192
|
const readPageTypography = async (contentFile, siteThemeTypography) => {
|
|
225
193
|
const { frontmatter, body } = await readSiteFile(contentFile.contentPath, contentFile.contentLabel);
|
|
226
194
|
const indentationIssues = [];
|
|
@@ -233,7 +201,7 @@ const readPageTypography = async (contentFile, siteThemeTypography) => {
|
|
|
233
201
|
].join('\n'));
|
|
234
202
|
}
|
|
235
203
|
|
|
236
|
-
const pageTypography =
|
|
204
|
+
const pageTypography = siteThemeTypography;
|
|
237
205
|
const sections = (await getBodySections(body)).sections
|
|
238
206
|
.filter((section) => section.id)
|
|
239
207
|
.map((section) => {
|
|
@@ -182,9 +182,7 @@ const getGlobalImageCandidates = (globalImageCandidatesByName, imageName, expect
|
|
|
182
182
|
|
|
183
183
|
const getReferenceLabel = (contentFile, section) => `${contentFile.contentLabel} [${section.id ?? 'page title'}]`;
|
|
184
184
|
|
|
185
|
-
const getRootLabel = (contentFile) => contentFile.
|
|
186
|
-
? contentFile.imagesLabel
|
|
187
|
-
: contentFile.imagesLabel.replace(/\/images$/, '');
|
|
185
|
+
const getRootLabel = (contentFile) => contentFile.imagesLabel.replace(/\/images$/, '');
|
|
188
186
|
|
|
189
187
|
const getCandidateLabel = ({ contentFile, imagePath }) =>
|
|
190
188
|
`${contentFile.imagesLabel}/${toPosixPath(path.relative(contentFile.imagesDir, imagePath))}`;
|
|
@@ -34,7 +34,7 @@ const runCli = (args, env = {}) => {
|
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
try {
|
|
37
|
-
await mkdir(path.join(siteDir, '
|
|
37
|
+
await mkdir(path.join(siteDir, 'pages', '000-home'), { recursive: true });
|
|
38
38
|
await mkdir(path.join(siteDir, 'public'), { recursive: true });
|
|
39
39
|
await writeFile(path.join(siteDir, 'public', 'robots.txt'), 'User-agent: *\nAllow: /\n');
|
|
40
40
|
await writeFile(path.join(siteDir, 'config.yaml'), 'url: https://example.com/\n');
|
|
@@ -55,7 +55,7 @@ palette: dark
|
|
|
55
55
|
title: Active banner
|
|
56
56
|
text: Active banner text.
|
|
57
57
|
`);
|
|
58
|
-
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
58
|
+
await writeFile(path.join(siteDir, 'pages', '000-home', 'content.md'), `---
|
|
59
59
|
page:
|
|
60
60
|
description: Test site for temporary banners.
|
|
61
61
|
---
|