@janga/norna 0.7.26 → 0.7.27

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.
@@ -246,6 +246,6 @@ console.log(`${action} ${kind} "${title}" at ${toDisplayPath(destination)}.`);
246
246
  if (kind === 'page') {
247
247
  console.log(`URL: /${pagePath}/`);
248
248
  } else {
249
- console.log('This navigation category has no page or URL of its own.');
249
+ console.log('This navigation category has no authored content page. Norna generates its destination from the child pages.');
250
250
  console.log(`Child URL prefix: /${pagePath}/`);
251
251
  }
@@ -1,8 +1,39 @@
1
1
  import packageJson from '../../package.json' with { type: 'json' };
2
+ import routes from './documentation-routes.json' with { type: 'json' };
3
+
4
+ const websiteRoot = 'https://janga.github.io/norna/reference/';
5
+ const repositoryRoot = 'https://github.com/janga/norna/blob/';
6
+
7
+ // Release tags through 0.7.26 contain the reference under docs/. Newer tags
8
+ // contain its single authored source in the documentation site's page tree.
9
+ export const usesLegacyReference = (version) => {
10
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(version ?? '');
11
+ if (!match) return false;
12
+ const [major, minor, patch] = match.slice(1).map(Number);
13
+ return major === 0 && (minor < 7 || (minor === 7 && patch <= 26));
14
+ };
15
+
16
+ const referenceDestination = (file, anchor) => {
17
+ const target = routes.legacy[`${file}#${anchor}`] ?? routes.legacy[file];
18
+ if (!target) throw new Error(`Unknown documentation destination: ${file}${anchor ? `#${anchor}` : ''}`);
19
+ const [route, targetAnchor] = target.split('#');
20
+ const source = routes.sources[route];
21
+ if (!source) throw new Error(`Missing documentation source for ${route}`);
22
+ return { route, source, anchor: targetAnchor, url: websiteRoot + target };
23
+ };
24
+
25
+ export const documentationLinkForVersion = (version, label, file, anchor) => {
26
+ const destination = referenceDestination(file, anchor);
27
+ const reference = version ? `v${version}` : 'main';
28
+ const source = usesLegacyReference(version)
29
+ ? `docs/${file}${anchor ? `#${anchor}` : ''}`
30
+ : `${destination.source}${destination.anchor ? `#${destination.anchor}` : ''}`;
31
+ const sourceLabel = version ? `Norna ${version} source reference` : 'Reference source';
32
+ return `[${label} (current)](${destination.url})\n\n[${sourceLabel}](${repositoryRoot}${reference}/${source})`;
33
+ };
2
34
 
3
35
  export const documentationRef = `v${packageJson.version}`;
4
- export const documentationRoot = `https://github.com/janga/norna/blob/${documentationRef}/docs`;
5
36
 
6
37
  export const documentationLink = (label, file, anchor) => (
7
- `[${label}](${documentationRoot}/${file}${anchor ? `#${anchor}` : ''})`
38
+ documentationLinkForVersion(packageJson.version, label, file, anchor)
8
39
  );
@@ -0,0 +1,222 @@
1
+ {
2
+ "sources": {
3
+ "site/files/": "site/pages/032-reference/pages/010-site/pages/010-files/content.md",
4
+ "site/pages/": "site/pages/032-reference/pages/010-site/pages/020-pages/content.md",
5
+ "site/urls/": "site/pages/032-reference/pages/010-site/pages/030-urls/content.md",
6
+ "site/metadata/": "site/pages/032-reference/pages/010-site/pages/040-metadata/content.md",
7
+ "site/images/": "site/pages/032-reference/pages/010-site/pages/050-images/content.md",
8
+ "site/public-files/": "site/pages/032-reference/pages/010-site/pages/060-public-files/content.md",
9
+ "site/requirements/": "site/pages/032-reference/pages/010-site/pages/070-requirements/content.md",
10
+ "configuration/site/": "site/pages/032-reference/pages/020-configuration/pages/010-site/content.md",
11
+ "configuration/language/": "site/pages/032-reference/pages/020-configuration/pages/020-language/content.md",
12
+ "configuration/navigation/": "site/pages/032-reference/pages/020-configuration/pages/030-navigation/content.md",
13
+ "configuration/search/": "site/pages/032-reference/pages/020-configuration/pages/040-search/content.md",
14
+ "configuration/source-links/": "site/pages/032-reference/pages/020-configuration/pages/050-source-links/content.md",
15
+ "configuration/theme/": "site/pages/032-reference/pages/020-configuration/pages/060-theme/content.md",
16
+ "configuration/presets/": "site/pages/032-reference/pages/020-configuration/pages/065-presets/content.md",
17
+ "configuration/palettes/": "site/pages/032-reference/pages/020-configuration/pages/070-palettes/content.md",
18
+ "configuration/appearance/": "site/pages/032-reference/pages/020-configuration/pages/080-appearance/content.md",
19
+ "configuration/layout/": "site/pages/032-reference/pages/020-configuration/pages/090-layout/content.md",
20
+ "configuration/images/": "site/pages/032-reference/pages/020-configuration/pages/100-images/content.md",
21
+ "configuration/typography/": "site/pages/032-reference/pages/020-configuration/pages/110-typography/content.md",
22
+ "configuration/sections/": "site/pages/032-reference/pages/020-configuration/pages/120-sections/content.md",
23
+ "configuration/shared-content/": "site/pages/032-reference/pages/020-configuration/pages/130-shared-content/content.md",
24
+ "content/markdown/": "site/pages/032-reference/pages/030-content/pages/010-markdown/content.md",
25
+ "content/structured-blocks/": "site/pages/032-reference/pages/030-content/pages/015-structured-blocks/content.md",
26
+ "content/images/": "site/pages/032-reference/pages/030-content/pages/020-images/content.md",
27
+ "content/cards/": "site/pages/032-reference/pages/030-content/pages/030-cards/content.md",
28
+ "content/child-lists/": "site/pages/032-reference/pages/030-content/pages/040-child-lists/content.md",
29
+ "content/callouts/": "site/pages/032-reference/pages/030-content/pages/050-callouts/content.md",
30
+ "content/tabs/": "site/pages/032-reference/pages/030-content/pages/060-tabs/content.md",
31
+ "content/details/": "site/pages/032-reference/pages/030-content/pages/070-details/content.md",
32
+ "content/tables/": "site/pages/032-reference/pages/030-content/pages/080-tables/content.md",
33
+ "content/code/": "site/pages/032-reference/pages/030-content/pages/090-code/content.md",
34
+ "content/sidenotes/": "site/pages/032-reference/pages/030-content/pages/100-sidenotes/content.md",
35
+ "content/footnotes/": "site/pages/032-reference/pages/030-content/pages/110-footnotes/content.md",
36
+ "commands/invocation/": "site/pages/032-reference/pages/040-commands/pages/010-invocation/content.md",
37
+ "commands/init/": "site/pages/032-reference/pages/040-commands/pages/020-init/content.md",
38
+ "commands/create/": "site/pages/032-reference/pages/040-commands/pages/030-create/content.md",
39
+ "commands/move/": "site/pages/032-reference/pages/040-commands/pages/040-move/content.md",
40
+ "commands/validate/": "site/pages/032-reference/pages/040-commands/pages/050-validate/content.md",
41
+ "commands/navigation/": "site/pages/032-reference/pages/040-commands/pages/055-navigation/content.md",
42
+ "commands/development/": "site/pages/032-reference/pages/040-commands/pages/060-development/content.md",
43
+ "commands/build/": "site/pages/032-reference/pages/040-commands/pages/070-build/content.md",
44
+ "commands/publishing/": "site/pages/032-reference/pages/040-commands/pages/080-publishing/content.md",
45
+ "commands/theme/": "site/pages/032-reference/pages/040-commands/pages/090-theme/content.md",
46
+ "commands/engine/": "site/pages/032-reference/pages/040-commands/pages/100-engine/content.md",
47
+ "commands/migration-audit/": "site/pages/032-reference/pages/040-commands/pages/110-migration-audit/content.md",
48
+ "reader/display/": "site/pages/032-reference/pages/050-reader/pages/010-display/content.md",
49
+ "reader/accessibility/": "site/pages/032-reference/pages/050-reader/pages/020-accessibility/content.md",
50
+ "workflows/editor/": "site/pages/032-reference/pages/060-workflows/pages/010-editor/content.md",
51
+ "workflows/editor-ai-suggestions/": "site/pages/032-reference/pages/060-workflows/pages/015-editor-ai-suggestions/content.md",
52
+ "workflows/publishing/": "site/pages/032-reference/pages/060-workflows/pages/020-publishing/content.md",
53
+ "workflows/embedded-publishing/": "site/pages/032-reference/pages/060-workflows/pages/030-embedded-publishing/content.md",
54
+ "workflows/upgrading/": "site/pages/032-reference/pages/060-workflows/pages/040-upgrading/content.md",
55
+ "workflows/legacy-source/": "site/pages/032-reference/pages/060-workflows/pages/050-legacy-source/content.md"
56
+ },
57
+ "legacy": {
58
+ "site-files.md": "site/files/",
59
+ "pages.md": "site/pages/",
60
+ "configuration.md": "configuration/site/",
61
+ "content.md": "content/markdown/",
62
+ "theme.md": "configuration/theme/",
63
+ "typography.md": "configuration/typography/",
64
+ "images-and-metadata.md": "site/images/",
65
+ "public-files.md": "site/public-files/",
66
+ "sitewide-content.md": "configuration/shared-content/",
67
+ "commands.md": "commands/invocation/",
68
+ "local-development.md": "commands/development/",
69
+ "requirements.md": "site/requirements/",
70
+ "publishing.md": "workflows/publishing/",
71
+ "upgrading.md": "workflows/upgrading/",
72
+ "editor-support.md": "workflows/editor/",
73
+ "client-javascript.md": "reader/accessibility/",
74
+ "presentation-guarantees.md": "reader/accessibility/",
75
+ "site-files.md#root-files": "site/files/#source-layout",
76
+ "site-files.md#page-hierarchy": "site/files/#page-folders",
77
+ "site-files.md#public-files": "site/public-files/",
78
+ "site-files.md#selecting-the-site-directory": "commands/invocation/#site-selection",
79
+ "site-files.md#versioned-files": "site/files/#generated-files",
80
+ "site-files.md#generated-files": "site/files/#generated-files",
81
+ "pages.md#homepage-and-top-level-entries": "site/pages/#page-or-category",
82
+ "pages.md#choose-a-section-page-or-category": "site/pages/#page-or-category",
83
+ "pages.md#review-an-existing-hierarchy": "commands/navigation/",
84
+ "pages.md#navigation-categories": "site/pages/#page-or-category",
85
+ "pages.md#opening-a-category-url": "site/pages/#opening-a-category-url",
86
+ "pages.md#reachable-content-and-stable-links": "site/pages/#opening-a-category-url",
87
+ "pages.md#nested-pages": "site/pages/#page-or-category",
88
+ "pages.md#directory-names-order-and-urls": "site/pages/#names-and-order",
89
+ "pages.md#preserve-old-page-urls": "site/urls/#keep-an-old-url",
90
+ "pages.md#move-or-reconcile-a-page": "commands/move/",
91
+ "pages.md#create-pages-and-categories": "commands/create/",
92
+ "pages.md#page-content": "content/markdown/",
93
+ "pages.md#navigation": "configuration/navigation/",
94
+ "pages.md#focus-reading": "reader/display/#focus-reading",
95
+ "pages.md#current-reading-position": "configuration/navigation/#orientation-and-long-trees",
96
+ "pages.md#page-images": "site/images/",
97
+ "pages.md#page-themes": "configuration/theme/#page-themes",
98
+ "configuration.md#url": "configuration/site/#public-url",
99
+ "configuration.md#language": "configuration/language/",
100
+ "configuration.md#what-the-setting-changes": "configuration/language/",
101
+ "configuration.md#choose-a-language-tag": "configuration/language/",
102
+ "configuration.md#supported-languages": "configuration/language/",
103
+ "configuration.md#search-and-translation-boundaries": "configuration/language/",
104
+ "configuration.md#edit-link": "configuration/source-links/",
105
+ "configuration.md#local-editor": "configuration/source-links/#destination-rules",
106
+ "configuration.md#remote-source-host": "configuration/source-links/#fields",
107
+ "configuration.md#navigation": "configuration/navigation/",
108
+ "configuration.md#navigationmode": "configuration/navigation/",
109
+ "configuration.md#one-page-sections": "configuration/navigation/",
110
+ "configuration.md#top-level-pages-top": "configuration/navigation/",
111
+ "configuration.md#child-pages-or-categories-tree": "configuration/navigation/",
112
+ "configuration.md#search": "configuration/search/",
113
+ "configuration.md#scrollbehavior": "configuration/site/#anchor-movement",
114
+ "configuration.md#publishing-discovery": "commands/publishing/",
115
+ "configuration.md#site-directory-selection": "commands/invocation/#site-selection",
116
+ "content.md#page-title-and-frontmatter": "site/metadata/",
117
+ "content.md#sections": "content/markdown/#page-title-and-sections",
118
+ "content.md#internal-links": "site/urls/",
119
+ "content.md#norna-blocks": "content/structured-blocks/",
120
+ "content.md#child-page-list": "content/child-lists/",
121
+ "content.md#image-stack": "content/images/#image-stack",
122
+ "content.md#image-carousel": "content/images/#image-carousel",
123
+ "content.md#card-list": "content/cards/",
124
+ "content.md#image-files": "site/images/",
125
+ "content.md#markdown-images": "content/markdown/#markdown-images",
126
+ "content.md#markdown-text": "content/markdown/#standard-syntax-and-extensions",
127
+ "content.md#semantic-callouts": "content/callouts/",
128
+ "content.md#tabs": "content/tabs/",
129
+ "content.md#details-disclosures": "content/details/",
130
+ "content.md#tables": "content/tables/",
131
+ "content.md#row-headings": "content/tables/#row-headings",
132
+ "content.md#sorting": "content/tables/#sorting",
133
+ "content.md#width-and-scrolling": "content/tables/#width-and-sticky-context",
134
+ "content.md#code-blocks": "content/code/",
135
+ "content.md#side-notes": "content/sidenotes/",
136
+ "content.md#reference-footnotes": "content/footnotes/",
137
+ "content.md#validation-and-sync": "commands/validate/",
138
+ "images-and-metadata.md#managed-source-images": "site/images/#source-files",
139
+ "images-and-metadata.md#managed-image-presentation": "configuration/images/",
140
+ "images-and-metadata.md#captions-for-tall-images": "configuration/images/#tall-captions-and-inspection",
141
+ "images-and-metadata.md#inspecting-detailed-images": "configuration/images/#tall-captions-and-inspection",
142
+ "images-and-metadata.md#markdown-images": "content/markdown/#markdown-images",
143
+ "images-and-metadata.md#generated-variants-and-static-svg": "site/images/#variants-and-svg",
144
+ "images-and-metadata.md#manifest": "site/images/#manifest-and-cache",
145
+ "images-and-metadata.md#metadata-behavior": "site/images/#credits-and-provenance",
146
+ "images-and-metadata.md#ai-generated-images": "site/images/#credits-and-provenance",
147
+ "images-and-metadata.md#github-actions-cache": "site/images/#manifest-and-cache",
148
+ "public-files.md#navigation-logo": "site/public-files/#recognized-files",
149
+ "public-files.md#browser-icons": "site/public-files/#recognized-files",
150
+ "public-files.md#social-sharing-image": "site/public-files/#social-previews",
151
+ "public-files.md#github-pages-custom-domain": "site/public-files/#downloads-and-hosting-files",
152
+ "public-files.md#generated-sitemap": "site/public-files/#generated-sitemap",
153
+ "public-files.md#generated-404-page": "site/public-files/#generated-404-page",
154
+ "public-files.md#other-static-files": "site/public-files/#downloads-and-hosting-files",
155
+ "public-files.md#generated-copy": "site/public-files/#preparation-and-reserved-paths",
156
+ "theme.md#theme-presets": "configuration/presets/",
157
+ "theme.md#choose-a-preset": "configuration/theme/#presets-and-root-overrides",
158
+ "theme.md#portfolio": "configuration/presets/#portfolio",
159
+ "theme.md#documentation": "configuration/presets/#documentation",
160
+ "theme.md#project": "configuration/presets/#project",
161
+ "theme.md#statement": "configuration/presets/#statement",
162
+ "theme.md#overrides": "configuration/theme/#presets-and-root-overrides",
163
+ "theme.md#export-a-preset": "commands/theme/#list-and-export-presets",
164
+ "theme.md#layout": "configuration/layout/",
165
+ "theme.md#image-sizing": "configuration/images/",
166
+ "theme.md#content-block-defaults": "content/cards/#width",
167
+ "theme.md#typography": "configuration/typography/",
168
+ "theme.md#palette-and-appearance": "configuration/palettes/",
169
+ "theme.md#palette": "configuration/palettes/",
170
+ "theme.md#appearance": "configuration/appearance/",
171
+ "theme.md#reader-display-controls": "reader/display/",
172
+ "theme.md#corners": "configuration/theme/#site-wide-settings",
173
+ "theme.md#section-backgrounds": "configuration/sections/",
174
+ "theme.md#page-themes": "configuration/theme/#page-themes",
175
+ "typography.md#typography-profiles": "configuration/typography/#font-profile-and-rhythm",
176
+ "typography.md#typography-rhythm": "configuration/typography/#font-profile-and-rhythm",
177
+ "typography.md#configuration-shape": "configuration/typography/#override-fields",
178
+ "typography.md#override-values": "configuration/typography/#override-fields",
179
+ "typography.md#page-text-width": "configuration/layout/",
180
+ "sitewide-content.md#logo": "configuration/shared-content/#logo",
181
+ "sitewide-content.md#banners": "configuration/shared-content/#banners",
182
+ "sitewide-content.md#footer": "configuration/shared-content/#footer",
183
+ "commands.md#cli-commands": "commands/invocation/#command-map",
184
+ "commands.md#starter-npm-scripts": "commands/invocation/#project-npm-scripts",
185
+ "commands.md#create-pages-and-categories": "commands/create/",
186
+ "commands.md#move-a-page": "commands/move/",
187
+ "commands.md#review-navigation-structure": "commands/navigation/",
188
+ "commands.md#command-summary": "commands/invocation/#command-map",
189
+ "local-development.md#start-local-preview": "commands/development/",
190
+ "local-development.md#test-on-a-phone": "commands/development/#lan-preview",
191
+ "local-development.md#manage-preview": "commands/development/#lifecycle-commands",
192
+ "local-development.md#when-startup-fails": "commands/development/#preparation-failures-and-stale-output",
193
+ "local-development.md#rebuild-stale-preview": "commands/development/#preparation-failures-and-stale-output",
194
+ "publishing.md#requirements": "workflows/publishing/",
195
+ "publishing.md#github-pages-workflow": "workflows/publishing/#configure-and-publish",
196
+ "publishing.md#publish-an-embedded-site-beside-another-static-build": "workflows/embedded-publishing/",
197
+ "publishing.md#deploy-an-already-committed-branch": "commands/publishing/#deploy-committed-sources",
198
+ "publishing.md#deploy-with-a-generated-commit": "commands/publishing/#build-and-create-a-commit",
199
+ "publishing.md#watch-a-deploy": "commands/publishing/#monitor-a-workflow",
200
+ "upgrading.md#before-changing-the-site": "workflows/upgrading/#establish-the-baseline",
201
+ "upgrading.md#safest-pre-10-migration": "workflows/upgrading/#older-source-models",
202
+ "upgrading.md#current-source-layout": "workflows/legacy-source/",
203
+ "upgrading.md#routine-engine-updates": "workflows/upgrading/#update-and-review",
204
+ "upgrading.md#validate-the-migration": "workflows/upgrading/#update-and-review",
205
+ "editor-support.md#install-the-extension": "workflows/editor/#install-and-verify",
206
+ "editor-support.md#what-you-see-in-vs-code": "workflows/editor/#install-and-verify",
207
+ "editor-support.md#requirements": "workflows/editor/#install-and-verify",
208
+ "editor-support.md#recognized-files": "workflows/editor/#recognized-files",
209
+ "editor-support.md#configuration-help": "workflows/editor/#suggestions-and-diagnostics",
210
+ "editor-support.md#markdown-and-image-help": "workflows/editor/#managed-images-and-quick-fixes",
211
+ "editor-support.md#refresh-after-an-engine-change": "workflows/editor/#refresh-and-troubleshoot",
212
+ "editor-support.md#troubleshooting": "workflows/editor/#refresh-and-troubleshoot",
213
+ "editor-support.md#turn-off-ai-suggestions-while-keeping-intellisense": "workflows/editor-ai-suggestions/",
214
+ "client-javascript.md#feature-contract": "reader/accessibility/#without-javascript",
215
+ "client-javascript.md#choosing-static-alternatives": "reader/accessibility/#without-javascript",
216
+ "presentation-guarantees.md#color-and-focus": "reader/accessibility/#color-focus-and-controls",
217
+ "presentation-guarantees.md#typography-and-reading-width": "reader/accessibility/#reading-and-reflow",
218
+ "presentation-guarantees.md#reflow-and-text-adaptation": "reader/accessibility/#reading-and-reflow",
219
+ "presentation-guarantees.md#controls-and-user-preferences": "reader/display/",
220
+ "presentation-guarantees.md#author-responsibilities": "reader/accessibility/#author-responsibilities"
221
+ }
222
+ }
@@ -94,6 +94,28 @@ export const decodePageDirectoryPath = (pageEntryPath, label = 'page entry path'
94
94
  return pageDirectories.join('/pages/');
95
95
  };
96
96
 
97
+ export const getSiteEntryPrefix = (siteDirLabel) => siteDirLabel
98
+ .replace(/^[./\\]+/, '')
99
+ .replace(/[^a-zA-Z0-9-]+/g, '-')
100
+ .replace(/^-+|-+$/g, '') || 'site';
101
+
102
+ const getPageEntryPrefix = (siteDirLabel) => `${getSiteEntryPrefix(siteDirLabel)}-page-`;
103
+
104
+ export const encodePageEntryId = (siteDirLabel, pageDirectoryPath) => (
105
+ `${getPageEntryPrefix(siteDirLabel)}${encodePageDirectoryPath(pageDirectoryPath)}`
106
+ );
107
+
108
+ export const decodePageEntryId = (siteDirLabel, entryId) => {
109
+ const prefix = getPageEntryPrefix(siteDirLabel);
110
+ if (!entryId.startsWith(prefix)) return null;
111
+
112
+ try {
113
+ return parsePageDirectoryPath(decodePageDirectoryPath(entryId.slice(prefix.length))).pageDirectory;
114
+ } catch {
115
+ return null;
116
+ }
117
+ };
118
+
97
119
  export const getPageDirectoryAncestors = (pageDirectoryPath) => {
98
120
  const { pageDirectories } = parsePageDirectoryPath(pageDirectoryPath);
99
121
  return pageDirectories.map((_, index) => (
@@ -209,7 +209,7 @@ const pageAlias = z.string().regex(
209
209
  'Use an old site-relative Norna page URL such as "/guides/install/". Start and end with / and use lowercase letters, numbers, and single hyphens.',
210
210
  ).describe('Previous site-relative URL that permanently identifies this page.');
211
211
  const pageMetadata = z.object({
212
- description: z.string().min(1).optional().describe('Optional page-specific meta description.'),
212
+ description: z.string().min(1).optional().describe('Page description used in metadata, social previews and generated child-page lists.'),
213
213
  aliases: z.array(pageAlias)
214
214
  .min(1)
215
215
  .refine((values) => new Set(values).size === values.length, 'Page aliases must be unique within the page.')
@@ -275,7 +275,7 @@ const sitewideShape = {
275
275
  };
276
276
 
277
277
  const categoryShape = {
278
- label: z.string().trim().min(1).describe('Navigation label for this non-routable page category.'),
278
+ label: z.string().trim().min(1).describe('Navigation label for a group of child pages without an authored page body.'),
279
279
  };
280
280
 
281
281
  export const schemaTopLevelKeys = Object.freeze({
@@ -290,7 +290,7 @@ export const schemaTopLevelKeys = Object.freeze({
290
290
  export const configSchema = z.object(configShape).strict()
291
291
  .describe('Technical settings for one Norna site. Additional pages cannot provide technical configuration.');
292
292
  export const categorySchema = z.object(categoryShape).strict()
293
- .describe('A non-routable navigation category containing child pages.');
293
+ .describe('A navigation group without an authored page body. Opening its URL redirects when its first listed direct child is a page; otherwise it shows a generated child list.');
294
294
  export const siteSchema = z.object(siteShape).strict()
295
295
  .describe('Frontmatter for a homepage or additional page content.md file.');
296
296
  export const themeVisualSchema = z.object(themeVisualShape).strict()
@@ -583,7 +583,7 @@ const addContentHelp = (jsonSchema) => {
583
583
  ]);
584
584
  addHelp(jsonSchema, 'page.description', [
585
585
  yamlExample('page:\n description: About this project.'),
586
- 'An optional page-specific description rendered as `<meta name="description">`. It is not visible in page content.',
586
+ 'A page-specific description used in metadata and social previews. Generated child-page and category lists can show it beside the page title; it is not inserted into the page body.',
587
587
  documentationLink('Page title and frontmatter reference', 'content.md', 'page-title-and-frontmatter'),
588
588
  ], ['About this project.']);
589
589
  addHelp(jsonSchema, 'page.aliases', [
@@ -1,7 +1,11 @@
1
1
  ---
2
+ import { getCollection } from 'astro:content';
3
+ import { getPresentationCssVariables, resolveThemePresentation } from '../../scripts/lib/presentation.mjs';
2
4
  import projectConfig from '../../scripts/lib/project-config.mjs';
3
5
  import { getAbsolutePageUrl } from '../../scripts/lib/site-page-urls.mjs';
6
+ import { siteThemeLabel } from '../../scripts/lib/site-paths.mjs';
4
7
  import { withBasePath } from '../lib/basePath';
8
+ import { getReaderPreferencesScript } from '../lib/readerPreferencesScript.mjs';
5
9
 
6
10
  interface Props {
7
11
  targetPathname: string;
@@ -13,23 +17,54 @@ const { targetPathname, targetTitle, categoryTitle } = Astro.props;
13
17
  const { labels, lang } = projectConfig.locale;
14
18
  const targetHref = withBasePath(projectConfig.site.basePath, targetPathname);
15
19
  const canonicalUrl = getAbsolutePageUrl(projectConfig.site.url, targetPathname);
20
+ const [siteTheme] = await getCollection('theme');
21
+ if (!siteTheme) throw new Error(`${siteThemeLabel} is required. Add a root theme before building the site.`);
22
+ const presentation = resolveThemePresentation(siteTheme.data, siteThemeLabel);
23
+ const documentStyle = Object.entries(getPresentationCssVariables(presentation))
24
+ .map(([name, value]) => `${name}: ${value}`).join('; ');
25
+ const readerPreferencesScript = getReaderPreferencesScript({
26
+ ...presentation.readerPreferences,
27
+ cookiePath: projectConfig.site.basePath,
28
+ });
16
29
  ---
17
30
 
18
31
  <!doctype html>
19
- <html lang={lang} data-pagefind-ignore="all">
32
+ <html lang={lang} data-pagefind-ignore="all" data-appearance={presentation.appearance.default} style={documentStyle}>
20
33
  <head>
21
34
  <meta charset="utf-8" />
22
35
  <meta name="viewport" content="width=device-width" />
23
36
  <meta name="robots" content="noindex, follow" />
24
- <meta http-equiv="refresh" content={`0; url=${targetHref}`} />
25
37
  <link rel="canonical" href={canonicalUrl} />
26
38
  <title>{categoryTitle ?? labels.pageMoved}: {targetTitle}</title>
39
+ <meta name="theme-color" content={presentation.palette.page.backgroundColor} />
27
40
  <style is:inline>
28
41
  :root {
29
- color-scheme: light dark;
42
+ --color-page: var(--palette-light-page-background);
43
+ --color-primary-text: var(--palette-light-primary-text);
44
+ --color-link-text: var(--palette-light-link-text);
45
+ color-scheme: light;
46
+ min-height: 100%;
47
+ background: var(--color-page);
48
+ color: var(--color-primary-text);
30
49
  font-family: system-ui, sans-serif;
31
50
  }
32
51
 
52
+ :root[data-appearance='dark'] {
53
+ --color-page: var(--palette-dark-page-background);
54
+ --color-primary-text: var(--palette-dark-primary-text);
55
+ --color-link-text: var(--palette-dark-link-text);
56
+ color-scheme: dark;
57
+ }
58
+
59
+ @media (prefers-color-scheme: dark) {
60
+ :root[data-appearance='system'] {
61
+ --color-page: var(--palette-dark-page-background);
62
+ --color-primary-text: var(--palette-dark-primary-text);
63
+ --color-link-text: var(--palette-dark-link-text);
64
+ color-scheme: dark;
65
+ }
66
+ }
67
+
33
68
  body {
34
69
  margin: 0;
35
70
  padding: 2rem;
@@ -39,7 +74,13 @@ const canonicalUrl = getAbsolutePageUrl(projectConfig.site.url, targetPathname);
39
74
  max-width: 42rem;
40
75
  margin-inline: auto;
41
76
  }
77
+
78
+ a {
79
+ color: var(--color-link-text);
80
+ }
42
81
  </style>
82
+ <script is:inline set:html={readerPreferencesScript} />
83
+ <meta http-equiv="refresh" content={`0; url=${targetHref}`} />
43
84
  </head>
44
85
  <body>
45
86
  <main>
@@ -9,17 +9,14 @@ import {
9
9
  siteThemePath,
10
10
  sitewideContentPath,
11
11
  } from '../scripts/lib/site-paths.mjs';
12
- import { encodePageDirectoryPath, parsePageDirectoryPath } from '../scripts/lib/page-model.mjs';
12
+ import { encodePageEntryId, getSiteEntryPrefix, parsePageDirectoryPath } from '../scripts/lib/page-model.mjs';
13
13
  import {
14
14
  siteSchema,
15
15
  sitewideSchema,
16
16
  themeVisualSchema,
17
17
  } from '../scripts/lib/schema-definitions.mjs';
18
18
 
19
- const siteEntryId = `${siteDirLabel
20
- .replace(/^[./\\]+/, '')
21
- .replace(/[^a-zA-Z0-9-]+/g, '-')
22
- .replace(/^-+|-+$/g, '') || 'site'}-content`;
19
+ const siteEntryPrefix = getSiteEntryPrefix(siteDirLabel);
23
20
  const emptyYamlMapping = (value: unknown) => value ?? {};
24
21
  const siteThemeSchema = z.preprocess(emptyYamlMapping, themeVisualSchema);
25
22
  const sitewideContentSchema = z.preprocess(emptyYamlMapping, sitewideSchema);
@@ -34,7 +31,7 @@ const site = defineCollection({
34
31
  .split('/')
35
32
  .filter((segment) => segment !== 'pages')
36
33
  .join('/pages/');
37
- return `${siteEntryId.replace(/-content$/, '')}-page-${encodePageDirectoryPath(parsePageDirectoryPath(pageDirectory, `page directory pages/${pageEntryDirectory}`).pageDirectory)}`;
34
+ return encodePageEntryId(siteDirLabel, parsePageDirectoryPath(pageDirectory, `page directory pages/${pageEntryDirectory}`).pageDirectory);
38
35
  },
39
36
  }),
40
37
  schema: siteSchema,
@@ -44,7 +41,7 @@ const theme = defineCollection({
44
41
  loader: glob({
45
42
  pattern: basename(siteThemePath),
46
43
  base: pathToFileURL(siteDir),
47
- generateId: () => `${siteEntryId.replace(/-content$/, '')}-theme`,
44
+ generateId: () => `${siteEntryPrefix}-theme`,
48
45
  }),
49
46
  schema: siteThemeSchema,
50
47
  });
@@ -53,7 +50,7 @@ const sitewide = defineCollection({
53
50
  loader: glob({
54
51
  pattern: basename(sitewideContentPath),
55
52
  base: pathToFileURL(siteDir),
56
- generateId: () => `${siteEntryId.replace(/-content$/, '')}-sitewide`,
53
+ generateId: () => `${siteEntryPrefix}-sitewide`,
57
54
  }),
58
55
  schema: sitewideContentSchema,
59
56
  });
@@ -1,12 +1,12 @@
1
1
  import type { CollectionEntry } from 'astro:content';
2
2
  import path from 'node:path';
3
- import { decodePageDirectoryPath, parsePageDirectoryPath } from '../../scripts/lib/page-model.mjs';
3
+ import { decodePageEntryId, parsePageDirectoryPath } from '../../scripts/lib/page-model.mjs';
4
4
  import { parsePageMarkdown } from '../../scripts/lib/page-markdown.mjs';
5
5
  import { readSiteFile } from '../../scripts/lib/site-content.mjs';
6
6
  import { homePageDirectory } from '../../scripts/lib/site-conventions.mjs';
7
7
  import { assertCategoryDestinationModel, createCategoryDestinationModel } from '../../scripts/lib/category-destinations.mjs';
8
8
  import { getSiteStructure } from '../../scripts/lib/site-structure.mjs';
9
- import { sitePagesDir, sitePagesLabel } from '../../scripts/lib/site-paths.mjs';
9
+ import { siteDirLabel, sitePagesDir, sitePagesLabel } from '../../scripts/lib/site-paths.mjs';
10
10
 
11
11
  type SiteEntry = CollectionEntry<'site'>;
12
12
 
@@ -48,24 +48,10 @@ export type SiteCategory = SiteNodeBase & {
48
48
 
49
49
  export type SiteNode = SitePage | SiteCategory;
50
50
 
51
- const pageIdMarker = '-page-';
52
-
53
- const stripPageIdPrefix = (id: string) => {
54
- const markerIndex = id.lastIndexOf(pageIdMarker);
55
- if (markerIndex === -1) return null;
56
-
57
- const candidate = id.slice(markerIndex + pageIdMarker.length);
58
- try {
59
- return parsePageDirectoryPath(decodePageDirectoryPath(candidate)).pageDirectory;
60
- } catch {
61
- return null;
62
- }
63
- };
64
-
65
- export const isHomePageEntry = (entry: SiteEntry) => stripPageIdPrefix(entry.id) === homePageDirectory;
51
+ export const isHomePageEntry = (entry: SiteEntry) => decodePageEntryId(siteDirLabel, entry.id) === homePageDirectory;
66
52
 
67
53
  export const getPageDirectory = (entry: SiteEntry) => {
68
- const pageDirectory = stripPageIdPrefix(entry.id);
54
+ const pageDirectory = decodePageEntryId(siteDirLabel, entry.id);
69
55
  if (!pageDirectory) throw new Error(`Page entry "${entry.id}" has no valid page directory.`);
70
56
  return pageDirectory;
71
57
  };
@@ -983,8 +983,9 @@ ul {
983
983
  padding: 0.78rem 0.9rem;
984
984
  background: var(--color-code-background) !important;
985
985
  color: var(--color-code-text);
986
- font-size: 0.92em;
987
- line-height: 1.45;
986
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
987
+ font-size: 0.875rem;
988
+ line-height: 1.43;
988
989
  text-align: left;
989
990
  }
990
991
 
@@ -61,5 +61,5 @@ Use `npm run norna:engine:version` to inspect the installed engine and
61
61
  `npm run norna:engine:update` to update it.
62
62
 
63
63
  Start with the [illustrated Getting Started guide](https://janga.github.io/norna/getting-started/install-norna/).
64
- Use the [Markdown reference index](https://github.com/janga/norna/blob/main/docs/README.md)
64
+ Use the [Norna reference](https://janga.github.io/norna/reference/)
65
65
  for exact file contracts, syntax, commands, and publishing behavior.