@janga/norna 0.7.16 → 0.7.17
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 +6 -6
- package/astro.config.mjs +35 -3
- package/docs/README.md +10 -10
- package/docs/commands.md +2 -2
- package/docs/content.md +82 -102
- package/docs/design/navigation-and-theme-plan.md +115 -0
- package/docs/design/norna-diagram-design.md +9 -9
- package/docs/design/site-examples-structure.md +9 -9
- package/docs/engine-development.md +9 -9
- package/docs/getting-started.md +20 -15
- package/docs/images-and-metadata.md +10 -9
- package/docs/pages.md +134 -0
- package/docs/public-files.md +49 -36
- package/docs/publishing.md +19 -3
- package/docs/site-files.md +29 -28
- package/docs/sitewide-content.md +20 -36
- package/docs/theme.md +20 -20
- package/docs/typography.md +10 -10
- package/examples/README.md +2 -2
- package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/site.schema.json +17 -41
- package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/sitewide.schema.json +26 -67
- package/examples/complete-sites/dog-shelter-multi-page/.astro/collections/theme.schema.json +185 -88
- package/examples/complete-sites/dog-shelter-multi-page/.astro/data-store.json +1 -1
- package/examples/complete-sites/dog-shelter-multi-page/.astro/dev-local.json +4 -4
- package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.json +5 -5
- package/examples/complete-sites/dog-shelter-multi-page/.astro/dev.log +33 -23
- package/examples/complete-sites/dog-shelter-multi-page/README.md +1 -1
- package/examples/complete-sites/dog-shelter-multi-page/node_modules/.vite/deps/_metadata.json +8 -8
- package/examples/complete-sites/dog-shelter-multi-page/site/.norna/generated-images.json +20 -20
- package/examples/complete-sites/dog-shelter-multi-page/site/content.md +3 -4
- package/examples/complete-sites/dog-shelter-multi-page/site/{routes → pages}/010-dogs/content.md +3 -4
- package/examples/complete-sites/dog-shelter-multi-page/site/{routes → pages}/020-adopt/content.md +3 -4
- package/examples/complete-sites/dog-shelter-multi-page/site/sitewide-content.yaml +2 -4
- package/examples/complete-sites/dog-shelter-single-page/.astro/collections/site.schema.json +17 -41
- package/examples/complete-sites/dog-shelter-single-page/.astro/collections/sitewide.schema.json +26 -67
- package/examples/complete-sites/dog-shelter-single-page/.astro/collections/theme.schema.json +185 -88
- package/examples/complete-sites/dog-shelter-single-page/.astro/data-store.json +1 -1
- package/examples/complete-sites/dog-shelter-single-page/.astro/dev-local.json +8 -0
- package/examples/complete-sites/dog-shelter-single-page/.astro/dev.json +5 -5
- package/examples/complete-sites/dog-shelter-single-page/.astro/dev.log +16 -12
- package/examples/complete-sites/dog-shelter-single-page/README.md +1 -1
- package/examples/complete-sites/dog-shelter-single-page/node_modules/.vite/deps/_metadata.json +8 -8
- package/examples/complete-sites/dog-shelter-single-page/site/.norna/generated-images.json +12 -12
- package/examples/complete-sites/dog-shelter-single-page/site/content.md +5 -2
- package/examples/complete-sites/dog-shelter-single-page/site/sitewide-content.yaml +2 -4
- package/examples/feature-demos/media-and-surfaces/.astro/collections/site.schema.json +17 -41
- package/examples/feature-demos/media-and-surfaces/.astro/collections/sitewide.schema.json +26 -67
- package/examples/feature-demos/media-and-surfaces/.astro/collections/theme.schema.json +185 -88
- package/examples/feature-demos/media-and-surfaces/.astro/data-store.json +1 -1
- package/examples/feature-demos/media-and-surfaces/.astro/dev.log +4 -14
- package/examples/feature-demos/media-and-surfaces/README.md +2 -2
- package/examples/feature-demos/media-and-surfaces/node_modules/.vite/deps/_metadata.json +9 -9
- package/examples/feature-demos/media-and-surfaces/site/.norna/generated-images.json +16 -16
- package/examples/feature-demos/media-and-surfaces/site/content.md +4 -7
- package/examples/feature-demos/media-and-surfaces/site/{routes → pages}/010-media/content.md +6 -5
- package/examples/feature-demos/media-and-surfaces/site/{routes → pages}/020-surfaces/content.md +7 -6
- package/examples/feature-demos/media-and-surfaces/site/sitewide-content.yaml +0 -2
- package/examples/feature-demos/sitewide-content/README.md +3 -3
- package/examples/feature-demos/sitewide-content/site/content.md +8 -7
- package/examples/feature-demos/sitewide-content/site/pages/010-logo-and-navigation/content.md +17 -0
- package/examples/feature-demos/sitewide-content/site/{routes → pages}/020-notices/content.md +7 -6
- package/examples/feature-demos/sitewide-content/site/{routes → pages}/030-footer/content.md +6 -5
- package/examples/feature-demos/sitewide-content/site/sitewide-content.yaml +4 -13
- package/examples/feature-demos/theme-presets/README.md +1 -1
- package/examples/feature-demos/theme-presets/site/.norna/generated-images.json +60 -60
- package/examples/feature-demos/theme-presets/site/content.md +4 -7
- package/examples/feature-demos/theme-presets/site/{routes → pages}/010-portfolio/content.md +4 -7
- package/examples/feature-demos/theme-presets/site/{routes → pages}/020-documentation/content.md +4 -7
- package/examples/feature-demos/theme-presets/site/{routes → pages}/030-project/content.md +4 -7
- package/examples/feature-demos/theme-presets/site/{routes → pages}/040-statement/content.md +4 -7
- package/examples/feature-demos/theme-presets/site/sitewide-content.yaml +1 -10
- package/fixtures/basic/site/content.md +5 -6
- package/fixtures/basic/site/sitewide-content.yaml +1 -8
- package/fixtures/content-model-v2/site/content.md +11 -13
- package/fixtures/content-model-v2/site/pages/010-guide/content.md +18 -0
- package/fixtures/nested-pages/site/.norna/generated-images.json +10 -0
- package/fixtures/nested-pages/site/config.yaml +1 -0
- package/fixtures/nested-pages/site/content.md +15 -0
- package/fixtures/nested-pages/site/pages/010-guides/content.md +15 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/content.md +21 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/images/diagram.svg +21 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/010-installation/pages/010-macos/content.md +14 -0
- package/fixtures/nested-pages/site/pages/010-guides/pages/020-workflows/content.md +11 -0
- package/fixtures/nested-pages/site/pages/010-guides/theme.yaml +3 -0
- package/fixtures/nested-pages/site/pages/020-reference/content.md +10 -0
- package/fixtures/nested-pages/site/pages/020-reference/pages/010-installation/content.md +11 -0
- package/fixtures/nested-pages/site/theme.yaml +3 -0
- package/package.json +7 -4
- package/schemas/config.schema.json +5 -5
- package/schemas/content-frontmatter.schema.json +28 -63
- package/schemas/manifest.json +1 -0
- package/schemas/sitewide-content.schema.json +58 -132
- package/schemas/theme.schema.json +396 -86
- package/scripts/check-config.mjs +6 -5
- package/scripts/deploy-site.mjs +6 -8
- package/scripts/doctor.mjs +2 -2
- package/scripts/generate-images.mjs +11 -18
- package/scripts/generate-schemas.mjs +3 -217
- package/scripts/lib/base-path-redirect.mjs +9 -0
- package/scripts/lib/editor-language-service.mjs +126 -66
- package/scripts/lib/heading-ids.mjs +161 -0
- package/scripts/lib/navigation-model.mjs +51 -0
- package/scripts/lib/norna-markdown-blocks.mjs +3 -4
- package/scripts/lib/page-model.mjs +83 -0
- package/scripts/lib/project-config.mjs +28 -0
- package/scripts/lib/schema-definitions.mjs +41 -46
- package/scripts/lib/schema-editor-metadata.mjs +475 -0
- package/scripts/lib/schema-value-definitions.mjs +6 -6
- package/scripts/lib/site-content.mjs +124 -87
- package/scripts/lib/site-paths.mjs +2 -2
- package/scripts/lib/theme-config.mjs +9 -16
- package/scripts/lib/theme-presets.mjs +9 -1
- package/scripts/release.mjs +1 -1
- package/scripts/show-typography.mjs +26 -20
- package/scripts/sync-content-sections.mjs +106 -65
- package/scripts/{test-temporary-visibility.mjs → test-banner-visibility.mjs} +7 -24
- package/scripts/test-client-javascript.mjs +39 -42
- package/scripts/test-content-check.mjs +138 -54
- package/scripts/test-content-model-v2.mjs +445 -228
- package/scripts/test-documentation.mjs +6 -1
- package/scripts/test-editor-language-service.mjs +168 -24
- package/scripts/test-engine-commands.mjs +2 -1
- package/scripts/test-heading-ids.mjs +68 -0
- package/scripts/test-navigation-model.mjs +63 -0
- package/scripts/test-nested-pages.mjs +110 -0
- package/scripts/test-package-check.mjs +31 -40
- package/scripts/test-page-model.mjs +80 -0
- package/scripts/test-project-config.mjs +25 -1
- package/scripts/test-schemas.mjs +66 -14
- package/scripts/test-theme-presets.mjs +33 -24
- package/src/components/NavigationPageTree.astro +68 -0
- package/src/components/SectionNavigationScript.astro +110 -5
- package/src/components/SiteBreadcrumbs.astro +29 -0
- package/src/components/SiteNavigation.astro +65 -69
- package/src/components/SitePage.astro +70 -46
- package/src/components/SiteSection.astro +27 -20
- package/src/content.config.ts +9 -5
- package/src/layouts/BaseLayout.astro +11 -20
- package/src/lib/pageThemes.ts +42 -0
- package/src/lib/sectionContent.ts +109 -106
- package/src/lib/siteNavigation.ts +48 -0
- package/src/lib/sitePages.ts +85 -38
- package/src/pages/{[slug].astro → [...slug].astro} +2 -2
- package/src/styles/global.css +373 -55
- package/starters/basic/README.md +5 -7
- package/starters/basic/site/content.md +7 -4
- package/starters/basic/site/sitewide-content.yaml +1 -10
- package/starters/project/README.md +8 -8
- package/starters/project/site/content.md +5 -4
- package/starters/project/site/{routes → pages}/010-guide/content.md +5 -4
- package/starters/project/site/sitewide-content.yaml +1 -10
- package/docs/routes.md +0 -121
- package/examples/feature-demos/sitewide-content/site/routes/010-identity/content.md +0 -20
- package/fixtures/content-model-v2/site/routes/010-guide/content.md +0 -17
- package/scripts/lib/route-model.mjs +0 -17
- package/scripts/test-route-model.mjs +0 -32
- package/src/lib/routeThemes.ts +0 -41
- /package/examples/complete-sites/dog-shelter-multi-page/site/images/{intro/shelter-dog.jpg → shelter-dog.jpg} +0 -0
- /package/examples/complete-sites/dog-shelter-multi-page/site/{routes/010-dogs/images/dogs → pages/010-dogs/images}/rex.jpg +0 -0
- /package/examples/complete-sites/dog-shelter-multi-page/site/{routes/010-dogs/images/dogs → pages/010-dogs/images}/rover.jpg +0 -0
- /package/examples/complete-sites/dog-shelter-multi-page/site/{routes/020-adopt/images/adopt → pages/020-adopt/images}/adoption-dog.jpg +0 -0
- /package/examples/complete-sites/dog-shelter-single-page/site/images/{you-can-help/foster-dog.jpg → foster-dog.jpg} +0 -0
- /package/examples/complete-sites/dog-shelter-single-page/site/images/{what-we-do/rescue-dog.jpg → rescue-dog.jpg} +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/cards → pages/010-media/images}/card-carousel.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/cards → pages/010-media/images}/card-stack.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/cards → pages/010-media/images}/card-surfaces.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/carousel → pages/010-media/images}/carousel-one.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/carousel → pages/010-media/images}/carousel-three.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/carousel → pages/010-media/images}/carousel-two.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/stack → pages/010-media/images}/stack-one.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes/010-media/images/stack → pages/010-media/images}/stack-two.svg +0 -0
- /package/examples/feature-demos/media-and-surfaces/site/{routes → pages}/020-surfaces/theme.yaml +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/010-portfolio/images/portfolio → pages/010-portfolio/images}/puppy-portrait.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/010-portfolio/images/portfolio → pages/010-portfolio/images}/shepherd-portrait.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/010-portfolio/images/portfolio → pages/010-portfolio/images}/white-puppy.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes → pages}/010-portfolio/theme.yaml +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/020-documentation/images/documentation → pages/020-documentation/images}/black-schnauzer.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/020-documentation/images/documentation → pages/020-documentation/images}/retriever-portrait.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/020-documentation/images/documentation → pages/020-documentation/images}/schnauzer-portrait.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes → pages}/020-documentation/theme.yaml +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/030-project/images/project → pages/030-project/images}/boxer-portrait.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/030-project/images/project → pages/030-project/images}/grass-puppy.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/030-project/images/project → pages/030-project/images}/grey-street-dog.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes → pages}/030-project/theme.yaml +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/040-statement/images/statement → pages/040-statement/images}/black-pedigree.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/040-statement/images/statement → pages/040-statement/images}/sheepdog-grass.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes/040-statement/images/statement → pages/040-statement/images}/sheepdog-wide.jpg +0 -0
- /package/examples/feature-demos/theme-presets/site/{routes → pages}/040-statement/theme.yaml +0 -0
- /package/fixtures/basic/site/images/{work/.gitkeep → .gitkeep} +0 -0
- /package/fixtures/content-model-v2/site/images/{card-section/adopt.svg → adopt.svg} +0 -0
- /package/fixtures/content-model-v2/site/images/{intro/detail.jpg → detail.jpg} +0 -0
- /package/fixtures/content-model-v2/site/images/{intro/duplicate.jpg → duplicate.jpg} +0 -0
- /package/fixtures/content-model-v2/site/images/{card-section/foster.svg → foster.svg} +0 -0
- /package/fixtures/content-model-v2/site/images/{intro/hero.jpg → hero.jpg} +0 -0
- /package/fixtures/content-model-v2/site/images/{plain/image.jpg → image.jpg} +0 -0
- /package/fixtures/content-model-v2/site/images/{timed/slide-one.jpg → slide-one.jpg} +0 -0
- /package/fixtures/content-model-v2/site/images/{timed/slide-three.jpg → slide-three.jpg} +0 -0
- /package/fixtures/content-model-v2/site/images/{timed/slide-two.jpg → slide-two.jpg} +0 -0
- /package/fixtures/content-model-v2/site/{routes/010-guide/images/intro → pages/010-guide/images}/duplicate.jpg +0 -0
- /package/starters/basic/site/images/{work/.gitkeep → .gitkeep} +0 -0
|
@@ -168,7 +168,7 @@ try {
|
|
|
168
168
|
assertFileExists(path.join(packagedStarterRoot, 'site', 'theme.yaml')),
|
|
169
169
|
assertFileExists(path.join(packagedStarterRoot, 'site', 'sitewide-content.yaml')),
|
|
170
170
|
assertFileExists(path.join(packagedStarterRoot, 'site', 'content.md')),
|
|
171
|
-
assertFileExists(path.join(packagedStarterRoot, 'site', 'images', '
|
|
171
|
+
assertFileExists(path.join(packagedStarterRoot, 'site', 'images', '.gitkeep')),
|
|
172
172
|
assertFileExists(path.join(packagedStarterRoot, 'site', 'public', 'robots.txt')),
|
|
173
173
|
]);
|
|
174
174
|
await assertFileIncludes(
|
|
@@ -198,21 +198,19 @@ try {
|
|
|
198
198
|
const packageCheckSitewide = await readFile(packageCheckSitewidePath, 'utf8');
|
|
199
199
|
await writeFile(
|
|
200
200
|
packageCheckSitewidePath,
|
|
201
|
-
|
|
201
|
+
`logo:\n height: 2.6rem\n${packageCheckSitewide}`,
|
|
202
202
|
);
|
|
203
203
|
await writeFile(
|
|
204
204
|
path.join(siteProjectRoot, 'site', 'public', 'logo.svg'),
|
|
205
205
|
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect width="32" height="32" fill="black"/></svg>\n',
|
|
206
206
|
);
|
|
207
207
|
await writeFile(path.join(siteProjectRoot, 'site', 'content.md'), `---
|
|
208
|
-
|
|
209
|
-
description: Site used by package checks.
|
|
210
|
-
sections:
|
|
211
|
-
intro: {}
|
|
212
|
-
warning: {}
|
|
213
|
-
work: {}
|
|
208
|
+
page:
|
|
209
|
+
description: Site used by package checks.
|
|
214
210
|
---
|
|
215
211
|
|
|
212
|
+
# Package Check Site
|
|
213
|
+
|
|
216
214
|
## Intro {#intro}
|
|
217
215
|
|
|
218
216
|
This page verifies that **packaged norna sites** can build with the current content model.
|
|
@@ -230,19 +228,17 @@ This section verifies styled section headings.
|
|
|
230
228
|
|
|
231
229
|
This section keeps the package check independent from user-facing starter copy.
|
|
232
230
|
`);
|
|
233
|
-
await mkdir(path.join(siteProjectRoot, 'site', '
|
|
234
|
-
await writeFile(path.join(siteProjectRoot, 'site', '
|
|
235
|
-
|
|
236
|
-
description:
|
|
237
|
-
navigation:
|
|
238
|
-
label: About
|
|
239
|
-
sections:
|
|
240
|
-
about: {}
|
|
231
|
+
await mkdir(path.join(siteProjectRoot, 'site', 'pages', '010-about'), { recursive: true });
|
|
232
|
+
await writeFile(path.join(siteProjectRoot, 'site', 'pages', '010-about', 'content.md'), `---
|
|
233
|
+
page:
|
|
234
|
+
description: Page used by package checks.
|
|
241
235
|
---
|
|
242
236
|
|
|
237
|
+
# About the site
|
|
238
|
+
|
|
243
239
|
## About {#about}
|
|
244
240
|
|
|
245
|
-
This
|
|
241
|
+
This page verifies that packaged norna sites can build additional pages.
|
|
246
242
|
`);
|
|
247
243
|
|
|
248
244
|
await runInherit(npmBin, ['install', '--no-audit', '--no-fund', '--prefer-offline', '--fetch-retries=0'], { cwd: siteProjectRoot, env: npmEnv });
|
|
@@ -255,9 +251,9 @@ This route verifies that packaged norna sites can build route pages.
|
|
|
255
251
|
assertFileMissing(path.join(initializedSiteRoot, '.DS_Store')),
|
|
256
252
|
assertFileMissing(path.join(initializedSiteRoot, 'site', '.DS_Store')),
|
|
257
253
|
]);
|
|
258
|
-
await runInherit(npxBin, ['norna', 'engine:version'], { cwd: path.join(siteProjectRoot, 'site', 'images'
|
|
259
|
-
await runInherit(npxBin, ['norna', 'doctor'], { cwd: path.join(siteProjectRoot, 'site', 'images'
|
|
260
|
-
await runInherit(npxBin, ['norna', 'config:check'], { cwd: path.join(siteProjectRoot, 'site', 'images'
|
|
254
|
+
await runInherit(npxBin, ['norna', 'engine:version'], { cwd: path.join(siteProjectRoot, 'site', 'images'), env: npmEnv });
|
|
255
|
+
await runInherit(npxBin, ['norna', 'doctor'], { cwd: path.join(siteProjectRoot, 'site', 'images'), env: npmEnv });
|
|
256
|
+
await runInherit(npxBin, ['norna', 'config:check'], { cwd: path.join(siteProjectRoot, 'site', 'images'), env: npmEnv });
|
|
261
257
|
await runInherit(npxBin, ['norna', 'content:check'], { cwd: siteProjectRoot, env: npmEnv });
|
|
262
258
|
await runInherit(npxBin, ['norna', 'check'], { cwd: siteProjectRoot, env: npmEnv });
|
|
263
259
|
await runInherit(npxBin, ['norna', 'build'], { cwd: siteProjectRoot, env: npmEnv });
|
|
@@ -315,15 +311,23 @@ This route verifies that packaged norna sites can build route pages.
|
|
|
315
311
|
);
|
|
316
312
|
await assertFileIncludes(
|
|
317
313
|
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
318
|
-
'<a class="site-brand" href="/site/"><img class="site-brand-logo" src="/site/logo.svg" alt="Package Check
|
|
314
|
+
'<a class="site-brand" href="/site/"><img class="site-brand-logo" src="/site/logo.svg" alt="Package Check Site">',
|
|
319
315
|
);
|
|
320
|
-
await
|
|
316
|
+
await assertFileExcludes(
|
|
321
317
|
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
322
318
|
'<div class="site-nav-submenu">',
|
|
323
319
|
);
|
|
324
320
|
await assertFileIncludes(
|
|
325
321
|
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
326
|
-
'href="/site
|
|
322
|
+
'href="/site/about/">About the site</a>',
|
|
323
|
+
);
|
|
324
|
+
await assertFileIncludes(
|
|
325
|
+
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
326
|
+
'<nav class="page-nav" aria-label="On this page">',
|
|
327
|
+
);
|
|
328
|
+
await assertFileIncludes(
|
|
329
|
+
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
330
|
+
'href="#intro">Intro</a>',
|
|
327
331
|
);
|
|
328
332
|
await assertFileIncludes(
|
|
329
333
|
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
@@ -367,7 +371,7 @@ This route verifies that packaged norna sites can build route pages.
|
|
|
367
371
|
);
|
|
368
372
|
await assertFileIncludes(
|
|
369
373
|
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
370
|
-
'href="
|
|
374
|
+
'href="#warning">Warning</a>',
|
|
371
375
|
);
|
|
372
376
|
await assertFileIncludes(
|
|
373
377
|
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
@@ -484,28 +488,15 @@ This route verifies that packaged norna sites can build route pages.
|
|
|
484
488
|
await writeFile(
|
|
485
489
|
siteContentPath,
|
|
486
490
|
siteContent.replace(
|
|
487
|
-
'
|
|
488
|
-
' intro
|
|
489
|
-
),
|
|
490
|
-
);
|
|
491
|
-
await runExpectFailure(
|
|
492
|
-
npxBin,
|
|
493
|
-
['norna', 'build'],
|
|
494
|
-
{ cwd: siteProjectRoot, env: npmEnv },
|
|
495
|
-
'sections.intro: Unrecognized key: "typography"',
|
|
496
|
-
);
|
|
497
|
-
await writeFile(
|
|
498
|
-
siteContentPath,
|
|
499
|
-
siteContent.replace(
|
|
500
|
-
' intro: {}',
|
|
501
|
-
' intro:\n palette: dark',
|
|
491
|
+
'description: Site used by package checks.',
|
|
492
|
+
'description: Site used by package checks.\nsections:\n intro: {}',
|
|
502
493
|
),
|
|
503
494
|
);
|
|
504
495
|
await runExpectFailure(
|
|
505
496
|
npxBin,
|
|
506
497
|
['norna', 'build'],
|
|
507
498
|
{ cwd: siteProjectRoot, env: npmEnv },
|
|
508
|
-
'sections
|
|
499
|
+
'defines "sections" at the top level, but it is not a valid top-level content field',
|
|
509
500
|
);
|
|
510
501
|
await writeFile(
|
|
511
502
|
siteThemePath,
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import {
|
|
3
|
+
decodePageDirectoryPath,
|
|
4
|
+
encodePageDirectoryPath,
|
|
5
|
+
getPageDirectoryAncestors,
|
|
6
|
+
parsePageDirectory,
|
|
7
|
+
parsePageDirectoryPath,
|
|
8
|
+
} from './lib/page-model.mjs';
|
|
9
|
+
|
|
10
|
+
assert.deepEqual(parsePageDirectory('010-getting-started'), {
|
|
11
|
+
pageDirectory: '010-getting-started',
|
|
12
|
+
pageOrder: 10,
|
|
13
|
+
pageId: 'getting-started',
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
assert.deepEqual(parsePageDirectory('120-api-reference'), {
|
|
17
|
+
pageDirectory: '120-api-reference',
|
|
18
|
+
pageOrder: 120,
|
|
19
|
+
pageId: 'api-reference',
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
assert.deepEqual(parsePageDirectoryPath('010-guides/pages/020-installation'), {
|
|
23
|
+
pageDirectory: '010-guides/pages/020-installation',
|
|
24
|
+
pageDirectories: ['010-guides', '020-installation'],
|
|
25
|
+
pageId: 'installation',
|
|
26
|
+
pageIds: ['guides', 'installation'],
|
|
27
|
+
pageOrder: 20,
|
|
28
|
+
pageOrders: [10, 20],
|
|
29
|
+
pagePath: 'guides/installation',
|
|
30
|
+
parentPagePath: 'guides',
|
|
31
|
+
depth: 2,
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
assert.equal(
|
|
35
|
+
encodePageDirectoryPath('010-guides/pages/020-installation'),
|
|
36
|
+
'010-guides--020-installation',
|
|
37
|
+
);
|
|
38
|
+
assert.equal(
|
|
39
|
+
decodePageDirectoryPath('010-guides--020-installation'),
|
|
40
|
+
'010-guides/pages/020-installation',
|
|
41
|
+
);
|
|
42
|
+
assert.deepEqual(
|
|
43
|
+
getPageDirectoryAncestors('010-guides/pages/020-installation/pages/030-macos'),
|
|
44
|
+
[
|
|
45
|
+
'010-guides',
|
|
46
|
+
'010-guides/pages/020-installation',
|
|
47
|
+
'010-guides/pages/020-installation/pages/030-macos',
|
|
48
|
+
],
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
for (const pageDirectoryPath of [
|
|
52
|
+
'010-guides/020-installation',
|
|
53
|
+
'010-guides/children/020-installation',
|
|
54
|
+
'pages/010-guides',
|
|
55
|
+
'010-guides/pages',
|
|
56
|
+
]) {
|
|
57
|
+
assert.throws(
|
|
58
|
+
() => parsePageDirectoryPath(pageDirectoryPath),
|
|
59
|
+
/(Nested pages must use|Use a pages directory)/,
|
|
60
|
+
`${pageDirectoryPath} should be rejected`,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const pageDirectory of [
|
|
65
|
+
'000-home',
|
|
66
|
+
'10-about',
|
|
67
|
+
'010_About',
|
|
68
|
+
'010-About',
|
|
69
|
+
'010-about-',
|
|
70
|
+
'010-about--team',
|
|
71
|
+
'010-om-oss!',
|
|
72
|
+
]) {
|
|
73
|
+
assert.throws(
|
|
74
|
+
() => parsePageDirectory(pageDirectory),
|
|
75
|
+
/Page directories must use the form NNN-page-id/,
|
|
76
|
+
`${pageDirectory} should be rejected`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log('Page model test passed.');
|
|
@@ -4,6 +4,7 @@ import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import path from 'node:path';
|
|
6
6
|
import { pathToFileURL } from 'node:url';
|
|
7
|
+
import { getBasePathRedirectLocation } from './lib/base-path-redirect.mjs';
|
|
7
8
|
|
|
8
9
|
const repoRoot = path.resolve(import.meta.dirname, '..');
|
|
9
10
|
const projectConfigUrl = pathToFileURL(path.join(repoRoot, 'scripts', 'lib', 'project-config.mjs')).href;
|
|
@@ -14,6 +15,7 @@ const importScript = `
|
|
|
14
15
|
basePath: projectConfig.site.basePath,
|
|
15
16
|
language: projectConfig.locale.lang,
|
|
16
17
|
labels: projectConfig.locale.labels,
|
|
18
|
+
navigationMode: projectConfig.navigation.mode,
|
|
17
19
|
scrollBehavior: projectConfig.navigation.scrollBehavior,
|
|
18
20
|
url: projectConfig.site.url,
|
|
19
21
|
}));
|
|
@@ -24,7 +26,7 @@ const createSite = async (name, config) => {
|
|
|
24
26
|
const siteDir = path.join(projectRoot, 'site');
|
|
25
27
|
await mkdir(siteDir, { recursive: true });
|
|
26
28
|
await writeFile(path.join(siteDir, 'config.yaml'), config);
|
|
27
|
-
await writeFile(path.join(siteDir, 'content.md'), '
|
|
29
|
+
await writeFile(path.join(siteDir, 'content.md'), '# Config test\n\n## Intro {#intro}\n\nText.\n');
|
|
28
30
|
await writeFile(path.join(siteDir, 'theme.yaml'), 'preset: documentation\n');
|
|
29
31
|
return { projectRoot, siteDir };
|
|
30
32
|
};
|
|
@@ -50,6 +52,12 @@ const assertFailure = (result, expectedPattern) => {
|
|
|
50
52
|
};
|
|
51
53
|
|
|
52
54
|
try {
|
|
55
|
+
assert.equal(getBasePathRedirectLocation('/norna/', '/norna'), '/norna/');
|
|
56
|
+
assert.equal(getBasePathRedirectLocation('/norna/', '/norna?source=test'), '/norna/?source=test');
|
|
57
|
+
assert.equal(getBasePathRedirectLocation('/norna/', '/norna/'), undefined);
|
|
58
|
+
assert.equal(getBasePathRedirectLocation('/norna/', '/norna/concepts/'), undefined);
|
|
59
|
+
assert.equal(getBasePathRedirectLocation('/', '/'), undefined);
|
|
60
|
+
|
|
53
61
|
const minimalSite = await createSite('minimal', 'url: https://example.com/docs\n');
|
|
54
62
|
const minimalResult = loadConfig(minimalSite);
|
|
55
63
|
assert.equal(minimalResult.status, 0, minimalResult.stderr);
|
|
@@ -57,14 +65,20 @@ try {
|
|
|
57
65
|
basePath: '/docs/',
|
|
58
66
|
language: 'en',
|
|
59
67
|
labels: {
|
|
68
|
+
breadcrumb: 'Breadcrumb',
|
|
60
69
|
dismissBanner: 'Dismiss notice',
|
|
70
|
+
built: 'Built',
|
|
61
71
|
images: 'Images',
|
|
72
|
+
navigationBack: 'Back to pages',
|
|
73
|
+
navigationMenu: 'Menu',
|
|
74
|
+
navigationOverview: 'Overview',
|
|
62
75
|
note: 'Note',
|
|
63
76
|
pageNavigation: 'On this page',
|
|
64
77
|
siteBanners: 'Site notices',
|
|
65
78
|
siteNavigation: 'Pages',
|
|
66
79
|
skipToContent: 'Skip to content',
|
|
67
80
|
},
|
|
81
|
+
navigationMode: 'automatic',
|
|
68
82
|
scrollBehavior: 'instant',
|
|
69
83
|
url: 'https://example.com/docs/',
|
|
70
84
|
});
|
|
@@ -74,9 +88,19 @@ try {
|
|
|
74
88
|
assert.equal(localizedResult.status, 0, localizedResult.stderr);
|
|
75
89
|
const localizedConfig = JSON.parse(localizedResult.stdout);
|
|
76
90
|
assert.equal(localizedConfig.language, 'sv-SE');
|
|
91
|
+
assert.equal(localizedConfig.labels.built, 'Byggd');
|
|
77
92
|
assert.equal(localizedConfig.labels.skipToContent, 'Hoppa till innehållet');
|
|
78
93
|
assert.equal(localizedConfig.scrollBehavior, 'smooth');
|
|
79
94
|
|
|
95
|
+
const treeNavigationSite = await createSite('tree-navigation', 'url: https://example.com/\n');
|
|
96
|
+
await writeFile(path.join(treeNavigationSite.siteDir, 'theme.yaml'), `preset: documentation
|
|
97
|
+
navigation:
|
|
98
|
+
mode: tree
|
|
99
|
+
`);
|
|
100
|
+
const treeNavigationResult = loadConfig(treeNavigationSite);
|
|
101
|
+
assert.equal(treeNavigationResult.status, 0, treeNavigationResult.stderr);
|
|
102
|
+
assert.equal(JSON.parse(treeNavigationResult.stdout).navigationMode, 'tree');
|
|
103
|
+
|
|
80
104
|
const overrideResult = loadConfig(minimalSite, {
|
|
81
105
|
NORNA_SITE_URL: 'http://127.0.0.1:4567/preview',
|
|
82
106
|
});
|
package/scripts/test-schemas.mjs
CHANGED
|
@@ -15,10 +15,13 @@ const filenames = [
|
|
|
15
15
|
];
|
|
16
16
|
const requiredRichHelp = {
|
|
17
17
|
'config.schema.json': ['url', 'language', 'scrollBehavior'],
|
|
18
|
-
'theme.schema.json': ['preset', 'layout', 'images', 'typography', 'palette', 'sectionSurfaces'],
|
|
19
|
-
'sitewide-content.schema.json': ['
|
|
20
|
-
'content-frontmatter.schema.json': ['
|
|
18
|
+
'theme.schema.json': ['preset', 'navigation', 'layout', 'images', 'typography', 'palette', 'sectionSurfaces'],
|
|
19
|
+
'sitewide-content.schema.json': ['logo', 'banners', 'footer'],
|
|
20
|
+
'content-frontmatter.schema.json': ['page', 'navigation'],
|
|
21
21
|
};
|
|
22
|
+
const manifest = JSON.parse(await readFile(path.join(root, 'schemas', 'manifest.json'), 'utf8'));
|
|
23
|
+
assert.equal(manifest.editorApiVersion, 1);
|
|
24
|
+
assert.equal(manifest.schemaVersion, 1);
|
|
22
25
|
|
|
23
26
|
const githubHeadingAnchor = (heading) => heading
|
|
24
27
|
.toLowerCase()
|
|
@@ -49,22 +52,26 @@ const assertDocumentationLinks = async (markdownDescription, location) => {
|
|
|
49
52
|
}
|
|
50
53
|
};
|
|
51
54
|
|
|
52
|
-
const visitSchema = (schema, location) => {
|
|
55
|
+
const visitSchema = (schema, location, richProperties) => {
|
|
53
56
|
assert.equal(schema.enum, undefined, `${location} uses an undescribed enum.`);
|
|
54
57
|
if (schema.properties) {
|
|
55
58
|
for (const [name, property] of Object.entries(schema.properties)) {
|
|
56
59
|
assert.ok(property.description, `${location}.${name} has no description.`);
|
|
57
|
-
|
|
60
|
+
richProperties.push([property, `${location}.${name}`]);
|
|
61
|
+
visitSchema(property, `${location}.${name}`, richProperties);
|
|
58
62
|
}
|
|
59
63
|
}
|
|
60
|
-
if (schema.items) visitSchema(schema.items, `${location}[]
|
|
64
|
+
if (schema.items) visitSchema(schema.items, `${location}[]`, richProperties);
|
|
65
|
+
if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {
|
|
66
|
+
visitSchema(schema.additionalProperties, `${location}.*`, richProperties);
|
|
67
|
+
}
|
|
61
68
|
for (const keyword of ['allOf', 'anyOf', 'oneOf']) {
|
|
62
69
|
for (const [index, candidate] of (schema[keyword] ?? []).entries()) {
|
|
63
70
|
if (Object.hasOwn(candidate, 'const')) {
|
|
64
71
|
assert.ok(candidate.title, `${location}.${keyword}[${index}] has no title.`);
|
|
65
72
|
assert.ok(candidate.description, `${location}.${keyword}[${index}] has no description.`);
|
|
66
73
|
}
|
|
67
|
-
visitSchema(candidate, `${location}.${keyword}[${index}]
|
|
74
|
+
visitSchema(candidate, `${location}.${keyword}[${index}]`, richProperties);
|
|
68
75
|
}
|
|
69
76
|
}
|
|
70
77
|
};
|
|
@@ -83,7 +90,16 @@ for (const filename of filenames) {
|
|
|
83
90
|
);
|
|
84
91
|
await assertDocumentationLinks(property.markdownDescription, `${filename}.${propertyName}`);
|
|
85
92
|
}
|
|
86
|
-
|
|
93
|
+
const richProperties = [];
|
|
94
|
+
visitSchema(schema, filename, richProperties);
|
|
95
|
+
for (const [property, location] of richProperties) {
|
|
96
|
+
assert.match(
|
|
97
|
+
property.markdownDescription,
|
|
98
|
+
/^```yaml\n/,
|
|
99
|
+
`${location} rich help must start with YAML syntax.`,
|
|
100
|
+
);
|
|
101
|
+
await assertDocumentationLinks(property.markdownDescription, location);
|
|
102
|
+
}
|
|
87
103
|
}
|
|
88
104
|
|
|
89
105
|
const config = JSON.parse(await readFile(path.join(root, 'schemas', 'config.schema.json'), 'utf8'));
|
|
@@ -92,11 +108,47 @@ assert.deepEqual(config.properties.language.examples, ['en', 'sv', 'en-GB', 'sv-
|
|
|
92
108
|
assert.equal(config.properties.scrollBehavior.default, 'instant');
|
|
93
109
|
|
|
94
110
|
const sitewide = JSON.parse(await readFile(path.join(root, 'schemas', 'sitewide-content.schema.json'), 'utf8'));
|
|
95
|
-
assert.deepEqual(Object.keys(sitewide.properties.
|
|
96
|
-
assert.
|
|
97
|
-
assert.match(sitewide.properties.
|
|
98
|
-
assert.match(sitewide.properties.
|
|
99
|
-
assert.match(sitewide.properties.
|
|
100
|
-
assert.equal(sitewide.properties.
|
|
111
|
+
assert.deepEqual(Object.keys(sitewide.properties.logo.properties), ['height']);
|
|
112
|
+
assert.match(sitewide.properties.logo.markdownDescription, /```yaml\nlogo:\n height: 2rem\n```/);
|
|
113
|
+
assert.match(sitewide.properties.logo.markdownDescription, /does not enable or select the file/);
|
|
114
|
+
assert.match(sitewide.properties.logo.markdownDescription, /homepage Markdown H1/);
|
|
115
|
+
assert.match(sitewide.properties.logo.markdownDescription, /docs\/public-files\.md#navigation-logo/);
|
|
116
|
+
assert.equal(sitewide.properties.logo.description.length < 100, true);
|
|
117
|
+
const bannerItem = sitewide.properties.banners.items;
|
|
118
|
+
assert.equal(bannerItem.title, 'Warning banner');
|
|
119
|
+
assert.match(bannerItem.description, /important temporary information/);
|
|
120
|
+
assert.match(bannerItem.markdownDescription, /A `warning` is a dismissible site-wide notice/);
|
|
121
|
+
await assertDocumentationLinks(bannerItem.markdownDescription, 'sitewide-content.schema.json.banners[]');
|
|
122
|
+
assert.equal(bannerItem.defaultSnippets[0].label, 'Warning banner');
|
|
123
|
+
assert.deepEqual(bannerItem.defaultSnippets[0].body, {
|
|
124
|
+
id: '${1:project-status}',
|
|
125
|
+
tone: 'warning',
|
|
126
|
+
title: '${2:Important notice}',
|
|
127
|
+
text: '${3:Brief explanation.}',
|
|
128
|
+
});
|
|
129
|
+
await assertDocumentationLinks(
|
|
130
|
+
bannerItem.defaultSnippets[0].markdownDescription,
|
|
131
|
+
'sitewide-content.schema.json.banners[].defaultSnippets[0]',
|
|
132
|
+
);
|
|
133
|
+
const buildInfo = sitewide.properties.footer.properties.buildInfo;
|
|
134
|
+
assert.equal(buildInfo.type, 'boolean');
|
|
135
|
+
assert.equal(buildInfo.default, false);
|
|
136
|
+
assert.match(buildInfo.markdownDescription, /footer:\n buildInfo: true/);
|
|
137
|
+
|
|
138
|
+
const theme = JSON.parse(await readFile(path.join(root, 'schemas', 'theme.schema.json'), 'utf8'));
|
|
139
|
+
assert.equal(theme.properties.layout.properties.gutter.defaultSnippets[0].label, 'Responsive page gutter');
|
|
140
|
+
assert.equal(theme.properties.layout.properties.spacing.defaultSnippets[0].label, 'Layout spacing overrides');
|
|
141
|
+
assert.equal(theme.properties.typography.properties.overrides.defaultSnippets[0].label, 'Typography overrides');
|
|
142
|
+
assert.equal(
|
|
143
|
+
theme.properties.images.properties.maxAvailableWidthPercent.defaultSnippets[0].label,
|
|
144
|
+
'Responsive image limit',
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const content = JSON.parse(await readFile(path.join(root, 'schemas', 'content-frontmatter.schema.json'), 'utf8'));
|
|
148
|
+
assert.deepEqual(Object.keys(content.properties), ['page', 'navigation']);
|
|
149
|
+
assert.deepEqual(Object.keys(content.properties.page.properties), ['description']);
|
|
150
|
+
assert.equal(content.properties.page.required, undefined);
|
|
151
|
+
assert.equal(content.required, undefined);
|
|
152
|
+
assert.deepEqual(Object.keys(content.properties.navigation.properties), ['listed']);
|
|
101
153
|
|
|
102
154
|
console.log('Schema metadata tests passed.');
|
|
@@ -36,6 +36,7 @@ try {
|
|
|
36
36
|
const resolved = resolveThemeConfig({ preset: presetName }, 'test theme');
|
|
37
37
|
assert.equal(resolved.preset, presetName);
|
|
38
38
|
assert.ok(resolved.layout?.density);
|
|
39
|
+
assert.equal(resolved.navigation?.mode, 'automatic');
|
|
39
40
|
assert.ok(resolved.images?.width);
|
|
40
41
|
assert.ok(resolved.typography?.fontFamily);
|
|
41
42
|
assert.ok(resolved.typography?.profile);
|
|
@@ -49,6 +50,7 @@ try {
|
|
|
49
50
|
palette: 'dark',
|
|
50
51
|
}, 'test theme');
|
|
51
52
|
assert.equal(overridden.layout.density, 'compact');
|
|
53
|
+
assert.equal(overridden.navigation.mode, 'automatic');
|
|
52
54
|
assert.equal(overridden.layout.pageWidth, '1300px');
|
|
53
55
|
assert.equal(overridden.images.width, '920px');
|
|
54
56
|
assert.equal(overridden.palette, 'dark');
|
|
@@ -87,13 +89,15 @@ try {
|
|
|
87
89
|
assert.equal(entry.description, metadata.description);
|
|
88
90
|
}
|
|
89
91
|
|
|
90
|
-
await mkdir(path.join(siteDir, '
|
|
92
|
+
await mkdir(path.join(siteDir, 'pages', '010-guide'), { recursive: true });
|
|
91
93
|
await writeFile(path.join(siteDir, 'config.yaml'), 'url: https://example.com/\n');
|
|
92
94
|
await writeFile(path.join(siteDir, 'content.md'), `---
|
|
93
|
-
|
|
94
|
-
description: Root page
|
|
95
|
+
page:
|
|
96
|
+
description: Root page
|
|
95
97
|
---
|
|
96
98
|
|
|
99
|
+
# Theme preset test
|
|
100
|
+
|
|
97
101
|
## Home {#home}
|
|
98
102
|
|
|
99
103
|
Root content.
|
|
@@ -103,18 +107,15 @@ layout:
|
|
|
103
107
|
pageWidth: 1300px
|
|
104
108
|
palette: dark
|
|
105
109
|
`);
|
|
106
|
-
await writeFile(path.join(siteDir, '
|
|
107
|
-
|
|
108
|
-
description:
|
|
109
|
-
navigation:
|
|
110
|
-
label: Guide
|
|
110
|
+
await writeFile(path.join(siteDir, 'pages', '010-guide', 'content.md'), `---
|
|
111
|
+
page:
|
|
112
|
+
description: Additional page
|
|
111
113
|
---
|
|
112
114
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
Route content.
|
|
115
|
+
# Guide
|
|
116
|
+
Page content.
|
|
116
117
|
`);
|
|
117
|
-
await writeFile(path.join(siteDir, '
|
|
118
|
+
await writeFile(path.join(siteDir, 'pages', '010-guide', 'theme.yaml'), `preset: portfolio
|
|
118
119
|
layout:
|
|
119
120
|
pageWidth: 1010px
|
|
120
121
|
palette: light
|
|
@@ -123,26 +124,33 @@ palette: light
|
|
|
123
124
|
const buildResult = runCli(['build']);
|
|
124
125
|
assert.equal(buildResult.status, 0, buildResult.stderr || buildResult.stdout);
|
|
125
126
|
const rootHtml = await readFile(path.join(tempRoot, 'dist', 'index.html'), 'utf8');
|
|
126
|
-
const
|
|
127
|
+
const pageHtml = await readFile(path.join(tempRoot, 'dist', 'guide', 'index.html'), 'utf8');
|
|
127
128
|
assert.match(rootHtml, /--page-width: 1300px/);
|
|
128
129
|
assert.match(rootHtml, /--font-sans: Georgia, 'Times New Roman', serif/);
|
|
129
130
|
assert.match(rootHtml, /--color-page: #000000/);
|
|
130
|
-
assert.match(
|
|
131
|
-
assert.match(
|
|
132
|
-
assert.match(
|
|
131
|
+
assert.match(pageHtml, /--page-width: 1010px/);
|
|
132
|
+
assert.match(pageHtml, /--font-sans: 'Helvetica Neue', Arial, sans-serif/);
|
|
133
|
+
assert.match(pageHtml, /--color-page: #ffffff/);
|
|
134
|
+
assert.match(rootHtml, /data-navigation-mode="top"/);
|
|
135
|
+
assert.match(pageHtml, /data-navigation-mode="top"/);
|
|
133
136
|
|
|
134
137
|
const typographyResult = runCli(['typography', 'show']);
|
|
135
138
|
assert.equal(typographyResult.status, 0, typographyResult.stderr || typographyResult.stdout);
|
|
136
139
|
assert.match(typographyResult.stdout, /value: reading/);
|
|
137
140
|
assert.match(typographyResult.stdout, /value: restrained/);
|
|
138
141
|
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
await writeFile(
|
|
142
|
-
const
|
|
143
|
-
assert.notEqual(
|
|
144
|
-
assert.match(
|
|
145
|
-
await writeFile(
|
|
142
|
+
const pageThemePath = path.join(siteDir, 'pages', '010-guide', 'theme.yaml');
|
|
143
|
+
const pageThemeSource = await readFile(pageThemePath, 'utf8');
|
|
144
|
+
await writeFile(pageThemePath, 'preset: unknown\n');
|
|
145
|
+
const invalidPagePresetResult = runCli(['config:check']);
|
|
146
|
+
assert.notEqual(invalidPagePresetResult.status, 0);
|
|
147
|
+
assert.match(invalidPagePresetResult.stderr, /Unknown theme preset "unknown" in .*pages\/010-guide\/theme\.yaml/);
|
|
148
|
+
await writeFile(pageThemePath, pageThemeSource);
|
|
149
|
+
await writeFile(pageThemePath, 'navigation:\n mode: sections\n');
|
|
150
|
+
const pageNavigationResult = runCli(['config:check']);
|
|
151
|
+
assert.notEqual(pageNavigationResult.status, 0);
|
|
152
|
+
assert.match(pageNavigationResult.stderr, /page themes may not define navigation/);
|
|
153
|
+
await writeFile(pageThemePath, pageThemeSource);
|
|
146
154
|
|
|
147
155
|
const exportResult = runCli(['theme:export', 'documentation']);
|
|
148
156
|
assert.equal(exportResult.status, 0, exportResult.stderr || exportResult.stdout);
|
|
@@ -154,6 +162,7 @@ palette: light
|
|
|
154
162
|
assert.match(exportedSource, /# Alternatives: dark, light, paper\./);
|
|
155
163
|
const exportedConfig = load(exportedSource);
|
|
156
164
|
assert.equal(exportedConfig.preset, 'documentation');
|
|
165
|
+
assert.equal(exportedConfig.navigation.mode, 'automatic');
|
|
157
166
|
assert.equal(exportedConfig.layout.pageWidth, themePresets.documentation.layout.pageWidth);
|
|
158
167
|
assert.equal(exportedConfig.typography.fontFamily, themePresets.documentation.typography.fontFamily);
|
|
159
168
|
assert.deepEqual(exportedConfig.sectionSurfaces, ['base', 'soft']);
|
|
@@ -167,7 +176,7 @@ palette: light
|
|
|
167
176
|
assert.notEqual(unknownExportResult.status, 0);
|
|
168
177
|
assert.match(unknownExportResult.stderr, /Unknown theme preset "unknown"/);
|
|
169
178
|
|
|
170
|
-
console.log('ok - complete theme presets resolve for root and
|
|
179
|
+
console.log('ok - complete theme presets resolve for root and page themes and export as protected references');
|
|
171
180
|
} finally {
|
|
172
181
|
await rm(tempRoot, { force: true, recursive: true });
|
|
173
182
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
import projectConfig from '../../scripts/lib/project-config.mjs';
|
|
3
|
+
import { withBasePath } from '../lib/basePath';
|
|
4
|
+
import type { PageNavigationNode } from '../lib/siteNavigation';
|
|
5
|
+
import type { SitePage } from '../lib/sitePages';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
nodes: PageNavigationNode[];
|
|
9
|
+
currentPage: SitePage;
|
|
10
|
+
variant: 'mobile' | 'submenu';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const { nodes, currentPage, variant } = Astro.props;
|
|
14
|
+
const pageHref = (page: SitePage) => withBasePath(projectConfig.site.basePath, page.pathname);
|
|
15
|
+
const hasBranch = (node: PageNavigationNode) => node.children.length > 0;
|
|
16
|
+
const isCurrentPage = (page: SitePage) => page.pathname === currentPage.pathname;
|
|
17
|
+
const isCurrentBranch = (page: SitePage) => (
|
|
18
|
+
page.isHome
|
|
19
|
+
? currentPage.isHome
|
|
20
|
+
: currentPage.pagePath === page.pagePath || currentPage.pagePath.startsWith(`${page.pagePath}/`)
|
|
21
|
+
);
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
<ul class:list={['navigation-page-tree', `navigation-page-tree-${variant}`]}>
|
|
25
|
+
{nodes.map((node) => (
|
|
26
|
+
<li
|
|
27
|
+
class:list={['navigation-page-node', {
|
|
28
|
+
'navigation-page-node-current': isCurrentPage(node.page),
|
|
29
|
+
'navigation-page-node-current-branch': isCurrentBranch(node.page),
|
|
30
|
+
}]}
|
|
31
|
+
>
|
|
32
|
+
{variant === 'mobile' && hasBranch(node) ? (
|
|
33
|
+
<details class="mobile-navigation-branch" open={isCurrentBranch(node.page)}>
|
|
34
|
+
<summary><span>{node.page.title}</span></summary>
|
|
35
|
+
<div class="mobile-navigation-branch-content">
|
|
36
|
+
<ul class="mobile-navigation-page-links">
|
|
37
|
+
<li>
|
|
38
|
+
<a
|
|
39
|
+
class="mobile-nav-destination mobile-nav-overview"
|
|
40
|
+
href={pageHref(node.page)}
|
|
41
|
+
aria-current={isCurrentPage(node.page) ? 'page' : undefined}
|
|
42
|
+
>
|
|
43
|
+
{projectConfig.locale.labels.navigationOverview}
|
|
44
|
+
</a>
|
|
45
|
+
</li>
|
|
46
|
+
</ul>
|
|
47
|
+
{node.children.length > 0 && (
|
|
48
|
+
<Astro.self nodes={node.children} currentPage={currentPage} variant={variant} />
|
|
49
|
+
)}
|
|
50
|
+
</div>
|
|
51
|
+
</details>
|
|
52
|
+
) : (
|
|
53
|
+
<>
|
|
54
|
+
<a
|
|
55
|
+
class:list={['navigation-page-link', { 'mobile-nav-destination': variant === 'mobile' }]}
|
|
56
|
+
href={pageHref(node.page)}
|
|
57
|
+
aria-current={isCurrentPage(node.page) ? 'page' : undefined}
|
|
58
|
+
>
|
|
59
|
+
{node.page.title}
|
|
60
|
+
</a>
|
|
61
|
+
{node.children.length > 0 && (
|
|
62
|
+
<Astro.self nodes={node.children} currentPage={currentPage} variant={variant} />
|
|
63
|
+
)}
|
|
64
|
+
</>
|
|
65
|
+
)}
|
|
66
|
+
</li>
|
|
67
|
+
))}
|
|
68
|
+
</ul>
|