@janga/norna 0.7.6 → 0.7.8
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 +8 -7
- package/astro.config.mjs +76 -0
- package/bin/norna-cli.mjs +2 -2
- package/docs/README.md +3 -3
- package/docs/commands.md +7 -6
- package/docs/configuration.md +3 -2
- package/docs/content.md +202 -91
- package/docs/design/site-examples-structure.md +1 -1
- package/docs/engine-development.md +3 -3
- package/docs/getting-started.md +50 -8
- package/docs/images-and-metadata.md +117 -20
- package/docs/routes.md +20 -12
- package/docs/site-structure.md +9 -5
- package/docs/theme.md +12 -8
- package/docs/typography.md +9 -10
- package/examples/dog-gallery/site/content.md +38 -77
- package/examples/dog-gallery/site/routes/010-dog-care/route-content.md +0 -10
- package/fixtures/basic/site/content.md +2 -4
- package/fixtures/content-model-v2/.astro/collections/site.schema.json +776 -0
- package/fixtures/content-model-v2/.astro/collections/theme.schema.json +668 -0
- package/fixtures/content-model-v2/.astro/content-assets.mjs +4 -0
- package/fixtures/content-model-v2/.astro/content-modules.mjs +1 -0
- package/fixtures/content-model-v2/.astro/content.d.ts +175 -0
- package/fixtures/content-model-v2/.astro/data-store.json +1 -0
- package/fixtures/content-model-v2/.astro/dev-local.json +8 -0
- package/fixtures/content-model-v2/.astro/dev.json +13 -0
- package/fixtures/content-model-v2/.astro/dev.log +231 -0
- package/fixtures/content-model-v2/.astro/settings.json +5 -0
- package/fixtures/content-model-v2/.astro/types.d.ts +2 -0
- package/fixtures/content-model-v2/dist/_astro/GalleryGrid.astro_astro_type_script_index_0_lang.B0kxDyGz.js +1 -0
- package/fixtures/content-model-v2/dist/_astro/SitePage.BbfZub4g.css +1 -0
- package/fixtures/content-model-v2/dist/guide/index.html +3 -0
- package/fixtures/content-model-v2/dist/images/generated/intro/detail-27930b14-480.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/intro/detail-27930b14-768.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/intro/detail-27930b14-971.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/intro/duplicate-49c98f6c-480.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/intro/duplicate-49c98f6c-768.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/intro/duplicate-49c98f6c-971.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/intro/hero-6d588a83-480.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/intro/hero-6d588a83-768.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/intro/hero-6d588a83-971.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-1080.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-1440.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-1600.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-480.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/plain/image-3770d763-768.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/routes/010-guide/images/intro/duplicate-5cf614d2-480.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/routes/010-guide/images/intro/duplicate-5cf614d2-768.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/routes/010-guide/images/intro/duplicate-5cf614d2-960.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-1080.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-1440.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-1600.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-480.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/timed/slide-one-3770d763-768.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-1080.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-1440.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-1600.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-480.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/timed/slide-three-5bdec65e-768.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-1080.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-1440.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-1600.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-480.webp +0 -0
- package/fixtures/content-model-v2/dist/images/generated/timed/slide-two-26c08aee-768.webp +0 -0
- package/fixtures/content-model-v2/dist/index.html +8 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/_metadata.json +80 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/astro-D_GIpJIE.js +434 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/astro-D_GIpJIE.js.map +1 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_axobject-query.js +2702 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/audit-DM9Hkl5B.js +1534 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +1 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/embla-carousel.js +1389 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/embla-carousel.js.map +1 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/highlight-DnZ1ULOL.js +48 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +1 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/icons-RyebKi45.js +42 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/icons-RyebKi45.js.map +1 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/package.json +3 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +4 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/settings-DaU-gNnK.js +190 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/settings-DaU-gNnK.js.map +1 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/toolbar-63PA619H.js +458 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/toolbar-63PA619H.js.map +1 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/ui-library--bkcNSYz.js +1016 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +1 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/window-jPkpKVcy.js +32 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/window-jPkpKVcy.js.map +1 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/xray-BXCHpEJP.js +111 -0
- package/fixtures/content-model-v2/node_modules/.vite/deps/xray-BXCHpEJP.js.map +1 -0
- package/fixtures/content-model-v2/site/.norna/generated-images.json +210 -0
- package/fixtures/content-model-v2/site/config.mjs +11 -0
- package/fixtures/content-model-v2/site/content.md +84 -0
- package/fixtures/content-model-v2/site/images/card-section/adopt.svg +9 -0
- package/fixtures/content-model-v2/site/images/card-section/foster.svg +12 -0
- package/fixtures/content-model-v2/site/images/intro/detail.jpg +0 -0
- package/fixtures/content-model-v2/site/images/intro/duplicate.jpg +0 -0
- package/fixtures/content-model-v2/site/images/intro/hero.jpg +0 -0
- package/fixtures/content-model-v2/site/images/plain/image.jpg +0 -0
- package/fixtures/content-model-v2/site/images/timed/slide-one.jpg +0 -0
- package/fixtures/content-model-v2/site/images/timed/slide-three.jpg +0 -0
- package/fixtures/content-model-v2/site/images/timed/slide-two.jpg +0 -0
- package/fixtures/content-model-v2/site/routes/010-guide/images/intro/duplicate.jpg +0 -0
- package/fixtures/content-model-v2/site/routes/010-guide/route-content.md +17 -0
- package/fixtures/content-model-v2/site/theme.md +9 -0
- package/package.json +3 -2
- package/scripts/deploy-site.mjs +11 -4
- package/scripts/dev-local.mjs +6 -0
- package/scripts/generate-images.mjs +132 -56
- package/scripts/lib/image-dimensions.mjs +26 -0
- package/scripts/lib/norna-markdown-blocks.mjs +643 -0
- package/scripts/lib/site-content.mjs +31 -29
- package/scripts/show-typography.mjs +17 -9
- package/scripts/sync-content-sections.mjs +414 -412
- package/scripts/test-content-check.mjs +59 -45
- package/scripts/test-content-model-v2.mjs +1230 -0
- package/scripts/test-engine-commands.mjs +1 -1
- package/scripts/test-package-check.mjs +36 -8
- package/scripts/test-temporary-visibility.mjs +3 -6
- package/src/components/CardList.astro +77 -0
- package/src/components/GalleryGrid.astro +22 -12
- package/src/components/SiteNavigation.astro +5 -4
- package/src/components/SitePage.astro +9 -2
- package/src/components/SiteSection.astro +35 -13
- package/src/content.config.ts +4 -17
- package/src/lib/generatedImages.ts +50 -10
- package/src/lib/sectionContent.ts +130 -35
- package/src/styles/global.css +187 -0
- package/starters/basic/README.md +4 -2
- package/starters/basic/site/content.md +9 -11
- package/starters/project/README.md +7 -3
- package/starters/project/site/content.md +0 -9
- package/starters/project/site/routes/010-guide/route-content.md +0 -5
package/README.md
CHANGED
|
@@ -25,15 +25,16 @@ A `norna` site is file-driven:
|
|
|
25
25
|
base path, in `site/config.mjs`.
|
|
26
26
|
3. The site keeps site-wide visual theme defaults, including layout, image
|
|
27
27
|
sizing, font, typography, colors, and frame colors, in `site/theme.md`.
|
|
28
|
-
4. The site keeps homepage content, section order,
|
|
29
|
-
captions in
|
|
28
|
+
4. The site keeps homepage content, Markdown section order, managed media
|
|
29
|
+
blocks, alt text, captions, and optional section metadata in
|
|
30
|
+
`site/content.md`.
|
|
30
31
|
5. Optional route pages live under
|
|
31
|
-
`site/routes/<route-
|
|
32
|
+
`site/routes/<NNN-route-id>/route-content.md`.
|
|
32
33
|
6. Source images live under `site/images/<section-id>/` for the homepage, or
|
|
33
|
-
under `site/routes/<route-
|
|
34
|
+
under `site/routes/<NNN-route-id>/images/<section-id>/` for a route.
|
|
34
35
|
7. Static public files live under `site/public/`.
|
|
35
|
-
8. `norna` validates the files,
|
|
36
|
-
|
|
36
|
+
8. `norna` validates the files, prepares managed images, builds static Astro
|
|
37
|
+
pages, and can help publish the committed branch through GitHub Pages.
|
|
37
38
|
|
|
38
39
|
The default site directory is `site/`. Commands can also use another directory
|
|
39
40
|
with `NORNA_SITE_DIR` or `norna --site-dir <path>`. Without an explicit site
|
|
@@ -75,7 +76,7 @@ Actions use the same engine version.
|
|
|
75
76
|
|
|
76
77
|
- Create a site: [Getting Started](docs/getting-started.md)
|
|
77
78
|
- Understand required site files: [Site Structure](docs/site-structure.md)
|
|
78
|
-
- Edit sections and
|
|
79
|
+
- Edit sections and managed media blocks: [Content](docs/content.md)
|
|
79
80
|
- Configure a site: [Configuration](docs/configuration.md)
|
|
80
81
|
- Publish under a GitHub Pages project path:
|
|
81
82
|
[`site.basePath`](docs/configuration.md#sitebasepath)
|
package/astro.config.mjs
CHANGED
|
@@ -1,14 +1,87 @@
|
|
|
1
1
|
// @ts-check
|
|
2
|
+
import { execFile } from 'node:child_process';
|
|
2
3
|
import path from 'node:path';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
3
5
|
import { defineConfig } from 'astro/config';
|
|
4
6
|
import {
|
|
5
7
|
astroCacheDir,
|
|
6
8
|
astroDistDir,
|
|
7
9
|
astroPublicDir,
|
|
8
10
|
engineRoot,
|
|
11
|
+
generatedImagesManifestPath,
|
|
12
|
+
siteContentPath,
|
|
13
|
+
siteImagesDir,
|
|
14
|
+
siteProjectRoot,
|
|
15
|
+
siteRoutesDir,
|
|
9
16
|
} from './scripts/lib/site-paths.mjs';
|
|
10
17
|
import projectConfig from './scripts/lib/project-config.mjs';
|
|
11
18
|
|
|
19
|
+
const execFileAsync = promisify(execFile);
|
|
20
|
+
|
|
21
|
+
const isWithinDirectory = (parentDirectory, filePath) => {
|
|
22
|
+
const relativePath = path.relative(parentDirectory, filePath);
|
|
23
|
+
return relativePath === '' || (!relativePath.startsWith('..') && !path.isAbsolute(relativePath));
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const runGenerateImages = () => execFileAsync(process.execPath, [path.join(engineRoot, 'scripts', 'generate-images.mjs')], {
|
|
27
|
+
cwd: siteProjectRoot,
|
|
28
|
+
maxBuffer: 1024 * 1024 * 10,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const nornaGeneratedImagesWatcher = () => ({
|
|
32
|
+
name: 'norna-generated-images-watcher',
|
|
33
|
+
configureServer(server) {
|
|
34
|
+
const manifestPath = path.resolve(generatedImagesManifestPath);
|
|
35
|
+
const watchedSourcePaths = [
|
|
36
|
+
siteContentPath,
|
|
37
|
+
siteImagesDir,
|
|
38
|
+
siteRoutesDir,
|
|
39
|
+
].map((watchedPath) => path.resolve(watchedPath));
|
|
40
|
+
let refreshTimer;
|
|
41
|
+
let refreshPromise = Promise.resolve();
|
|
42
|
+
|
|
43
|
+
const isRelevantSourcePath = (changedPath) => {
|
|
44
|
+
const resolvedPath = path.resolve(changedPath);
|
|
45
|
+
return watchedSourcePaths.some((watchedPath) => (
|
|
46
|
+
resolvedPath === watchedPath || isWithinDirectory(watchedPath, resolvedPath)
|
|
47
|
+
));
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const refreshImages = () => {
|
|
51
|
+
refreshPromise = refreshPromise
|
|
52
|
+
.catch(() => {})
|
|
53
|
+
.then(async () => {
|
|
54
|
+
try {
|
|
55
|
+
await runGenerateImages();
|
|
56
|
+
} catch (error) {
|
|
57
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
58
|
+
server.config.logger.error(`Norna image refresh failed:\n${message}`);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const scheduleRefreshImages = (changedPath) => {
|
|
64
|
+
if (!isRelevantSourcePath(changedPath)) return;
|
|
65
|
+
|
|
66
|
+
clearTimeout(refreshTimer);
|
|
67
|
+
refreshTimer = setTimeout(refreshImages, 250);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
server.watcher.add(manifestPath);
|
|
71
|
+
server.watcher.add(watchedSourcePaths);
|
|
72
|
+
server.watcher.on('change', (changedPath) => {
|
|
73
|
+
if (path.resolve(changedPath) !== manifestPath) return;
|
|
74
|
+
|
|
75
|
+
server.ws.send({ type: 'full-reload' });
|
|
76
|
+
});
|
|
77
|
+
server.watcher.on('add', scheduleRefreshImages);
|
|
78
|
+
server.watcher.on('change', scheduleRefreshImages);
|
|
79
|
+
server.watcher.on('unlink', scheduleRefreshImages);
|
|
80
|
+
server.watcher.on('addDir', scheduleRefreshImages);
|
|
81
|
+
server.watcher.on('unlinkDir', scheduleRefreshImages);
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
|
|
12
85
|
// https://astro.build/config
|
|
13
86
|
export default defineConfig({
|
|
14
87
|
base: projectConfig.site.basePath,
|
|
@@ -16,4 +89,7 @@ export default defineConfig({
|
|
|
16
89
|
outDir: astroDistDir,
|
|
17
90
|
publicDir: astroPublicDir,
|
|
18
91
|
srcDir: path.join(engineRoot, 'src'),
|
|
92
|
+
vite: {
|
|
93
|
+
plugins: [nornaGeneratedImagesWatcher()],
|
|
94
|
+
},
|
|
19
95
|
});
|
package/bin/norna-cli.mjs
CHANGED
|
@@ -16,7 +16,7 @@ Commands:
|
|
|
16
16
|
dev:stop Stop local dev server
|
|
17
17
|
config:check Validate site/config.mjs
|
|
18
18
|
content:check Validate site/content.md and image references
|
|
19
|
-
content:sync
|
|
19
|
+
content:sync Move misplaced Norna-managed images and refresh generated images
|
|
20
20
|
typography presets Show built-in typography preset values
|
|
21
21
|
typography show Show resolved typography for the selected site
|
|
22
22
|
site:public Sync site/public/ to public/
|
|
@@ -128,9 +128,9 @@ try {
|
|
|
128
128
|
await runScript('scripts/check-config.mjs', rest);
|
|
129
129
|
} else if (command === 'content:check') {
|
|
130
130
|
await runScript('scripts/sync-content-sections.mjs', ['--check', ...rest]);
|
|
131
|
-
await runAstroInherit(['sync']);
|
|
132
131
|
} else if (command === 'content:sync') {
|
|
133
132
|
await runScript('scripts/sync-content-sections.mjs', ['--write', ...rest]);
|
|
133
|
+
await runScript('scripts/generate-images.mjs', rest);
|
|
134
134
|
} else if (command === 'typography:presets' || (command === 'typography' && subcommand === 'presets')) {
|
|
135
135
|
const scriptArgs = command === 'typography' ? subcommandRest : rest;
|
|
136
136
|
await runScript('scripts/show-typography.mjs', ['presets', ...scriptArgs]);
|
package/docs/README.md
CHANGED
|
@@ -38,9 +38,9 @@ For a quick reference:
|
|
|
38
38
|
- [Configuration](configuration.md) describes technical `site/config.mjs`
|
|
39
39
|
fields such as URL, base path, locale labels, footer, GitHub, and deploy
|
|
40
40
|
settings.
|
|
41
|
-
- [Content](content.md) describes page frontmatter,
|
|
42
|
-
|
|
43
|
-
content validation.
|
|
41
|
+
- [Content](content.md) describes page frontmatter, Markdown-authored
|
|
42
|
+
sections, optional section metadata, managed media blocks, temporary
|
|
43
|
+
sections, and content validation.
|
|
44
44
|
- [Theme](theme.md) describes site-wide layout, layout density, image sizing,
|
|
45
45
|
font, typography rhythm, presentation colors, frame colors, and inline styles.
|
|
46
46
|
- [Typography](typography.md) describes presets, rhythms, roles, overrides,
|
package/docs/commands.md
CHANGED
|
@@ -101,10 +101,11 @@ should normally mean the repository's complete publishable artifact, while
|
|
|
101
101
|
cache path.
|
|
102
102
|
- `config:check`: validates `site/config.mjs` against the runtime config
|
|
103
103
|
reader.
|
|
104
|
-
- `content:check`: validates section structure
|
|
105
|
-
|
|
106
|
-
- `content:sync` / `norna:sync`:
|
|
107
|
-
|
|
104
|
+
- `content:check`: validates section structure, managed media references,
|
|
105
|
+
inline styles, and common content mistakes.
|
|
106
|
+
- `content:sync` / `norna:sync`: moves misplaced referenced image files after
|
|
107
|
+
confirmation when the intended move is unambiguous. Moving files between the
|
|
108
|
+
homepage and routes, or between routes, requires a clean Git working tree.
|
|
108
109
|
- `typography presets`: prints the exact built-in values for typography
|
|
109
110
|
presets and rhythms.
|
|
110
111
|
- `typography show`: prints the selected site's resolved typography for the
|
|
@@ -112,8 +113,8 @@ should normally mean the repository's complete publishable artifact, while
|
|
|
112
113
|
and inherited page or section values are marked with `inherited: true`.
|
|
113
114
|
- `site:public`: copies `site/public/` into `site/.norna/public/` and
|
|
114
115
|
removes stale copied static files.
|
|
115
|
-
- `images`: generates WebP variants
|
|
116
|
-
`site/.norna/generated-images.json`.
|
|
116
|
+
- `images`: generates WebP variants for raster images, copies managed SVG
|
|
117
|
+
images, and writes `site/.norna/generated-images.json`.
|
|
117
118
|
- `engine:update [version|latest]`: updates the site repository's
|
|
118
119
|
`@janga/norna` dependency with `npm install --save-exact`, normalizes
|
|
119
120
|
`package-lock.json` for the pinned GitHub Actions Linux/npm environment, and
|
package/docs/configuration.md
CHANGED
|
@@ -39,7 +39,7 @@ site: {
|
|
|
39
39
|
`?`, `#`, or `//`.
|
|
40
40
|
- Consequence: use `/` for a root site or custom domain. Use
|
|
41
41
|
`/repository-name/` for a GitHub Pages project site without a custom domain.
|
|
42
|
-
Source Markdown can still use root-relative paths such as `/
|
|
42
|
+
Source Markdown can still use root-relative paths such as `/favicon.svg` or
|
|
43
43
|
`/getting-started/`; Norna prefixes them during rendering.
|
|
44
44
|
|
|
45
45
|
Examples:
|
|
@@ -141,7 +141,8 @@ Fields:
|
|
|
141
141
|
`Sections`.
|
|
142
142
|
- `locale.labels.menu`: mobile menu summary text, default `Menu`.
|
|
143
143
|
- `locale.labels.closeMenu`: reserved close-menu label, default `Close menu`.
|
|
144
|
-
- `locale.labels.gallery`:
|
|
144
|
+
- `locale.labels.gallery`: Norna managed media ARIA label prefix, default
|
|
145
|
+
`Images`.
|
|
145
146
|
|
|
146
147
|
Example:
|
|
147
148
|
|
package/docs/content.md
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
# Content
|
|
2
2
|
|
|
3
|
-
`site/content.md` is the homepage page file for a
|
|
4
|
-
|
|
5
|
-
the homepage.
|
|
3
|
+
`site/content.md` is the homepage page file for a Norna site. It contains page
|
|
4
|
+
metadata in frontmatter and the homepage content in Markdown.
|
|
6
5
|
|
|
7
|
-
Route pages use the same page
|
|
6
|
+
Route pages use the same page model in
|
|
8
7
|
`site/routes/<NNN-route-id>/route-content.md`. See [Routes](routes.md) for the
|
|
9
8
|
route-specific rules.
|
|
10
9
|
|
|
@@ -22,8 +21,7 @@ The Astro content schema validates these top-level fields in page files:
|
|
|
22
21
|
- `presentation`: optional page-level presentation overrides. See
|
|
23
22
|
[Theme](theme.md) and [Typography](typography.md).
|
|
24
23
|
- `frame`: optional page-level frame color source. See [Theme](theme.md).
|
|
25
|
-
- `sections`:
|
|
26
|
-
presentation overrides, and image rows.
|
|
24
|
+
- `sections`: optional section metadata keyed by section id.
|
|
27
25
|
|
|
28
26
|
Minimal homepage:
|
|
29
27
|
|
|
@@ -31,8 +29,6 @@ Minimal homepage:
|
|
|
31
29
|
---
|
|
32
30
|
title: My Site
|
|
33
31
|
description: A small Norna site.
|
|
34
|
-
sections:
|
|
35
|
-
- id: intro
|
|
36
32
|
---
|
|
37
33
|
|
|
38
34
|
## Intro {#intro}
|
|
@@ -42,99 +38,217 @@ Text...
|
|
|
42
38
|
|
|
43
39
|
## Sections
|
|
44
40
|
|
|
45
|
-
|
|
41
|
+
Markdown level 2 headings define the page sections and their order:
|
|
46
42
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
- `visible`: optional date window that controls whether the section is rendered.
|
|
50
|
-
- `presentation`: optional section-level visual overrides.
|
|
51
|
-
- `gallery`: optional array, defaulting to `[]`.
|
|
43
|
+
```md
|
|
44
|
+
## About {#about}
|
|
52
45
|
|
|
53
|
-
|
|
46
|
+
...
|
|
54
47
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
48
|
+
## Work {#work}
|
|
49
|
+
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
## Contact {#contact}
|
|
53
|
+
|
|
54
|
+
...
|
|
62
55
|
```
|
|
63
56
|
|
|
64
|
-
Every
|
|
65
|
-
explicit id:
|
|
57
|
+
Every section heading must have an explicit id:
|
|
66
58
|
|
|
67
59
|
```md
|
|
68
60
|
## Work {#work}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The id must match `^[a-z0-9-]+$`. It is used for anchors, navigation, image
|
|
64
|
+
directories, and optional section metadata. The visible section navigation
|
|
65
|
+
label comes from the Markdown heading text.
|
|
66
|
+
|
|
67
|
+
Markdown section content starts at the level 2 heading and continues until the
|
|
68
|
+
next level 2 heading. `###` and `####` headings are body subheadings within the
|
|
69
|
+
current section, not new sections.
|
|
70
|
+
|
|
71
|
+
## Section Metadata
|
|
69
72
|
|
|
70
|
-
|
|
73
|
+
Use `sections` only when a section needs structured metadata that is not
|
|
74
|
+
naturally expressed by Markdown, such as visibility or presentation overrides.
|
|
75
|
+
|
|
76
|
+
```yaml
|
|
77
|
+
sections:
|
|
78
|
+
work:
|
|
79
|
+
presentation:
|
|
80
|
+
typography:
|
|
81
|
+
preset: statement
|
|
71
82
|
```
|
|
72
83
|
|
|
73
|
-
|
|
84
|
+
Each `sections.<section-id>` key must match a Markdown heading id in the same
|
|
85
|
+
page file:
|
|
74
86
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
87
|
+
```md
|
|
88
|
+
## Work {#work}
|
|
89
|
+
```
|
|
78
90
|
|
|
79
|
-
|
|
80
|
-
from the
|
|
91
|
+
Do not list sections in frontmatter just to define order. Section order comes
|
|
92
|
+
from the Markdown heading order.
|
|
81
93
|
|
|
82
|
-
##
|
|
94
|
+
## Managed Media Blocks
|
|
83
95
|
|
|
84
|
-
|
|
96
|
+
Norna-managed local images and cards are written in Markdown fenced blocks at
|
|
97
|
+
the point where they should appear in the section. Markdown determines
|
|
98
|
+
placement: move the fenced block in the page file to move the rendered image,
|
|
99
|
+
carousel, or card list.
|
|
85
100
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
101
|
+
Use `norna-image-stack` for one or more stacked images:
|
|
102
|
+
|
|
103
|
+
````md
|
|
104
|
+
```norna-image-stack
|
|
105
|
+
- image: work.jpg
|
|
106
|
+
alt: A woven artwork on a white wall.
|
|
107
|
+
caption: Work in progress.
|
|
91
108
|
```
|
|
109
|
+
````
|
|
110
|
+
|
|
111
|
+
Use `norna-image-carousel` for a carousel:
|
|
112
|
+
|
|
113
|
+
````md
|
|
114
|
+
```norna-image-carousel
|
|
115
|
+
- image: first.jpg
|
|
116
|
+
alt: First image.
|
|
117
|
+
caption: First caption.
|
|
118
|
+
- image: second.jpg
|
|
119
|
+
alt: Second image.
|
|
120
|
+
caption: Second caption.
|
|
121
|
+
```
|
|
122
|
+
````
|
|
123
|
+
|
|
124
|
+
Use `norna-card-list` for a list of compact cards. Cards can include text,
|
|
125
|
+
managed images, links, and optional badge text:
|
|
126
|
+
|
|
127
|
+
````md
|
|
128
|
+
```norna-card-list
|
|
129
|
+
layout: image-top
|
|
130
|
+
flow: grid
|
|
131
|
+
size: m
|
|
132
|
+
width: normal
|
|
133
|
+
|
|
134
|
+
- title: Adopt
|
|
135
|
+
text: Give a dog a new home.
|
|
136
|
+
image: adopt.svg
|
|
137
|
+
link: /adopt/
|
|
138
|
+
badge-text: Recommended
|
|
139
|
+
- title: Foster
|
|
140
|
+
text: Help for a shorter period.
|
|
141
|
+
image: foster.svg
|
|
142
|
+
```
|
|
143
|
+
````
|
|
92
144
|
|
|
93
|
-
|
|
145
|
+
Card-list options:
|
|
94
146
|
|
|
95
|
-
- `
|
|
96
|
-
|
|
97
|
-
- `
|
|
98
|
-
- `
|
|
147
|
+
- `layout`: `image-top`, `image-left`, or `image-right`.
|
|
148
|
+
- `flow`: `grid` or `stack`.
|
|
149
|
+
- `size`: `s`, `m`, `l`, or `xl`.
|
|
150
|
+
- `width`: `text`, `narrow`, `normal`, or `wide`.
|
|
99
151
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
152
|
+
Each card starts with `- title: Card title`. Card fields use two spaces of
|
|
153
|
+
indentation. Supported fields are `text`, `image`, `link`, and `badge-text`.
|
|
154
|
+
Each card must include at least one of `text`, `image`, or `link`.
|
|
103
155
|
|
|
104
|
-
|
|
156
|
+
Each image entry supports:
|
|
105
157
|
|
|
106
|
-
|
|
158
|
+
- `image`: required filename matching
|
|
159
|
+
`^[a-z0-9][a-z0-9.-]*\.(jpe?g|png|svg)$`.
|
|
160
|
+
It must be a filename, not a path.
|
|
161
|
+
- `alt`: optional alt text. If omitted, Norna renders an empty alt attribute.
|
|
162
|
+
- `caption`: optional caption.
|
|
107
163
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
caption: "Second caption."
|
|
164
|
+
Start every image entry in image stacks and carousels with
|
|
165
|
+
`- image: filename.jpg`. Optional fields use two spaces of indentation:
|
|
166
|
+
|
|
167
|
+
````md
|
|
168
|
+
```norna-image-stack
|
|
169
|
+
- image: filename.jpg
|
|
170
|
+
alt: Optional alt text.
|
|
171
|
+
caption: Optional caption.
|
|
117
172
|
```
|
|
173
|
+
````
|
|
118
174
|
|
|
119
|
-
|
|
120
|
-
|
|
175
|
+
Use three or more matching backticks or tildes for fenced blocks. If you need
|
|
176
|
+
to document a Norna image block inside another Markdown code sample, make the
|
|
177
|
+
outer fence longer than the inner fence:
|
|
178
|
+
|
|
179
|
+
````md
|
|
180
|
+
````
|
|
181
|
+
```norna-image-stack
|
|
182
|
+
- image: filename.jpg
|
|
183
|
+
```
|
|
184
|
+
````
|
|
185
|
+
````
|
|
121
186
|
|
|
122
187
|
`content:check` warns when carousel images have different aspect ratios. Exact
|
|
123
188
|
matching proportions are recommended because mixed proportions can make the
|
|
124
189
|
layout move while the user changes slides.
|
|
125
190
|
|
|
191
|
+
SVG files are allowed in image stacks and carousels. When an SVG has a
|
|
192
|
+
`viewBox` or numeric `width` and `height`, Norna uses that ratio in the same
|
|
193
|
+
layout logic as raster images. SVG files without an intrinsic aspect ratio are
|
|
194
|
+
rendered directly, but carousel usage produces a warning because stable slide
|
|
195
|
+
sizing is less predictable.
|
|
196
|
+
|
|
197
|
+
## Image Files
|
|
198
|
+
|
|
199
|
+
Homepage images live under:
|
|
200
|
+
|
|
201
|
+
```text
|
|
202
|
+
site/images/<section-id>/
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Route images live under:
|
|
206
|
+
|
|
207
|
+
```text
|
|
208
|
+
site/routes/<NNN-route-id>/images/<section-id>/
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Image references in Norna managed media blocks use only the filename:
|
|
212
|
+
|
|
213
|
+
````md
|
|
214
|
+
```norna-image-stack
|
|
215
|
+
- image: portrait.jpg
|
|
216
|
+
```
|
|
217
|
+
````
|
|
218
|
+
|
|
219
|
+
If `portrait.jpg` is referenced from `## Team {#team}`, the expected homepage
|
|
220
|
+
location is `site/images/team/portrait.jpg`.
|
|
221
|
+
|
|
222
|
+
Filenames do not have to be globally unique for the site to be valid. Automatic
|
|
223
|
+
sync only moves files when the filename identifies exactly one source candidate
|
|
224
|
+
across the site's page and route image roots. If more than one candidate
|
|
225
|
+
exists, Norna reports the ambiguity instead of guessing.
|
|
226
|
+
|
|
227
|
+
## Markdown Images
|
|
228
|
+
|
|
229
|
+
Markdown image syntax is allowed for external images and public static assets:
|
|
230
|
+
|
|
231
|
+
```md
|
|
232
|
+

|
|
233
|
+

|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Relative local Markdown images such as `` are not
|
|
237
|
+
managed by Norna. Use `norna-image-stack`, `norna-image-carousel`, or
|
|
238
|
+
`norna-card-list` for local site images that should be validated, processed and
|
|
239
|
+
synced.
|
|
240
|
+
|
|
126
241
|
## Temporary Sections
|
|
127
242
|
|
|
128
|
-
Use `sections
|
|
129
|
-
date window:
|
|
243
|
+
Use `sections.<section-id>.visible` for sections that should be rendered only
|
|
244
|
+
during a date window:
|
|
130
245
|
|
|
131
246
|
```yaml
|
|
132
247
|
sections:
|
|
133
|
-
|
|
248
|
+
exhibition:
|
|
134
249
|
visible:
|
|
135
250
|
from: "2026-08-01"
|
|
136
251
|
until: "2026-09-16"
|
|
137
|
-
gallery: []
|
|
138
252
|
```
|
|
139
253
|
|
|
140
254
|
`from` is inclusive. `until` is exclusive. With the example above, the section
|
|
@@ -156,17 +270,6 @@ NORNA_TODAY=2026-08-15 npm run norna:build
|
|
|
156
270
|
|
|
157
271
|
## Markdown Text
|
|
158
272
|
|
|
159
|
-
Section Markdown starts at the matching level 2 heading and continues until the
|
|
160
|
-
next level 2 heading.
|
|
161
|
-
|
|
162
|
-
```md
|
|
163
|
-
## Intro {#intro}
|
|
164
|
-
|
|
165
|
-
Paragraph text.
|
|
166
|
-
|
|
167
|
-
Another paragraph.
|
|
168
|
-
```
|
|
169
|
-
|
|
170
273
|
Inline styles use this Markdown form:
|
|
171
274
|
|
|
172
275
|
```md
|
|
@@ -184,16 +287,17 @@ Run:
|
|
|
184
287
|
npm run norna:content:check
|
|
185
288
|
```
|
|
186
289
|
|
|
187
|
-
This checks section
|
|
188
|
-
files, misplaced referenced images, duplicate image references,
|
|
189
|
-
|
|
190
|
-
|
|
290
|
+
This checks section heading ids, section metadata, duplicate image names,
|
|
291
|
+
missing image files, misplaced referenced images, duplicate image references,
|
|
292
|
+
invalid managed media blocks, unreferenced images, undefined inline styles,
|
|
293
|
+
Markdown image references to unmanaged local files, and common frontmatter
|
|
294
|
+
indentation and structure mistakes.
|
|
191
295
|
|
|
192
296
|
Frontmatter uses YAML indentation. Use ordinary spaces, not tabs or
|
|
193
297
|
non-breaking spaces. `content:check` reports a focused error when indentation is
|
|
194
298
|
invalid, when a key is indented under a line that already has a value, or when a
|
|
195
|
-
known nested key such as
|
|
196
|
-
|
|
299
|
+
known nested key such as section-specific `typography` appears at the top
|
|
300
|
+
level:
|
|
197
301
|
|
|
198
302
|
```yaml
|
|
199
303
|
presentation:
|
|
@@ -205,14 +309,7 @@ presentation:
|
|
|
205
309
|
```
|
|
206
310
|
|
|
207
311
|
Top-level page frontmatter may contain only `title`, `description`,
|
|
208
|
-
`navigation`, `presentation`, `frame`, and `sections`.
|
|
209
|
-
under one `sections[]` item:
|
|
210
|
-
|
|
211
|
-
```yaml
|
|
212
|
-
sections:
|
|
213
|
-
- id: work
|
|
214
|
-
gallery: []
|
|
215
|
-
```
|
|
312
|
+
`navigation`, `presentation`, `frame`, and `sections`.
|
|
216
313
|
|
|
217
314
|
Run:
|
|
218
315
|
|
|
@@ -220,6 +317,20 @@ Run:
|
|
|
220
317
|
npm run norna:sync
|
|
221
318
|
```
|
|
222
319
|
|
|
223
|
-
This
|
|
224
|
-
|
|
225
|
-
|
|
320
|
+
This moves referenced image files into the section directory shown by the
|
|
321
|
+
Markdown placement. It prompts before writing unless `--yes` is passed.
|
|
322
|
+
|
|
323
|
+
`content:sync` is intentionally conservative. It only moves a file when the
|
|
324
|
+
filename identifies exactly one matching source candidate across the site's
|
|
325
|
+
page and route image roots, and when the move will not break another reference.
|
|
326
|
+
If the intended move is ambiguous, rename or move the file manually and run
|
|
327
|
+
`content:check` again.
|
|
328
|
+
|
|
329
|
+
Duplicate filenames are allowed when files already live where their Markdown
|
|
330
|
+
references expect them. Automatic relocation only requires site-wide filename
|
|
331
|
+
uniqueness for the file being moved.
|
|
332
|
+
|
|
333
|
+
When `content:sync` needs to move an image between the homepage and a route, or
|
|
334
|
+
between two routes, the Git working tree must be clean before the write. This
|
|
335
|
+
keeps cross-route sync easy to roll back. `content:check` only reports issues
|
|
336
|
+
and does not require a clean working tree.
|
|
@@ -26,7 +26,7 @@ site repository. It should not be a showcase for every feature.
|
|
|
26
26
|
An example site is a runnable site that demonstrates one or more features.
|
|
27
27
|
|
|
28
28
|
Examples can be richer than the starter. They may show routes, navigation,
|
|
29
|
-
typography presets, typography rhythms,
|
|
29
|
+
typography presets, typography rhythms, image stacks, image carousels, inline
|
|
30
30
|
styles, and site-specific configuration choices.
|
|
31
31
|
|
|
32
32
|
### Documentation Site
|
|
@@ -9,13 +9,13 @@ This document is for work on the reusable `norna` package itself.
|
|
|
9
9
|
- `scripts/lib/site-paths.mjs`: engine/site path resolution.
|
|
10
10
|
- `scripts/lib/project-config.mjs`: `site/config.mjs` validation and defaults.
|
|
11
11
|
- `scripts/sync-content-sections.mjs`: content validation and sync behavior.
|
|
12
|
-
- `scripts/generate-images.mjs`:
|
|
12
|
+
- `scripts/generate-images.mjs`: managed image pipeline and manifest.
|
|
13
13
|
- `scripts/sync-site-public.mjs`: static public file sync.
|
|
14
14
|
- `scripts/deploy-site.mjs`: deploy and deploy:commit behavior.
|
|
15
15
|
- `scripts/watch-pages-deploy.mjs`: GitHub Pages workflow monitor.
|
|
16
16
|
- `src/content.config.ts`: Astro content schema.
|
|
17
|
-
- `src/components/` and `src/layouts/`: rendered page, navigation, image
|
|
18
|
-
and layout.
|
|
17
|
+
- `src/components/` and `src/layouts/`: rendered page, navigation, image
|
|
18
|
+
blocks, and layout.
|
|
19
19
|
- `tests/`: Playwright navigation diagnostics.
|
|
20
20
|
- `fixtures/basic/site/`: minimal site used for engine checks.
|
|
21
21
|
- `starters/basic/`: copyable site starter.
|