@janga/norna 0.7.9 → 0.7.11
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 +4 -2
- package/bin/norna-cli.mjs +3 -0
- package/docs/README.md +16 -12
- package/docs/commands.md +6 -0
- package/docs/configuration.md +10 -54
- package/docs/content.md +20 -21
- package/docs/design/norna-diagram-design.md +2 -0
- package/docs/design/site-examples-structure.md +35 -24
- package/docs/engine-development.md +13 -7
- package/docs/getting-started.md +56 -19
- package/docs/routes.md +8 -6
- package/docs/site-structure.md +11 -6
- package/docs/sitewide-content.md +74 -0
- package/docs/theme.md +125 -140
- package/docs/typography.md +72 -45
- package/examples/README.md +41 -0
- package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/site.schema.json +59 -0
- package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/sitewide.schema.json +138 -0
- package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/theme.schema.json +755 -0
- package/examples/complete-sites/dog-shelter-multi-page/.astro/content-modules.mjs +1 -0
- package/examples/{typography-presets → complete-sites/dog-shelter-multi-page}/.astro/content.d.ts +8 -0
- package/examples/complete-sites/dog-shelter-multi-page/.astro/data-store.json +1 -0
- package/examples/{typography-presets → complete-sites/dog-shelter-multi-page}/.astro/dev-local.json +2 -2
- package/examples/{typography-presets → complete-sites/dog-shelter-multi-page}/.astro/dev.json +2 -2
- package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.log +53 -0
- package/examples/complete-sites/dog-shelter-multi-page/.astro/settings.json +5 -0
- package/examples/complete-sites/dog-shelter-multi-page/README.md +9 -0
- package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/_metadata.json +13 -7
- package/examples/complete-sites/dog-shelter-multi-page/site/.norna/generated-images.json +18 -0
- package/examples/complete-sites/dog-shelter-multi-page/site/config.mjs +30 -0
- package/examples/complete-sites/dog-shelter-multi-page/site/content.md +12 -0
- package/examples/complete-sites/dog-shelter-multi-page/site/public/favicon.svg +10 -0
- package/examples/complete-sites/dog-shelter-multi-page/site/routes/010-dogs/images/dogs/rex.svg +19 -0
- package/examples/complete-sites/dog-shelter-multi-page/site/routes/010-dogs/images/dogs/rover.svg +19 -0
- package/examples/complete-sites/dog-shelter-multi-page/site/routes/010-dogs/route-content.md +30 -0
- package/examples/complete-sites/dog-shelter-multi-page/site/routes/020-adopt/route-content.md +12 -0
- package/examples/complete-sites/dog-shelter-multi-page/site/sitewide-content.md +6 -0
- package/examples/complete-sites/dog-shelter-multi-page/site/theme.md +25 -0
- package/examples/complete-sites/dog-shelter-single-page/README.md +9 -0
- package/examples/complete-sites/dog-shelter-single-page/site/.norna/generated-images.json +18 -0
- package/examples/complete-sites/dog-shelter-single-page/site/config.mjs +30 -0
- package/examples/complete-sites/dog-shelter-single-page/site/content.md +38 -0
- package/examples/complete-sites/dog-shelter-single-page/site/images/what-we-do/dog-house.svg +25 -0
- package/examples/complete-sites/dog-shelter-single-page/site/images/you-can-help/heart.svg +13 -0
- package/examples/complete-sites/dog-shelter-single-page/site/public/favicon.svg +10 -0
- package/examples/complete-sites/dog-shelter-single-page/site/public/robots.txt +2 -0
- package/examples/complete-sites/dog-shelter-single-page/site/sitewide-content.md +6 -0
- package/examples/complete-sites/dog-shelter-single-page/site/theme.md +25 -0
- package/examples/feature-demos/media-and-surfaces/.astro/collections/site.schema.json +59 -0
- package/examples/feature-demos/media-and-surfaces/.astro/collections/sitewide.schema.json +138 -0
- package/examples/feature-demos/media-and-surfaces/.astro/collections/theme.schema.json +755 -0
- package/examples/feature-demos/media-and-surfaces/.astro/content-modules.mjs +1 -0
- package/examples/feature-demos/media-and-surfaces/.astro/content.d.ts +183 -0
- package/examples/feature-demos/media-and-surfaces/.astro/data-store.json +1 -0
- package/examples/feature-demos/media-and-surfaces/.astro/dev.log +11 -0
- package/examples/feature-demos/media-and-surfaces/.astro/settings.json +5 -0
- package/examples/feature-demos/media-and-surfaces/.astro/types.d.ts +2 -0
- package/examples/feature-demos/media-and-surfaces/README.md +15 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/_metadata.json +74 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro-B3JHJyiv.js +434 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro-B3JHJyiv.js.map +1 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_axobject-query.js +2702 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/audit-C3SVr7dT.js +1534 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/audit-C3SVr7dT.js.map +1 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/highlight-BLtvqtzO.js +48 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/highlight-BLtvqtzO.js.map +1 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/icons-C92i61-2.js +42 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/icons-C92i61-2.js.map +1 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/settings-v5GgKks_.js +190 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/settings-v5GgKks_.js.map +1 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/toolbar-BFvJVp26.js +458 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/toolbar-BFvJVp26.js.map +1 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/ui-library-Cd35oAvo.js +1016 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/ui-library-Cd35oAvo.js.map +1 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/window-vo3A6TMn.js +32 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/window-vo3A6TMn.js.map +1 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/xray-DwhzGckb.js +111 -0
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/xray-DwhzGckb.js.map +1 -0
- package/examples/feature-demos/media-and-surfaces/site/.norna/generated-images.json +66 -0
- package/examples/feature-demos/media-and-surfaces/site/config.mjs +30 -0
- package/examples/feature-demos/media-and-surfaces/site/content.md +64 -0
- package/examples/feature-demos/media-and-surfaces/site/public/robots.txt +2 -0
- package/examples/feature-demos/media-and-surfaces/site/routes/010-media/images/cards/card-carousel.svg +11 -0
- package/examples/feature-demos/media-and-surfaces/site/routes/010-media/images/cards/card-stack.svg +9 -0
- package/examples/feature-demos/media-and-surfaces/site/routes/010-media/images/cards/card-surfaces.svg +8 -0
- package/examples/feature-demos/media-and-surfaces/site/routes/010-media/images/carousel/carousel-one.svg +8 -0
- package/examples/feature-demos/media-and-surfaces/site/routes/010-media/images/carousel/carousel-three.svg +13 -0
- package/examples/feature-demos/media-and-surfaces/site/routes/010-media/images/carousel/carousel-two.svg +8 -0
- package/examples/feature-demos/media-and-surfaces/site/routes/010-media/images/stack/stack-one.svg +8 -0
- package/examples/feature-demos/media-and-surfaces/site/routes/010-media/images/stack/stack-two.svg +9 -0
- package/examples/feature-demos/media-and-surfaces/site/routes/010-media/route-content.md +85 -0
- package/examples/feature-demos/media-and-surfaces/site/routes/010-media/theme.md +3 -0
- package/examples/feature-demos/media-and-surfaces/site/routes/020-surfaces/route-content.md +44 -0
- package/examples/feature-demos/media-and-surfaces/site/routes/020-surfaces/theme.md +8 -0
- package/examples/feature-demos/media-and-surfaces/site/sitewide-content.md +6 -0
- package/examples/feature-demos/media-and-surfaces/site/theme.md +3 -0
- package/examples/{typography-presets/.astro/collections/theme.schema.json → feature-demos/theme-presets/.astro/collections/routeTheme.schema.json} +143 -65
- package/examples/feature-demos/theme-presets/.astro/collections/site.schema.json +59 -0
- package/examples/feature-demos/theme-presets/.astro/collections/sitewide.schema.json +138 -0
- package/examples/feature-demos/theme-presets/.astro/collections/theme.schema.json +755 -0
- package/examples/feature-demos/theme-presets/.astro/content-assets.mjs +4 -0
- package/examples/feature-demos/theme-presets/.astro/content-modules.mjs +1 -0
- package/examples/feature-demos/theme-presets/.astro/content.d.ts +183 -0
- package/examples/feature-demos/theme-presets/.astro/data-store.json +1 -0
- package/examples/feature-demos/theme-presets/.astro/dev-local.json +8 -0
- package/examples/feature-demos/theme-presets/.astro/dev.json +15 -0
- package/examples/feature-demos/theme-presets/.astro/dev.log +127 -0
- package/examples/feature-demos/theme-presets/.astro/types.d.ts +2 -0
- package/examples/feature-demos/theme-presets/README.md +14 -0
- package/examples/feature-demos/theme-presets/node_modules/.vite/deps/embla-carousel.js +1389 -0
- package/examples/feature-demos/theme-presets/node_modules/.vite/deps/embla-carousel.js.map +1 -0
- package/examples/feature-demos/theme-presets/node_modules/.vite/deps/package.json +3 -0
- package/examples/feature-demos/theme-presets/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +4 -0
- package/examples/feature-demos/theme-presets/site/.norna/generated-images.json +290 -0
- package/examples/{typography-presets → feature-demos/theme-presets}/site/config.mjs +4 -16
- package/examples/feature-demos/theme-presets/site/content.md +17 -0
- package/examples/feature-demos/theme-presets/site/routes/010-portfolio/route-content.md +34 -0
- package/examples/feature-demos/theme-presets/site/routes/010-portfolio/theme.md +3 -0
- package/examples/feature-demos/theme-presets/site/routes/020-documentation/route-content.md +33 -0
- package/examples/feature-demos/theme-presets/site/routes/020-documentation/theme.md +3 -0
- package/examples/feature-demos/theme-presets/site/routes/030-project/route-content.md +34 -0
- package/examples/feature-demos/theme-presets/site/routes/030-project/theme.md +3 -0
- package/examples/feature-demos/theme-presets/site/routes/040-statement/route-content.md +34 -0
- package/examples/feature-demos/theme-presets/site/routes/040-statement/theme.md +3 -0
- package/examples/feature-demos/theme-presets/site/sitewide-content.md +14 -0
- package/examples/feature-demos/theme-presets/site/theme.md +3 -0
- package/fixtures/basic/site/config.mjs +0 -13
- package/fixtures/basic/site/sitewide-content.md +12 -0
- package/fixtures/basic/site/theme.md +2 -2
- package/fixtures/content-model-v2/site/content.md +2 -5
- package/fixtures/content-model-v2/site/theme.md +1 -3
- package/package.json +9 -4
- package/scripts/build-pages.mjs +71 -0
- package/scripts/check-config.mjs +23 -1
- package/scripts/export-theme-preset.mjs +31 -0
- package/scripts/init-site.mjs +1 -0
- package/scripts/lib/example-sites.mjs +34 -0
- package/scripts/lib/frontmatter-yaml.mjs +28 -0
- package/scripts/lib/logo-assets.mjs +35 -0
- package/scripts/lib/norna-markdown-blocks.mjs +166 -0
- package/scripts/lib/presentation.mjs +106 -47
- package/scripts/lib/project-config.mjs +68 -105
- package/scripts/lib/site-content.mjs +26 -118
- package/scripts/lib/site-paths.mjs +2 -0
- package/scripts/lib/sitewide-config.mjs +34 -0
- package/scripts/lib/theme-config.mjs +75 -8
- package/scripts/lib/theme-presets.mjs +209 -0
- package/scripts/lib/typography.mjs +20 -26
- package/scripts/show-typography.mjs +43 -149
- package/scripts/sync-content-sections.mjs +19 -17
- package/scripts/test-content-check.mjs +59 -27
- package/scripts/test-content-model-v2.mjs +154 -0
- package/scripts/test-engine-commands.mjs +4 -1
- package/scripts/test-examples.mjs +15 -0
- package/scripts/test-navigation.mjs +73 -45
- package/scripts/test-package-check.mjs +66 -24
- package/scripts/test-temporary-visibility.mjs +19 -2
- package/scripts/test-theme-presets.mjs +151 -0
- package/src/components/CardList.astro +5 -2
- package/src/components/GalleryGrid.astro +37 -16
- package/src/components/SiteBanners.astro +100 -0
- package/src/components/SiteNavigation.astro +48 -31
- package/src/components/SitePage.astro +55 -13
- package/src/components/SiteSection.astro +193 -35
- package/src/content.config.ts +80 -48
- package/src/layouts/BaseLayout.astro +64 -14
- package/src/lib/routeThemes.ts +61 -0
- package/src/lib/sectionContent.ts +235 -51
- package/src/lib/sitePublicAssets.ts +1 -0
- package/src/styles/global.css +284 -93
- package/starters/basic/.github/workflows/deploy.yml +1 -1
- package/starters/basic/README.md +7 -6
- package/starters/basic/package.json +1 -0
- package/starters/basic/site/config.mjs +0 -13
- package/starters/basic/site/content.md +1 -1
- package/starters/basic/site/sitewide-content.md +14 -0
- package/starters/basic/site/theme.md +13 -24
- package/starters/project/.github/workflows/deploy.yml +1 -1
- package/starters/project/README.md +10 -8
- package/starters/project/package.json +1 -0
- package/starters/project/site/config.mjs +0 -13
- package/starters/project/site/sitewide-content.md +14 -0
- package/starters/project/site/theme.md +3 -11
- package/examples/dog-gallery/site/.norna/generated-images.json +0 -226
- package/examples/dog-gallery/site/config.mjs +0 -67
- package/examples/dog-gallery/site/content.md +0 -128
- package/examples/dog-gallery/site/images/black-dogs/black-puppy-meadow.png +0 -0
- package/examples/dog-gallery/site/images/black-dogs/photo-of-a-black-dog.jpg +0 -0
- package/examples/dog-gallery/site/images/brown-dogs/brown-dog.jpg +0 -0
- package/examples/dog-gallery/site/images/brown-dogs/dog-accompanies-master.jpg +0 -0
- package/examples/dog-gallery/site/images/golden-dogs/golden-retriever.jpg +0 -0
- package/examples/dog-gallery/site/images/golden-dogs/toller-puppy.jpg +0 -0
- package/examples/dog-gallery/site/images/white-dogs/white-cute-dog.jpg +0 -0
- package/examples/dog-gallery/site/images/white-dogs/white-puppy-garden.png +0 -0
- package/examples/dog-gallery/site/public/favicon.svg +0 -7
- package/examples/dog-gallery/site/routes/010-dog-care/route-content.md +0 -38
- package/examples/dog-gallery/site/theme.md +0 -112
- package/examples/typography-presets/.astro/collections/site.schema.json +0 -776
- package/examples/typography-presets/.astro/data-store.json +0 -1
- package/examples/typography-presets/.astro/dev.log +0 -24
- package/examples/typography-presets/site/.norna/generated-images.json +0 -290
- package/examples/typography-presets/site/content.md +0 -134
- package/examples/typography-presets/site/theme.md +0 -43
- /package/examples/{typography-presets/.astro/content-modules.mjs → complete-sites/dog-shelter-multi-page/.astro/content-assets.mjs} +0 -0
- /package/examples/{typography-presets → complete-sites/dog-shelter-multi-page}/.astro/types.d.ts +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/astro-D_GIpJIE.js +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/astro-D_GIpJIE.js.map +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/astro_n_aria-query.js +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/astro_n_axobject-query.js +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/astro_n_html-escaper.js +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/audit-DM9Hkl5B.js +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +0 -0
- /package/examples/{typography-presets → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/embla-carousel.js +0 -0
- /package/examples/{typography-presets → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/embla-carousel.js.map +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/highlight-DnZ1ULOL.js +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/icons-RyebKi45.js +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/icons-RyebKi45.js.map +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/package.json +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/settings-DaU-gNnK.js +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/settings-DaU-gNnK.js.map +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/toolbar-63PA619H.js +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/toolbar-63PA619H.js.map +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/ui-library--bkcNSYz.js +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/window-jPkpKVcy.js +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/window-jPkpKVcy.js.map +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/xray-BXCHpEJP.js +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/node_modules/.vite/deps/xray-BXCHpEJP.js.map +0 -0
- /package/examples/{dog-gallery → complete-sites/dog-shelter-multi-page}/site/public/robots.txt +0 -0
- /package/examples/{typography-presets → feature-demos/media-and-surfaces}/.astro/content-assets.mjs +0 -0
- /package/examples/{typography-presets → feature-demos/media-and-surfaces}/node_modules/.vite/deps/package.json +0 -0
- /package/examples/{typography-presets → feature-demos/media-and-surfaces}/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/.astro/settings.json +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/_metadata.json +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/astro-D_GIpJIE.js +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/astro-D_GIpJIE.js.map +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/astro_n_aria-query.js +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/astro_n_aria-query.js.map +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/astro_n_axobject-query.js +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/astro_n_axobject-query.js.map +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/astro_n_html-escaper.js +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/astro_n_html-escaper.js.map +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/audit-DM9Hkl5B.js +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/highlight-DnZ1ULOL.js +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/icons-RyebKi45.js +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/icons-RyebKi45.js.map +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/settings-DaU-gNnK.js +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/settings-DaU-gNnK.js.map +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/toolbar-63PA619H.js +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/toolbar-63PA619H.js.map +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/ui-library--bkcNSYz.js +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/window-jPkpKVcy.js +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/window-jPkpKVcy.js.map +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/xray-BXCHpEJP.js +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/node_modules/.vite/deps/xray-BXCHpEJP.js.map +0 -0
- /package/examples/{typography-presets → feature-demos/theme-presets}/site/public/robots.txt +0 -0
- /package/examples/{typography-presets/site/images/quiet-gallery → feature-demos/theme-presets/site/routes/010-portfolio/images/portfolio}/puppy-portrait.jpg +0 -0
- /package/examples/{typography-presets/site/images/quiet-gallery → feature-demos/theme-presets/site/routes/010-portfolio/images/portfolio}/shepherd-portrait.jpg +0 -0
- /package/examples/{typography-presets/site/images/quiet-gallery → feature-demos/theme-presets/site/routes/010-portfolio/images/portfolio}/white-puppy.jpg +0 -0
- /package/examples/{typography-presets/site/images/compact-gallery → feature-demos/theme-presets/site/routes/020-documentation/images/documentation}/black-schnauzer.jpg +0 -0
- /package/examples/{typography-presets/site/images/compact-gallery → feature-demos/theme-presets/site/routes/020-documentation/images/documentation}/retriever-portrait.jpg +0 -0
- /package/examples/{typography-presets/site/images/compact-gallery → feature-demos/theme-presets/site/routes/020-documentation/images/documentation}/schnauzer-portrait.jpg +0 -0
- /package/examples/{typography-presets/site/images/text-forward → feature-demos/theme-presets/site/routes/030-project/images/project}/boxer-portrait.jpg +0 -0
- /package/examples/{typography-presets/site/images/text-forward → feature-demos/theme-presets/site/routes/030-project/images/project}/grass-puppy.jpg +0 -0
- /package/examples/{typography-presets/site/images/text-forward → feature-demos/theme-presets/site/routes/030-project/images/project}/grey-street-dog.jpg +0 -0
- /package/examples/{typography-presets/site → feature-demos/theme-presets/site/routes/040-statement}/images/statement/black-pedigree.jpg +0 -0
- /package/examples/{typography-presets/site → feature-demos/theme-presets/site/routes/040-statement}/images/statement/sheepdog-grass.jpg +0 -0
- /package/examples/{typography-presets/site → feature-demos/theme-presets/site/routes/040-statement}/images/statement/sheepdog-wide.jpg +0 -0
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Project
|
|
3
|
+
description: A complete theme preset for project and product websites.
|
|
4
|
+
navigation:
|
|
5
|
+
label: Project
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Project {#project}
|
|
9
|
+
|
|
10
|
+
This route uses the complete `project` preset without overrides. It provides a
|
|
11
|
+
compact, neutral presentation for project overviews, feature descriptions and
|
|
12
|
+
practical documentation.
|
|
13
|
+
|
|
14
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed posuere
|
|
15
|
+
consectetur est at lobortis. Cras mattis consectetur purus sit amet fermentum.
|
|
16
|
+
Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
|
|
17
|
+
|
|
18
|
+
### Practical explanations
|
|
19
|
+
|
|
20
|
+
Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Donec sed odio
|
|
21
|
+
dui. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget
|
|
22
|
+
lacinia odio sem nec elit.
|
|
23
|
+
|
|
24
|
+
```norna-image-stack
|
|
25
|
+
- image: grass-puppy.jpg
|
|
26
|
+
alt: A puppy standing in green grass.
|
|
27
|
+
caption: Puppy in grass. Photo by Bicanski, CC0, via Pixnio.
|
|
28
|
+
- image: grey-street-dog.jpg
|
|
29
|
+
alt: A grey dog standing on a paved street.
|
|
30
|
+
caption: Grey dog on pavement. Photo by Bicanski, CC0, via Pixnio.
|
|
31
|
+
- image: boxer-portrait.jpg
|
|
32
|
+
alt: A brown boxer-type dog looking at the camera.
|
|
33
|
+
caption: Brown dog portrait. Photo by Bicanski, CC0, via Pixnio.
|
|
34
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Statement
|
|
3
|
+
description: A stronger typography preset for short declarative content.
|
|
4
|
+
navigation:
|
|
5
|
+
label: Statement
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Statement {#statement}
|
|
9
|
+
|
|
10
|
+
This route uses the complete `statement` preset without overrides. Its airy
|
|
11
|
+
layout, stronger typography and alternating surfaces suit short, declarative
|
|
12
|
+
content with a more editorial voice.
|
|
13
|
+
|
|
14
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec id elit non mi
|
|
15
|
+
porta gravida at eget metus. Integer posuere erat a ante venenatis dapibus
|
|
16
|
+
posuere velit aliquet.
|
|
17
|
+
|
|
18
|
+
### Short emphasis
|
|
19
|
+
|
|
20
|
+
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Aenean
|
|
21
|
+
lacinia bibendum nulla sed consectetur. Maecenas sed diam eget risus varius
|
|
22
|
+
blandit sit amet non magna.
|
|
23
|
+
|
|
24
|
+
```norna-image-stack
|
|
25
|
+
- image: sheepdog-grass.jpg
|
|
26
|
+
alt: A sheepdog puppy sitting in green grass.
|
|
27
|
+
caption: Sheepdog puppy in grass. Photo by Bicanski, CC0, via Pixnio.
|
|
28
|
+
- image: sheepdog-wide.jpg
|
|
29
|
+
alt: A sheepdog puppy lying outdoors.
|
|
30
|
+
caption: Sheepdog puppy outdoors. Photo by Bicanski, CC0, via Pixnio.
|
|
31
|
+
- image: black-pedigree.jpg
|
|
32
|
+
alt: A black dog standing in grass.
|
|
33
|
+
caption: Black pedigree dog portrait. Photo by Bicanski, CC0, via Pixnio.
|
|
34
|
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
navigation:
|
|
3
|
+
brand: Theme Presets
|
|
4
|
+
footer:
|
|
5
|
+
copyrightMessage: Dog photos by Bicanski, CC0, via Pixnio.
|
|
6
|
+
buildInfo:
|
|
7
|
+
enabled: true
|
|
8
|
+
text: Built
|
|
9
|
+
dateTimeFormat:
|
|
10
|
+
locale: en-GB
|
|
11
|
+
timeZone: UTC
|
|
12
|
+
dateStyle: short
|
|
13
|
+
timeStyle: short
|
|
14
|
+
---
|
|
@@ -19,19 +19,6 @@ export default {
|
|
|
19
19
|
gallery: 'Images',
|
|
20
20
|
},
|
|
21
21
|
},
|
|
22
|
-
footer: {
|
|
23
|
-
copyrightMessage: '(c) Fixture Artist.',
|
|
24
|
-
buildInfo: {
|
|
25
|
-
enabled: true,
|
|
26
|
-
text: 'Built',
|
|
27
|
-
dateTimeFormat: {
|
|
28
|
-
locale: 'en-GB',
|
|
29
|
-
timeZone: 'UTC',
|
|
30
|
-
dateStyle: 'short',
|
|
31
|
-
timeStyle: 'short',
|
|
32
|
-
},
|
|
33
|
-
},
|
|
34
|
-
},
|
|
35
22
|
github: {
|
|
36
23
|
repo: 'owner/fixture-site',
|
|
37
24
|
branch: 'main',
|
|
@@ -2,10 +2,7 @@
|
|
|
2
2
|
title: Content Model v2 Fixture
|
|
3
3
|
description: Internal fixture for Markdown-authoritative Norna content.
|
|
4
4
|
sections:
|
|
5
|
-
intro:
|
|
6
|
-
presentation:
|
|
7
|
-
typography:
|
|
8
|
-
preset: statement
|
|
5
|
+
intro: {}
|
|
9
6
|
timed:
|
|
10
7
|
visible:
|
|
11
8
|
from: "2026-01-01"
|
|
@@ -15,7 +12,7 @@ sections:
|
|
|
15
12
|
## Intro {#intro}
|
|
16
13
|
|
|
17
14
|
This fixture validates that Markdown defines sections and that section metadata
|
|
18
|
-
is optional. It also checks
|
|
15
|
+
is optional. It also checks **inline emphasis**.
|
|
19
16
|
|
|
20
17
|
```norna-image-stack
|
|
21
18
|
- image: hero.jpg
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@janga/norna",
|
|
3
3
|
"description": "A command-line publishing workflow for simple static sites.",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.7.
|
|
5
|
+
"version": "0.7.11",
|
|
6
6
|
"license": "GPL-3.0-only",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -51,22 +51,26 @@
|
|
|
51
51
|
"config:check": "node bin/norna.mjs config:check",
|
|
52
52
|
"content:check": "node bin/norna.mjs content:check",
|
|
53
53
|
"content:sync": "node bin/norna.mjs content:sync",
|
|
54
|
+
"theme:export": "node bin/norna.mjs theme:export",
|
|
54
55
|
"typography:presets": "node bin/norna.mjs typography presets",
|
|
55
56
|
"typography:show": "node bin/norna.mjs typography show",
|
|
56
57
|
"site:public": "node bin/norna.mjs site:public",
|
|
57
58
|
"images": "node bin/norna.mjs images",
|
|
58
59
|
"build": "node bin/norna.mjs build",
|
|
60
|
+
"build:pages": "node scripts/build-pages.mjs",
|
|
59
61
|
"build:local": "node bin/norna.mjs build:local",
|
|
60
62
|
"deploy": "node bin/norna.mjs deploy",
|
|
61
63
|
"deploy:commit": "node bin/norna.mjs deploy:commit",
|
|
62
64
|
"deploy:watch": "node bin/norna.mjs deploy:watch",
|
|
63
65
|
"doctor": "node bin/norna.mjs doctor",
|
|
64
|
-
"demo:build": "node bin/norna.mjs --site-dir examples/
|
|
65
|
-
"test": "npm run test:route-model && npm run test:content-check && npm run test:content-model-v2 && npm run test:site-public && npm run test:cli-discovery && npm run test:engine-commands && npm run test:ci-lockfile && npm run test:temporary-visibility && npm run test:fixture:build && npm run package:check",
|
|
66
|
+
"demo:build": "node bin/norna.mjs --site-dir examples/feature-demos/media-and-surfaces/site build",
|
|
67
|
+
"test": "npm run test:route-model && npm run test:content-check && npm run test:content-model-v2 && npm run test:theme-presets && npm run test:site-public && npm run test:cli-discovery && npm run test:engine-commands && npm run test:ci-lockfile && npm run test:temporary-visibility && npm run test:fixture:build && npm run test:examples && npm run package:check",
|
|
66
68
|
"test:fixture:build": "node bin/norna.mjs --site-dir fixtures/basic/site build",
|
|
69
|
+
"test:examples": "node scripts/test-examples.mjs",
|
|
67
70
|
"test:route-model": "node scripts/test-route-model.mjs",
|
|
68
71
|
"test:content-check": "node scripts/test-content-check.mjs",
|
|
69
72
|
"test:content-model-v2": "node scripts/test-content-model-v2.mjs",
|
|
73
|
+
"test:theme-presets": "node scripts/test-theme-presets.mjs",
|
|
70
74
|
"test:site-public": "node scripts/test-site-public.mjs",
|
|
71
75
|
"test:cli-discovery": "node scripts/test-cli-discovery.mjs",
|
|
72
76
|
"test:engine-commands": "node scripts/test-engine-commands.mjs",
|
|
@@ -85,7 +89,8 @@
|
|
|
85
89
|
},
|
|
86
90
|
"dependencies": {
|
|
87
91
|
"astro": "^7.1.3",
|
|
88
|
-
"embla-carousel": "^8.6.0"
|
|
92
|
+
"embla-carousel": "^8.6.0",
|
|
93
|
+
"satteri": "^0.9.1"
|
|
89
94
|
},
|
|
90
95
|
"devDependencies": {
|
|
91
96
|
"@playwright/test": "^1.61.1"
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { cp, mkdir, mkdtemp, readFile, rm } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import documentationConfig from '../site/config.mjs';
|
|
6
|
+
import { getExampleSites } from './lib/example-sites.mjs';
|
|
7
|
+
import { runInherit } from './lib/run-command.mjs';
|
|
8
|
+
|
|
9
|
+
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
10
|
+
const cliPath = path.join(root, 'bin', 'norna.mjs');
|
|
11
|
+
const distDirectory = path.join(root, 'dist');
|
|
12
|
+
const temporaryDirectory = await mkdtemp(path.join(tmpdir(), 'norna-pages-'));
|
|
13
|
+
const artifactDirectory = path.join(temporaryDirectory, 'artifact');
|
|
14
|
+
const documentationUrl = new URL(documentationConfig.site.url);
|
|
15
|
+
const documentationBasePath = documentationConfig.site.basePath;
|
|
16
|
+
const examples = await getExampleSites(root);
|
|
17
|
+
let artifactStarted = false;
|
|
18
|
+
|
|
19
|
+
const buildSite = (siteLabel, env = process.env) => runInherit(
|
|
20
|
+
process.execPath,
|
|
21
|
+
[cliPath, ...(siteLabel ? ['--site-dir', siteLabel] : []), 'build'],
|
|
22
|
+
{ cwd: root, env },
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
console.log('Building documentation site');
|
|
27
|
+
await buildSite();
|
|
28
|
+
await cp(distDirectory, artifactDirectory, { recursive: true });
|
|
29
|
+
artifactStarted = true;
|
|
30
|
+
|
|
31
|
+
for (const example of examples) {
|
|
32
|
+
const relativePublicPath = `examples/${example.category}/${example.name}/`;
|
|
33
|
+
const basePath = `${documentationBasePath.replace(/\/$/, '')}/${relativePublicPath}`;
|
|
34
|
+
const siteUrl = new URL(relativePublicPath, documentationUrl).href;
|
|
35
|
+
|
|
36
|
+
console.log(`\nBuilding ${example.siteLabel} for ${siteUrl}`);
|
|
37
|
+
await buildSite(example.siteLabel, {
|
|
38
|
+
...process.env,
|
|
39
|
+
NORNA_EXAMPLE_BASE_PATH: basePath,
|
|
40
|
+
NORNA_EXAMPLE_SITE_URL: siteUrl,
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const destination = path.join(artifactDirectory, relativePublicPath);
|
|
44
|
+
await mkdir(path.dirname(destination), { recursive: true });
|
|
45
|
+
await cp(distDirectory, destination, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
} finally {
|
|
48
|
+
if (artifactStarted) {
|
|
49
|
+
await rm(distDirectory, { recursive: true, force: true });
|
|
50
|
+
await cp(artifactDirectory, distDirectory, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
await rm(temporaryDirectory, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const documentationExamplesHtml = await readFile(path.join(distDirectory, 'examples', 'index.html'), 'utf8');
|
|
56
|
+
|
|
57
|
+
for (const example of examples) {
|
|
58
|
+
const relativePublicPath = `examples/${example.category}/${example.name}/`;
|
|
59
|
+
const basePath = `${documentationBasePath.replace(/\/$/, '')}/${relativePublicPath}`;
|
|
60
|
+
const siteUrl = new URL(relativePublicPath, documentationUrl).href;
|
|
61
|
+
const exampleHtml = await readFile(path.join(distDirectory, relativePublicPath, 'index.html'), 'utf8');
|
|
62
|
+
|
|
63
|
+
if (!documentationExamplesHtml.includes(`href="${siteUrl}"`)) {
|
|
64
|
+
throw new Error(`Documentation is missing the rendered example link ${siteUrl}.`);
|
|
65
|
+
}
|
|
66
|
+
if (!exampleHtml.includes(`href="${basePath}`) && !exampleHtml.includes(`src="${basePath}`)) {
|
|
67
|
+
throw new Error(`Rendered example ${relativePublicPath} does not use its deployment base path ${basePath}.`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log(`\nok - assembled documentation and ${examples.length} rendered examples in dist/`);
|
package/scripts/check-config.mjs
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import { siteConfigLabel } from './lib/site-paths.mjs';
|
|
1
|
+
import { siteConfigLabel, sitePublicLabel } from './lib/site-paths.mjs';
|
|
2
|
+
import { getLogoAssets } from './lib/logo-assets.mjs';
|
|
3
|
+
import { readSitewideConfig } from './lib/sitewide-config.mjs';
|
|
4
|
+
import { readThemeConfig, validateRouteThemeFiles } from './lib/theme-config.mjs';
|
|
2
5
|
|
|
3
6
|
const formatErrorMessage = (error) => {
|
|
4
7
|
if (error instanceof SyntaxError) {
|
|
@@ -17,10 +20,29 @@ const formatErrorMessage = (error) => {
|
|
|
17
20
|
|
|
18
21
|
try {
|
|
19
22
|
const { projectConfig } = await import('./lib/project-config.mjs');
|
|
23
|
+
const themeConfig = await readThemeConfig();
|
|
24
|
+
const sitewideConfig = await readSitewideConfig();
|
|
25
|
+
await validateRouteThemeFiles();
|
|
26
|
+
const logoAssets = getLogoAssets();
|
|
27
|
+
|
|
28
|
+
if (logoAssets.length > 1) {
|
|
29
|
+
throw new Error([
|
|
30
|
+
`Found multiple logo files in ${sitePublicLabel}. Keep exactly one of logo.svg, logo.png, logo.jpg, or logo.jpeg.`,
|
|
31
|
+
...logoAssets.map(({ filename }) => `- ${sitePublicLabel}/${filename}`),
|
|
32
|
+
].join('\n'));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (logoAssets.length === 0) {
|
|
36
|
+
console.warn(`Warning: No logo file found in ${sitePublicLabel}. Norna will use sitewide navigation.brand or the homepage title as the navigation label.`);
|
|
37
|
+
console.warn(`Add exactly one of ${sitePublicLabel}/logo.svg, logo.png, logo.jpg, or logo.jpeg when the site should have a logo.`);
|
|
38
|
+
} else if (sitewideConfig.navigation?.brand) {
|
|
39
|
+
console.warn('Warning: Both sitewide navigation.brand and a logo file are configured. The logo is used; navigation.brand is only the text fallback.');
|
|
40
|
+
}
|
|
20
41
|
|
|
21
42
|
console.log('Config check passed.');
|
|
22
43
|
console.log(`Site URL: ${projectConfig.site.url}`);
|
|
23
44
|
console.log(`Base path: ${projectConfig.site.basePath}`);
|
|
45
|
+
console.log(`Theme preset: ${themeConfig.preset ?? '(none)'}`);
|
|
24
46
|
console.log(`Page width: ${projectConfig.layout.pageWidth}`);
|
|
25
47
|
console.log(`Gutter: desktop ${projectConfig.layout.gutter.desktop}, mobile ${projectConfig.layout.gutter.mobile}`);
|
|
26
48
|
console.log(`Layout density: ${projectConfig.layout.density}`);
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { writeFile } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import {
|
|
4
|
+
renderThemePresetReference,
|
|
5
|
+
themePresetNames,
|
|
6
|
+
} from './lib/theme-presets.mjs';
|
|
7
|
+
import { siteDir, siteThemeLabel } from './lib/site-paths.mjs';
|
|
8
|
+
|
|
9
|
+
const [presetName, ...extraArgs] = process.argv.slice(2);
|
|
10
|
+
|
|
11
|
+
if (!presetName || extraArgs.length > 0) {
|
|
12
|
+
throw new Error(`Usage: norna theme:export <preset>\nAvailable presets: ${themePresetNames.join(', ')}`);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const filename = `orig-${presetName}-theme.md`;
|
|
16
|
+
const outputPath = path.join(siteDir, filename);
|
|
17
|
+
const outputLabel = path.posix.join(path.posix.dirname(siteThemeLabel), filename);
|
|
18
|
+
const source = renderThemePresetReference(presetName, siteThemeLabel);
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
await writeFile(outputPath, source, { encoding: 'utf8', flag: 'wx' });
|
|
22
|
+
} catch (error) {
|
|
23
|
+
if (error?.code === 'EEXIST') {
|
|
24
|
+
throw new Error(`${outputLabel} already exists. Norna will not overwrite it.`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.log(`Exported the ${presetName} theme preset to ${outputLabel}.`);
|
|
31
|
+
console.log(`Norna still reads ${siteThemeLabel}; use the exported file as an override reference.`);
|
package/scripts/init-site.mjs
CHANGED
|
@@ -133,6 +133,7 @@ const galleryScripts = {
|
|
|
133
133
|
'norna:config:check': cliCommand('config:check'),
|
|
134
134
|
'norna:content:check': cliCommand('content:check'),
|
|
135
135
|
'norna:sync': cliCommand('content:sync'),
|
|
136
|
+
'norna:theme:export': cliCommand('theme:export'),
|
|
136
137
|
'norna:typography:presets': cliCommand('typography presets'),
|
|
137
138
|
'norna:typography:show': cliCommand('typography show'),
|
|
138
139
|
'norna:public': cliCommand('site:public'),
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { readdir } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
export const exampleCategories = ['complete-sites', 'feature-demos'];
|
|
6
|
+
|
|
7
|
+
export const getExampleSites = async (root) => {
|
|
8
|
+
const examples = [];
|
|
9
|
+
|
|
10
|
+
for (const category of exampleCategories) {
|
|
11
|
+
const categoryDirectory = path.join(root, 'examples', category);
|
|
12
|
+
const entries = await readdir(categoryDirectory, { withFileTypes: true });
|
|
13
|
+
|
|
14
|
+
for (const entry of entries.filter((candidate) => candidate.isDirectory()).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
15
|
+
const siteDirectory = path.join(categoryDirectory, entry.name, 'site');
|
|
16
|
+
if (!existsSync(path.join(siteDirectory, 'content.md'))) {
|
|
17
|
+
throw new Error(`Example ${path.relative(root, siteDirectory)} is missing content.md.`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
examples.push({
|
|
21
|
+
category,
|
|
22
|
+
name: entry.name,
|
|
23
|
+
siteDirectory,
|
|
24
|
+
siteLabel: path.relative(root, siteDirectory),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (examples.length === 0) {
|
|
30
|
+
throw new Error('No example sites were found.');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return examples;
|
|
34
|
+
};
|
|
@@ -20,9 +20,37 @@ const stripInlineComment = (value) => {
|
|
|
20
20
|
return value;
|
|
21
21
|
};
|
|
22
22
|
|
|
23
|
+
const splitInlineList = (value) => {
|
|
24
|
+
const entries = [];
|
|
25
|
+
let entryStart = 0;
|
|
26
|
+
let quote = null;
|
|
27
|
+
|
|
28
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
29
|
+
const character = value[index];
|
|
30
|
+
const previous = value[index - 1];
|
|
31
|
+
|
|
32
|
+
if ((character === '"' || character === "'") && previous !== '\\') {
|
|
33
|
+
quote = quote === character ? null : quote ?? character;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (character === ',' && quote === null) {
|
|
38
|
+
entries.push(value.slice(entryStart, index).trim());
|
|
39
|
+
entryStart = index + 1;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
entries.push(value.slice(entryStart).trim());
|
|
44
|
+
return entries.filter(Boolean);
|
|
45
|
+
};
|
|
46
|
+
|
|
23
47
|
const parseScalar = (rawValue) => {
|
|
24
48
|
const value = stripInlineComment(rawValue.trim());
|
|
25
49
|
|
|
50
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
51
|
+
const listValue = value.slice(1, -1).trim();
|
|
52
|
+
return listValue ? splitInlineList(listValue).map(parseScalar) : [];
|
|
53
|
+
}
|
|
26
54
|
if (value === 'true') return true;
|
|
27
55
|
if (value === 'false') return false;
|
|
28
56
|
if (/^-?\d+(?:\.\d+)?$/.test(value)) return Number(value);
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { readdirSync } from 'node:fs';
|
|
2
|
+
import { sitePublicDir, sitePublicLabel } from './site-paths.mjs';
|
|
3
|
+
|
|
4
|
+
const logoFilePattern = /^logo\.(svg|png|jpe?g)$/i;
|
|
5
|
+
|
|
6
|
+
export const getLogoAssets = () => {
|
|
7
|
+
let entries;
|
|
8
|
+
|
|
9
|
+
try {
|
|
10
|
+
entries = readdirSync(sitePublicDir, { withFileTypes: true });
|
|
11
|
+
} catch (error) {
|
|
12
|
+
if (error?.code === 'ENOENT') return [];
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return entries
|
|
17
|
+
.filter((entry) => entry.isFile() && logoFilePattern.test(entry.name))
|
|
18
|
+
.map((entry) => ({
|
|
19
|
+
filename: entry.name,
|
|
20
|
+
href: `/${entry.name}`,
|
|
21
|
+
}));
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const getLogoAsset = () => {
|
|
25
|
+
const assets = getLogoAssets();
|
|
26
|
+
|
|
27
|
+
if (assets.length > 1) {
|
|
28
|
+
throw new Error([
|
|
29
|
+
`Found multiple logo files in ${sitePublicLabel}. Keep exactly one of logo.svg, logo.png, logo.jpg, or logo.jpeg.`,
|
|
30
|
+
...assets.map(({ filename }) => `- ${sitePublicLabel}/${filename}`),
|
|
31
|
+
].join('\n'));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return assets[0] ?? null;
|
|
35
|
+
};
|
|
@@ -588,6 +588,172 @@ export const splitNornaMarkdownBlockMarkers = (html, options = {}) => {
|
|
|
588
588
|
|
|
589
589
|
const normalizeBlockSource = (source) => source.replace(/\r\n?/g, '\n').trim();
|
|
590
590
|
|
|
591
|
+
const inlineNoteReferenceRegex = /\{note-ref\}/g;
|
|
592
|
+
const inlineNoteDeclarationRegex = /^\s*\{note:\s*(.*)\}\s*$/;
|
|
593
|
+
const inlineNoteDeclarationStartRegex = /\{note(?:\s*:|\s*\})/;
|
|
594
|
+
|
|
595
|
+
const maskInlineCodeAndComments = (source) => source.replace(
|
|
596
|
+
/<!--[\s\S]*?-->|(`+)([\s\S]*?)\1/g,
|
|
597
|
+
(match) => match.replace(/[^\r\n]/g, ' '),
|
|
598
|
+
);
|
|
599
|
+
|
|
600
|
+
const countInlineNoteReferences = (line) => Array.from(line.matchAll(inlineNoteReferenceRegex)).length;
|
|
601
|
+
|
|
602
|
+
const formatInlineNoteError = (message, options, line) => ({
|
|
603
|
+
blockType: 'inline-note',
|
|
604
|
+
line,
|
|
605
|
+
message: failMessage(message, { ...options, line }),
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
const getInlineNoteErrorMessage = () => 'Write the note on its own line as "{note: Your explanatory text.}".';
|
|
609
|
+
|
|
610
|
+
/**
|
|
611
|
+
* Finds the deliberately small inline-note syntax while ignoring fenced and
|
|
612
|
+
* inline code. Notes are paired by position, so no user-authored IDs are
|
|
613
|
+
* needed. The caller decides whether diagnostics should be fatal.
|
|
614
|
+
*/
|
|
615
|
+
export const extractInlineNoteDiagnostics = (markdown, options = {}) => {
|
|
616
|
+
const lines = normalizeLines(markdown);
|
|
617
|
+
const maskedLines = maskInlineCodeAndComments(maskFencedCodeBlocks(markdown)).split('\n');
|
|
618
|
+
const notes = [];
|
|
619
|
+
const errors = [];
|
|
620
|
+
let paragraph = null;
|
|
621
|
+
let pendingParagraph = null;
|
|
622
|
+
let noteJustSeen = false;
|
|
623
|
+
|
|
624
|
+
const addError = (message, line) => errors.push(formatInlineNoteError(message, options, line));
|
|
625
|
+
|
|
626
|
+
const finishParagraph = () => {
|
|
627
|
+
if (!paragraph) return;
|
|
628
|
+
if (paragraph.referenceCount > 0) {
|
|
629
|
+
pendingParagraph = paragraph;
|
|
630
|
+
}
|
|
631
|
+
paragraph = null;
|
|
632
|
+
};
|
|
633
|
+
|
|
634
|
+
const reportMissingNote = () => {
|
|
635
|
+
if (!pendingParagraph) return;
|
|
636
|
+
addError(
|
|
637
|
+
`Paragraph on line ${pendingParagraph.referenceLine} contains "{note-ref}" but has no following "{note: ...}".`,
|
|
638
|
+
pendingParagraph.referenceLine,
|
|
639
|
+
);
|
|
640
|
+
pendingParagraph = null;
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
const attachNote = (noteMatch, lineNumber) => {
|
|
644
|
+
const text = noteMatch[1].trim();
|
|
645
|
+
if (!text) {
|
|
646
|
+
addError(`The note on line ${lineNumber} is empty. ${getInlineNoteErrorMessage()}`, lineNumber);
|
|
647
|
+
pendingParagraph = null;
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
if (/!\[[^\]]*\]\([^)]*\)|<img\b/i.test(text)) {
|
|
652
|
+
addError('Inline notes cannot contain images. Use a Norna image block with a caption instead.', lineNumber);
|
|
653
|
+
pendingParagraph = null;
|
|
654
|
+
return;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
notes.push({
|
|
658
|
+
markdown: text,
|
|
659
|
+
referenceLine: pendingParagraph.referenceLine,
|
|
660
|
+
noteLine: lineNumber,
|
|
661
|
+
});
|
|
662
|
+
pendingParagraph = null;
|
|
663
|
+
noteJustSeen = true;
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
667
|
+
const line = lines[index] ?? '';
|
|
668
|
+
const maskedLine = maskedLines[index] ?? line;
|
|
669
|
+
const lineNumber = (options.lineOffset ?? 0) + index + 1;
|
|
670
|
+
const fence = getFenceInfo(line);
|
|
671
|
+
|
|
672
|
+
if (fence && fence.length >= 3) {
|
|
673
|
+
finishParagraph();
|
|
674
|
+
reportMissingNote();
|
|
675
|
+
let closingIndex = -1;
|
|
676
|
+
for (let candidateIndex = index + 1; candidateIndex < lines.length; candidateIndex += 1) {
|
|
677
|
+
if (getFenceCloseInfo(lines[candidateIndex], fence)) {
|
|
678
|
+
closingIndex = candidateIndex;
|
|
679
|
+
break;
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
index = closingIndex === -1 ? lines.length : closingIndex;
|
|
683
|
+
noteJustSeen = false;
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
const trimmed = line.trim();
|
|
688
|
+
const maskedTrimmed = maskedLine.trim();
|
|
689
|
+
const noteMatch = trimmed.match(inlineNoteDeclarationRegex);
|
|
690
|
+
const looksLikeNoteDeclaration = inlineNoteDeclarationStartRegex.test(maskedTrimmed);
|
|
691
|
+
const referenceCount = countInlineNoteReferences(maskedLine);
|
|
692
|
+
const isHeading = /^#{1,6}(?:\s|$)/.test(trimmed);
|
|
693
|
+
|
|
694
|
+
if (!trimmed) {
|
|
695
|
+
finishParagraph();
|
|
696
|
+
noteJustSeen = false;
|
|
697
|
+
continue;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
if (pendingParagraph) {
|
|
701
|
+
if (noteMatch) {
|
|
702
|
+
attachNote(noteMatch, lineNumber);
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
if (looksLikeNoteDeclaration) {
|
|
707
|
+
addError(`Invalid note syntax on line ${lineNumber}. ${getInlineNoteErrorMessage()}`, lineNumber);
|
|
708
|
+
pendingParagraph = null;
|
|
709
|
+
} else {
|
|
710
|
+
reportMissingNote();
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
if (noteJustSeen) {
|
|
715
|
+
addError(`Add a blank line after the note on the previous line before starting more text.`, lineNumber);
|
|
716
|
+
noteJustSeen = false;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
if (noteMatch || looksLikeNoteDeclaration) {
|
|
720
|
+
addError(
|
|
721
|
+
noteMatch
|
|
722
|
+
? `A "{note: ...}" requires a preceding paragraph containing "{note-ref}".`
|
|
723
|
+
: `Invalid note syntax on line ${lineNumber}. ${getInlineNoteErrorMessage()}`,
|
|
724
|
+
lineNumber,
|
|
725
|
+
);
|
|
726
|
+
continue;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if (isHeading) {
|
|
730
|
+
if (referenceCount > 0) {
|
|
731
|
+
addError('Place "{note-ref}" in a paragraph, not in a heading.', lineNumber);
|
|
732
|
+
}
|
|
733
|
+
finishParagraph();
|
|
734
|
+
reportMissingNote();
|
|
735
|
+
continue;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
if (!paragraph) {
|
|
739
|
+
paragraph = {
|
|
740
|
+
referenceCount: 0,
|
|
741
|
+
referenceLine: lineNumber,
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
paragraph.referenceCount += referenceCount;
|
|
746
|
+
if (referenceCount > 0 && paragraph.referenceCount > 1) {
|
|
747
|
+
addError('A paragraph may contain only one "{note-ref}".', lineNumber);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
finishParagraph();
|
|
752
|
+
reportMissingNote();
|
|
753
|
+
|
|
754
|
+
return { notes, errors };
|
|
755
|
+
};
|
|
756
|
+
|
|
591
757
|
export const splitNornaRenderedCodeBlocks = (html, rawBlocks, options = {}) => {
|
|
592
758
|
const blocks = [];
|
|
593
759
|
const renderedCodeRegex = /<pre\b[^>]*>\s*<code\b[^>]*>([\s\S]*?)<\/code>\s*<\/pre>/g;
|