@janga/norna 0.7.26 → 0.7.28

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.
Files changed (110) hide show
  1. package/README.md +51 -35
  2. package/astro.config.mjs +19 -1
  3. package/bin/norna-cli.mjs +6 -4
  4. package/package.json +9 -5
  5. package/schemas/card-list.schema.json +1 -1
  6. package/schemas/config.schema.json +66 -66
  7. package/schemas/content-frontmatter.schema.json +17 -8
  8. package/schemas/manifest.json +49 -3
  9. package/schemas/page-theme.schema.json +1585 -41
  10. package/schemas/root-theme.schema.json +1767 -0
  11. package/schemas/sitewide-content.schema.json +18 -18
  12. package/schemas/theme.schema.json +243 -243
  13. package/scripts/add-site-node.mjs +13 -161
  14. package/scripts/build-site.mjs +11 -6
  15. package/scripts/check-config.mjs +7 -23
  16. package/scripts/dev-local.mjs +17 -10
  17. package/scripts/export-theme-preset.mjs +2 -2
  18. package/scripts/generate-images.mjs +7 -6
  19. package/scripts/generate-schemas.mjs +9 -6
  20. package/scripts/lib/category-destinations.mjs +13 -12
  21. package/scripts/lib/documentation-links.mjs +33 -2
  22. package/scripts/lib/documentation-routes.json +229 -0
  23. package/scripts/lib/docusaurus-migration-audit.mjs +9 -6
  24. package/scripts/lib/editor-file-policy.mjs +18 -0
  25. package/scripts/lib/editor-language-service.mjs +53 -55
  26. package/scripts/lib/editor-page-addresses.mjs +55 -0
  27. package/scripts/lib/editor-page-placement.mjs +172 -0
  28. package/scripts/lib/editor-site-editing-tree.mjs +146 -0
  29. package/scripts/lib/editor-site-files.mjs +153 -0
  30. package/scripts/lib/editor-site-links.mjs +79 -0
  31. package/scripts/lib/editor-site-tree.mjs +151 -0
  32. package/scripts/lib/editor-source-files.mjs +87 -0
  33. package/scripts/lib/editor-theme-help.mjs +43 -0
  34. package/scripts/lib/locale-registry.mjs +1 -1
  35. package/scripts/lib/locales/en.mjs +1 -1
  36. package/scripts/lib/navigation-model.mjs +2 -44
  37. package/scripts/lib/navigation-review.mjs +14 -50
  38. package/scripts/lib/norna-markdown-blocks.mjs +1 -1
  39. package/scripts/lib/page-aliases.mjs +3 -1
  40. package/scripts/lib/page-model.mjs +39 -20
  41. package/scripts/lib/page-move-apply.mjs +117 -0
  42. package/scripts/lib/page-move-plan.mjs +27 -59
  43. package/scripts/lib/presentation.mjs +3 -3
  44. package/scripts/lib/project-config.mjs +7 -8
  45. package/scripts/lib/schema-definitions.mjs +12 -39
  46. package/scripts/lib/schema-editor-metadata.mjs +12 -59
  47. package/scripts/lib/schema-value-definitions.mjs +1 -1
  48. package/scripts/lib/site-content.mjs +7 -24
  49. package/scripts/lib/site-conventions.mjs +19 -1
  50. package/scripts/lib/site-navigation-tree.mjs +2 -7
  51. package/scripts/lib/site-node-create.mjs +169 -0
  52. package/scripts/lib/site-paths.mjs +22 -10
  53. package/scripts/lib/site-structure.mjs +72 -82
  54. package/scripts/lib/site-upgrade.mjs +30 -0
  55. package/scripts/lib/source-files.mjs +34 -0
  56. package/scripts/lib/theme-config.mjs +26 -68
  57. package/scripts/lib/theme-packages.mjs +47 -0
  58. package/scripts/lib/theme-presets.mjs +20 -53
  59. package/scripts/lib/typography.mjs +2 -2
  60. package/scripts/lib/yaml-config.mjs +1 -1
  61. package/scripts/lib/yaml-source-edit.mjs +94 -0
  62. package/scripts/move-site-page.mjs +3 -124
  63. package/scripts/show-typography.mjs +32 -17
  64. package/scripts/sync-content-sections.mjs +9 -7
  65. package/scripts/sync-site-public.mjs +1 -1
  66. package/scripts/upgrade-site.mjs +17 -0
  67. package/src/components/AreaNavigationMenu.astro +24 -0
  68. package/src/components/AreaNavigationPanel.astro +55 -0
  69. package/src/components/AreaNavigationSwitcher.astro +30 -0
  70. package/src/components/ImageStackEnhancement.astro +0 -7
  71. package/src/components/NavigationContinuity.astro +26 -0
  72. package/src/components/NavigationPageTree.astro +13 -4
  73. package/src/components/NavigationState.astro +11 -0
  74. package/src/components/NavigationTreeControls.astro +3 -2
  75. package/src/components/PageAliasRedirect.astro +44 -3
  76. package/src/components/SearchPage.astro +9 -1
  77. package/src/components/SectionNavigationScript.astro +106 -26
  78. package/src/components/SiteNavigation.astro +45 -20
  79. package/src/components/SitePage.astro +37 -61
  80. package/src/components/SiteTreeNavigation.astro +38 -18
  81. package/src/components/TopPageMenu.astro +4 -3
  82. package/src/components/TreeNavigationScript.astro +50 -57
  83. package/src/content.config.ts +15 -16
  84. package/src/layouts/BaseLayout.astro +15 -39
  85. package/src/lib/areaNavigation.ts +42 -0
  86. package/src/lib/navigationContinuity.ts +212 -0
  87. package/src/lib/navigationFollowing.ts +28 -18
  88. package/src/lib/navigationState.mjs +88 -0
  89. package/src/lib/navigationStateScript.mjs +76 -0
  90. package/src/lib/navigationVisibility.ts +9 -0
  91. package/src/lib/pageThemes.ts +5 -42
  92. package/src/lib/sectionContent.ts +3 -2
  93. package/src/lib/siteNavigation.ts +6 -0
  94. package/src/lib/sitePages.ts +12 -23
  95. package/src/pages/404.astro +1 -1
  96. package/src/pages/[...slug].astro +5 -15
  97. package/src/pages/index.astro +1 -3
  98. package/src/styles/area-navigation.css +399 -0
  99. package/src/styles/content.css +5 -31
  100. package/src/styles/navigation-continuity.css +163 -0
  101. package/src/styles/page-layout.css +57 -68
  102. package/src/styles/responsive.css +0 -4
  103. package/starters/basic/README.md +10 -9
  104. package/starters/basic/site/{pages/000-home → root}/content.md +2 -2
  105. package/schemas/category.schema.json +0 -20
  106. package/src/components/PageContentsNavigation.astro +0 -20
  107. /package/starters/basic/site/{pages/000-home → root}/images/.gitkeep +0 -0
  108. /package/starters/basic/site/{theme.yaml → root/tree-theme.yaml} +0 -0
  109. /package/starters/basic/site/{config.yaml → site-config/settings.yaml} +0 -0
  110. /package/starters/basic/site/{sitewide-content.yaml → site-config/shared-content.yaml} +0 -0
@@ -0,0 +1,229 @@
1
+ {
2
+ "sources": {
3
+ "": "site/root/pages/032-reference/content.md",
4
+ "site/": "site/root/pages/032-reference/pages/010-site/content.md",
5
+ "site/files/": "site/root/pages/032-reference/pages/010-site/pages/010-files/content.md",
6
+ "site/pages/": "site/root/pages/032-reference/pages/010-site/pages/020-pages/content.md",
7
+ "site/urls/": "site/root/pages/032-reference/pages/010-site/pages/030-urls/content.md",
8
+ "site/metadata/": "site/root/pages/032-reference/pages/010-site/pages/040-metadata/content.md",
9
+ "site/images/": "site/root/pages/032-reference/pages/010-site/pages/050-images/content.md",
10
+ "site/public-files/": "site/root/pages/032-reference/pages/010-site/pages/060-public-files/content.md",
11
+ "site/requirements/": "site/root/pages/032-reference/pages/010-site/pages/070-requirements/content.md",
12
+ "configuration/": "site/root/pages/032-reference/pages/020-configuration/content.md",
13
+ "configuration/site/": "site/root/pages/032-reference/pages/020-configuration/pages/010-site/content.md",
14
+ "configuration/language/": "site/root/pages/032-reference/pages/020-configuration/pages/020-language/content.md",
15
+ "configuration/navigation/": "site/root/pages/032-reference/pages/020-configuration/pages/030-navigation/content.md",
16
+ "configuration/search/": "site/root/pages/032-reference/pages/020-configuration/pages/040-search/content.md",
17
+ "configuration/source-links/": "site/root/pages/032-reference/pages/020-configuration/pages/050-source-links/content.md",
18
+ "configuration/theme/": "site/root/pages/032-reference/pages/020-configuration/pages/060-theme/content.md",
19
+ "configuration/presets/": "site/root/pages/032-reference/pages/020-configuration/pages/065-presets/content.md",
20
+ "configuration/palettes/": "site/root/pages/032-reference/pages/020-configuration/pages/070-palettes/content.md",
21
+ "configuration/appearance/": "site/root/pages/032-reference/pages/020-configuration/pages/080-appearance/content.md",
22
+ "configuration/layout/": "site/root/pages/032-reference/pages/020-configuration/pages/090-layout/content.md",
23
+ "configuration/images/": "site/root/pages/032-reference/pages/020-configuration/pages/100-images/content.md",
24
+ "configuration/typography/": "site/root/pages/032-reference/pages/020-configuration/pages/110-typography/content.md",
25
+ "configuration/sections/": "site/root/pages/032-reference/pages/020-configuration/pages/120-sections/content.md",
26
+ "configuration/shared-content/": "site/root/pages/032-reference/pages/020-configuration/pages/130-shared-content/content.md",
27
+ "content/": "site/root/pages/032-reference/pages/030-content/content.md",
28
+ "content/markdown/": "site/root/pages/032-reference/pages/030-content/pages/010-markdown/content.md",
29
+ "content/structured-blocks/": "site/root/pages/032-reference/pages/030-content/pages/015-structured-blocks/content.md",
30
+ "content/images/": "site/root/pages/032-reference/pages/030-content/pages/020-images/content.md",
31
+ "content/cards/": "site/root/pages/032-reference/pages/030-content/pages/030-cards/content.md",
32
+ "content/child-lists/": "site/root/pages/032-reference/pages/030-content/pages/040-child-lists/content.md",
33
+ "content/callouts/": "site/root/pages/032-reference/pages/030-content/pages/050-callouts/content.md",
34
+ "content/tabs/": "site/root/pages/032-reference/pages/030-content/pages/060-tabs/content.md",
35
+ "content/details/": "site/root/pages/032-reference/pages/030-content/pages/070-details/content.md",
36
+ "content/tables/": "site/root/pages/032-reference/pages/030-content/pages/080-tables/content.md",
37
+ "content/code/": "site/root/pages/032-reference/pages/030-content/pages/090-code/content.md",
38
+ "content/sidenotes/": "site/root/pages/032-reference/pages/030-content/pages/100-sidenotes/content.md",
39
+ "content/footnotes/": "site/root/pages/032-reference/pages/030-content/pages/110-footnotes/content.md",
40
+ "commands/": "site/root/pages/032-reference/pages/040-commands/content.md",
41
+ "commands/invocation/": "site/root/pages/032-reference/pages/040-commands/pages/010-invocation/content.md",
42
+ "commands/init/": "site/root/pages/032-reference/pages/040-commands/pages/020-init/content.md",
43
+ "commands/create/": "site/root/pages/032-reference/pages/040-commands/pages/030-create/content.md",
44
+ "commands/move/": "site/root/pages/032-reference/pages/040-commands/pages/040-move/content.md",
45
+ "commands/validate/": "site/root/pages/032-reference/pages/040-commands/pages/050-validate/content.md",
46
+ "commands/navigation/": "site/root/pages/032-reference/pages/040-commands/pages/055-navigation/content.md",
47
+ "commands/development/": "site/root/pages/032-reference/pages/040-commands/pages/060-development/content.md",
48
+ "commands/build/": "site/root/pages/032-reference/pages/040-commands/pages/070-build/content.md",
49
+ "commands/publishing/": "site/root/pages/032-reference/pages/040-commands/pages/080-publishing/content.md",
50
+ "commands/theme/": "site/root/pages/032-reference/pages/040-commands/pages/090-theme/content.md",
51
+ "commands/engine/": "site/root/pages/032-reference/pages/040-commands/pages/100-engine/content.md",
52
+ "commands/migration-audit/": "site/root/pages/032-reference/pages/040-commands/pages/110-migration-audit/content.md",
53
+ "reader/": "site/root/pages/032-reference/pages/050-reader/content.md",
54
+ "reader/display/": "site/root/pages/032-reference/pages/050-reader/pages/010-display/content.md",
55
+ "reader/accessibility/": "site/root/pages/032-reference/pages/050-reader/pages/020-accessibility/content.md",
56
+ "workflows/": "site/root/pages/032-reference/pages/060-workflows/content.md",
57
+ "workflows/editor/": "site/root/pages/032-reference/pages/060-workflows/pages/010-editor/content.md",
58
+ "workflows/editor-ai-suggestions/": "site/root/pages/032-reference/pages/060-workflows/pages/015-editor-ai-suggestions/content.md",
59
+ "workflows/publishing/": "site/root/pages/032-reference/pages/060-workflows/pages/020-publishing/content.md",
60
+ "workflows/embedded-publishing/": "site/root/pages/032-reference/pages/060-workflows/pages/030-embedded-publishing/content.md",
61
+ "workflows/upgrading/": "site/root/pages/032-reference/pages/060-workflows/pages/040-upgrading/content.md",
62
+ "workflows/legacy-source/": "site/root/pages/032-reference/pages/060-workflows/pages/050-legacy-source/content.md"
63
+ },
64
+ "legacy": {
65
+ "site-files.md": "site/files/",
66
+ "pages.md": "site/pages/",
67
+ "configuration.md": "configuration/site/",
68
+ "content.md": "content/markdown/",
69
+ "theme.md": "configuration/theme/",
70
+ "typography.md": "configuration/typography/",
71
+ "images-and-metadata.md": "site/images/",
72
+ "public-files.md": "site/public-files/",
73
+ "sitewide-content.md": "configuration/shared-content/",
74
+ "commands.md": "commands/invocation/",
75
+ "local-development.md": "commands/development/",
76
+ "requirements.md": "site/requirements/",
77
+ "publishing.md": "workflows/publishing/",
78
+ "upgrading.md": "workflows/upgrading/",
79
+ "editor-support.md": "workflows/editor/",
80
+ "client-javascript.md": "reader/accessibility/",
81
+ "presentation-guarantees.md": "reader/accessibility/",
82
+ "site-files.md#root-files": "site/files/#source-layout",
83
+ "site-files.md#page-hierarchy": "site/files/#page-folders",
84
+ "site-files.md#public-files": "site/public-files/",
85
+ "site-files.md#selecting-the-site-directory": "commands/invocation/#site-selection",
86
+ "site-files.md#versioned-files": "site/files/#generated-files",
87
+ "site-files.md#generated-files": "site/files/#generated-files",
88
+ "pages.md#homepage-and-top-level-entries": "site/pages/#choose-pages-and-sections",
89
+ "pages.md#choose-a-section-page-or-category": "site/pages/#choose-pages-and-sections",
90
+ "pages.md#review-an-existing-hierarchy": "commands/navigation/",
91
+ "pages.md#navigation-categories": "site/pages/#choose-pages-and-sections",
92
+ "pages.md#opening-a-category-url": "site/pages/#list-child-pages",
93
+ "pages.md#reachable-content-and-stable-links": "site/pages/#list-child-pages",
94
+ "pages.md#nested-pages": "site/pages/#choose-pages-and-sections",
95
+ "pages.md#directory-names-order-and-urls": "site/pages/#names-and-order",
96
+ "pages.md#preserve-old-page-urls": "site/urls/#keep-an-old-url",
97
+ "pages.md#move-or-reconcile-a-page": "commands/move/",
98
+ "pages.md#create-pages-and-categories": "commands/create/",
99
+ "pages.md#page-content": "content/markdown/",
100
+ "pages.md#navigation": "configuration/navigation/",
101
+ "pages.md#focus-reading": "reader/display/#focus-reading",
102
+ "pages.md#current-reading-position": "configuration/navigation/#orientation-and-long-trees",
103
+ "pages.md#page-images": "site/images/",
104
+ "pages.md#page-themes": "configuration/theme/#page-themes",
105
+ "configuration.md#url": "configuration/site/#public-url",
106
+ "configuration.md#language": "configuration/language/",
107
+ "configuration.md#what-the-setting-changes": "configuration/language/",
108
+ "configuration.md#choose-a-language-tag": "configuration/language/",
109
+ "configuration.md#supported-languages": "configuration/language/",
110
+ "configuration.md#search-and-translation-boundaries": "configuration/language/",
111
+ "configuration.md#edit-link": "configuration/source-links/",
112
+ "configuration.md#local-editor": "configuration/source-links/#destination-rules",
113
+ "configuration.md#remote-source-host": "configuration/source-links/#fields",
114
+ "configuration.md#navigation": "configuration/navigation/",
115
+ "configuration.md#navigationmode": "configuration/navigation/",
116
+ "configuration.md#one-page-sections": "configuration/navigation/",
117
+ "configuration.md#top-level-pages-top": "configuration/navigation/",
118
+ "configuration.md#child-pages-or-categories-tree": "configuration/navigation/",
119
+ "configuration.md#search": "configuration/search/",
120
+ "configuration.md#scrollbehavior": "configuration/site/#anchor-movement",
121
+ "configuration.md#publishing-discovery": "commands/publishing/",
122
+ "configuration.md#site-directory-selection": "commands/invocation/#site-selection",
123
+ "content.md#page-title-and-frontmatter": "site/metadata/",
124
+ "content.md#sections": "content/markdown/#page-title-and-sections",
125
+ "content.md#internal-links": "site/urls/",
126
+ "content.md#norna-blocks": "content/structured-blocks/",
127
+ "content.md#child-page-list": "content/child-lists/",
128
+ "content.md#image-stack": "content/images/#image-stack",
129
+ "content.md#image-carousel": "content/images/#image-carousel",
130
+ "content.md#card-list": "content/cards/",
131
+ "content.md#image-files": "site/images/",
132
+ "content.md#markdown-images": "content/markdown/#markdown-images",
133
+ "content.md#markdown-text": "content/markdown/#standard-syntax-and-extensions",
134
+ "content.md#semantic-callouts": "content/callouts/",
135
+ "content.md#tabs": "content/tabs/",
136
+ "content.md#details-disclosures": "content/details/",
137
+ "content.md#tables": "content/tables/",
138
+ "content.md#row-headings": "content/tables/#row-headings",
139
+ "content.md#sorting": "content/tables/#sorting",
140
+ "content.md#width-and-scrolling": "content/tables/#width-and-sticky-context",
141
+ "content.md#code-blocks": "content/code/",
142
+ "content.md#side-notes": "content/sidenotes/",
143
+ "content.md#reference-footnotes": "content/footnotes/",
144
+ "content.md#validation-and-sync": "commands/validate/",
145
+ "images-and-metadata.md#managed-source-images": "site/images/#source-files",
146
+ "images-and-metadata.md#managed-image-presentation": "configuration/images/",
147
+ "images-and-metadata.md#captions-for-tall-images": "configuration/images/#tall-captions-and-inspection",
148
+ "images-and-metadata.md#inspecting-detailed-images": "configuration/images/#tall-captions-and-inspection",
149
+ "images-and-metadata.md#markdown-images": "content/markdown/#markdown-images",
150
+ "images-and-metadata.md#generated-variants-and-static-svg": "site/images/#variants-and-svg",
151
+ "images-and-metadata.md#manifest": "site/images/#manifest-and-cache",
152
+ "images-and-metadata.md#metadata-behavior": "site/images/#credits-and-provenance",
153
+ "images-and-metadata.md#ai-generated-images": "site/images/#credits-and-provenance",
154
+ "images-and-metadata.md#github-actions-cache": "site/images/#manifest-and-cache",
155
+ "public-files.md#navigation-logo": "site/public-files/#recognized-files",
156
+ "public-files.md#browser-icons": "site/public-files/#recognized-files",
157
+ "public-files.md#social-sharing-image": "site/public-files/#social-previews",
158
+ "public-files.md#github-pages-custom-domain": "site/public-files/#downloads-and-hosting-files",
159
+ "public-files.md#generated-sitemap": "site/public-files/#generated-sitemap",
160
+ "public-files.md#generated-404-page": "site/public-files/#generated-404-page",
161
+ "public-files.md#other-static-files": "site/public-files/#downloads-and-hosting-files",
162
+ "public-files.md#generated-copy": "site/public-files/#preparation-and-reserved-paths",
163
+ "theme.md#theme-presets": "configuration/presets/",
164
+ "theme.md#choose-a-preset": "configuration/theme/#presets-and-root-overrides",
165
+ "theme.md#portfolio": "configuration/presets/#portfolio",
166
+ "theme.md#documentation": "configuration/presets/#documentation",
167
+ "theme.md#project": "configuration/presets/#project",
168
+ "theme.md#statement": "configuration/presets/#statement",
169
+ "theme.md#overrides": "configuration/theme/#presets-and-root-overrides",
170
+ "theme.md#export-a-preset": "commands/theme/#list-and-export-presets",
171
+ "theme.md#layout": "configuration/layout/",
172
+ "theme.md#image-sizing": "configuration/images/",
173
+ "theme.md#content-block-defaults": "content/cards/#width",
174
+ "theme.md#typography": "configuration/typography/",
175
+ "theme.md#palette-and-appearance": "configuration/palettes/",
176
+ "theme.md#palette": "configuration/palettes/",
177
+ "theme.md#appearance": "configuration/appearance/",
178
+ "theme.md#reader-display-controls": "reader/display/",
179
+ "theme.md#corners": "configuration/theme/#visual-settings",
180
+ "theme.md#section-backgrounds": "configuration/sections/",
181
+ "theme.md#page-themes": "configuration/theme/#page-themes",
182
+ "typography.md#typography-profiles": "configuration/typography/#font-profile-and-rhythm",
183
+ "typography.md#typography-rhythm": "configuration/typography/#font-profile-and-rhythm",
184
+ "typography.md#configuration-shape": "configuration/typography/#override-fields",
185
+ "typography.md#override-values": "configuration/typography/#override-fields",
186
+ "typography.md#page-text-width": "configuration/layout/",
187
+ "sitewide-content.md#logo": "configuration/shared-content/#logo",
188
+ "sitewide-content.md#banners": "configuration/shared-content/#banners",
189
+ "sitewide-content.md#footer": "configuration/shared-content/#footer",
190
+ "commands.md#cli-commands": "commands/invocation/#command-map",
191
+ "commands.md#starter-npm-scripts": "commands/invocation/#project-npm-scripts",
192
+ "commands.md#create-pages-and-categories": "commands/create/",
193
+ "commands.md#move-a-page": "commands/move/",
194
+ "commands.md#review-navigation-structure": "commands/navigation/",
195
+ "commands.md#command-summary": "commands/invocation/#command-map",
196
+ "local-development.md#start-local-preview": "commands/development/",
197
+ "local-development.md#test-on-a-phone": "commands/development/#lan-preview",
198
+ "local-development.md#manage-preview": "commands/development/#lifecycle-commands",
199
+ "local-development.md#when-startup-fails": "commands/development/#preparation-failures-and-stale-output",
200
+ "local-development.md#rebuild-stale-preview": "commands/development/#preparation-failures-and-stale-output",
201
+ "publishing.md#requirements": "workflows/publishing/",
202
+ "publishing.md#github-pages-workflow": "workflows/publishing/#configure-and-publish",
203
+ "publishing.md#publish-an-embedded-site-beside-another-static-build": "workflows/embedded-publishing/",
204
+ "publishing.md#deploy-an-already-committed-branch": "commands/publishing/#deploy-committed-sources",
205
+ "publishing.md#deploy-with-a-generated-commit": "commands/publishing/#build-and-create-a-commit",
206
+ "publishing.md#watch-a-deploy": "commands/publishing/#monitor-a-workflow",
207
+ "upgrading.md#before-changing-the-site": "workflows/upgrading/#establish-the-baseline",
208
+ "upgrading.md#safest-pre-10-migration": "workflows/upgrading/#older-source-models",
209
+ "upgrading.md#current-source-layout": "workflows/legacy-source/",
210
+ "upgrading.md#routine-engine-updates": "workflows/upgrading/#update-and-review",
211
+ "upgrading.md#validate-the-migration": "workflows/upgrading/#update-and-review",
212
+ "editor-support.md#install-the-extension": "workflows/editor/#install-and-verify",
213
+ "editor-support.md#what-you-see-in-vs-code": "workflows/editor/#install-and-verify",
214
+ "editor-support.md#requirements": "workflows/editor/#install-and-verify",
215
+ "editor-support.md#recognized-files": "workflows/editor/#recognized-files",
216
+ "editor-support.md#configuration-help": "workflows/editor/#suggestions-and-diagnostics",
217
+ "editor-support.md#markdown-and-image-help": "workflows/editor/#managed-images-and-quick-fixes",
218
+ "editor-support.md#refresh-after-an-engine-change": "workflows/editor/#refresh-and-troubleshoot",
219
+ "editor-support.md#troubleshooting": "workflows/editor/#refresh-and-troubleshoot",
220
+ "editor-support.md#turn-off-ai-suggestions-while-keeping-intellisense": "workflows/editor-ai-suggestions/",
221
+ "client-javascript.md#feature-contract": "reader/accessibility/#without-javascript",
222
+ "client-javascript.md#choosing-static-alternatives": "reader/accessibility/#without-javascript",
223
+ "presentation-guarantees.md#color-and-focus": "reader/accessibility/#color-focus-and-controls",
224
+ "presentation-guarantees.md#typography-and-reading-width": "reader/accessibility/#reading-and-reflow",
225
+ "presentation-guarantees.md#reflow-and-text-adaptation": "reader/accessibility/#reading-and-reflow",
226
+ "presentation-guarantees.md#controls-and-user-preferences": "reader/display/",
227
+ "presentation-guarantees.md#author-responsibilities": "reader/accessibility/#author-responsibilities"
228
+ }
229
+ }
@@ -1,3 +1,4 @@
1
+ import { getSiteSourcePaths } from './site-conventions.mjs';
1
2
  import { execFile } from 'node:child_process';
2
3
  import { createHash } from 'node:crypto';
3
4
  import { access, mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
@@ -584,14 +585,16 @@ export const writeMigrationReport = async (report, reportDir, { sourceRoot = nul
584
585
  const entries = await readdir(resolvedReportDir);
585
586
  if (entries.length > 0) throw new Error('Migration report directory must be empty: ' + resolvedReportDir);
586
587
  } else await mkdir(resolvedReportDir, { recursive: true });
587
- await mkdir(path.join(resolvedReportDir, 'pages', '000-home'), { recursive: true });
588
- await writeFile(path.join(resolvedReportDir, 'config.yaml'), 'url: https://example.invalid/\n');
589
- await writeFile(path.join(resolvedReportDir, 'theme.yaml'), 'preset: documentation\n');
590
- await writeFile(path.join(resolvedReportDir, 'pages', '000-home', 'content.md'), renderReportHome(report));
588
+ const source = getSiteSourcePaths(resolvedReportDir);
589
+ await mkdir(source.pages, { recursive: true });
590
+ await mkdir(path.join(resolvedReportDir, 'site-config'), { recursive: true });
591
+ await writeFile(path.join(resolvedReportDir, 'site-config/settings.yaml'), 'url: https://example.invalid/\n');
592
+ await writeFile(path.join(resolvedReportDir, 'root', 'tree-theme.yaml'), 'preset: documentation\n');
593
+ await writeFile(source.content, renderReportHome(report));
591
594
  if (report.problemTypes.length > 0) {
592
- const categoryDir = path.join(resolvedReportDir, 'pages', '010-problems');
595
+ const categoryDir = path.join(source.pages, '010-problems');
593
596
  await mkdir(path.join(categoryDir, 'pages'), { recursive: true });
594
- await writeFile(path.join(categoryDir, 'category.yaml'), 'label: Problems\n');
597
+ await writeFile(path.join(categoryDir, 'content.md'), '---\npage:\n listChildren: true\n---\n\n# Problems\n');
595
598
  for (const [index, problem] of report.problemTypes.entries()) {
596
599
  const pageDir = path.join(categoryDir, 'pages', String((index + 1) * 10).padStart(3, '0') + '-' + problemPageId(problem));
597
600
  await mkdir(pageDir, { recursive: true });
@@ -0,0 +1,18 @@
1
+ import { getSiteSourcePaths } from './site-conventions.mjs';
2
+ import path from 'node:path';
3
+ import { getSourceFileDefinition } from './source-files.mjs';
4
+ import { isInside } from './editor-site-links.mjs';
5
+
6
+ export const editorFileRemovalPolicy = ({ siteRoot, sourcePath, filePath }) => {
7
+ const directory = path.dirname(sourcePath);
8
+ const siteOwner = sourcePath === siteRoot || directory === getSiteSourcePaths(siteRoot).root;
9
+ const definition = getSourceFileDefinition(siteRoot, filePath);
10
+ if (definition && !definition.required && definition.removalEffect
11
+ && (path.dirname(filePath) === directory || definition.location === 'site-config' && siteOwner)) {
12
+ return { kind: definition.schemaKind === 'sitewideContent' ? 'shared-content' : 'theme', effect: definition.removalEffect };
13
+ }
14
+ if (siteOwner && isInside(path.join(siteRoot, 'public'), filePath)) return {
15
+ kind: 'public', effect: 'This file will no longer be published. Links to it may break; an icon or logo may disappear. References outside page content are not checked.',
16
+ };
17
+ return null;
18
+ };
@@ -17,11 +17,14 @@ import {
17
17
  socialImageAssetFilenames,
18
18
  } from './public-asset-conventions.mjs';
19
19
  import { parsePageMarkdownSource } from './page-markdown.mjs';
20
+ import { selectPageTheme } from './theme-packages.mjs';
21
+ import { getSourcePageLocation } from './source-files.mjs';
22
+ import { assertSectionBackgroundPatternCompatibility } from './presentation.mjs';
20
23
  import { siteSchema } from './schema-definitions.mjs';
21
- import { homePageDirectory } from './site-conventions.mjs';
24
+ import { getSiteSourcePaths, homePageDirectory } from './site-conventions.mjs';
22
25
 
23
26
  const supportedImageExtensions = new Set(['.jpg', '.jpeg', '.png', '.svg']);
24
- const siteConfigNames = ['config.yaml'];
27
+ const siteConfigNames = ['site-config/settings.yaml'];
25
28
  const fileExists = (filePath) => access(filePath).then(() => true, () => false);
26
29
  const toPosixPath = (filePath) => filePath.split(path.sep).join('/');
27
30
 
@@ -79,45 +82,35 @@ const getNavigationNodesForEditor = async (siteRoot) => {
79
82
  for (const entry of entries) {
80
83
  const nodeDirectory = path.join(pagesDirectory, entry.name);
81
84
  const contentPath = path.join(nodeDirectory, 'content.md');
82
- const categoryPath = path.join(nodeDirectory, 'category.yaml');
83
- const [hasContent, hasCategory] = await Promise.all([
84
- fileExists(contentPath),
85
- fileExists(categoryPath),
86
- ]);
87
-
88
- if (hasContent !== hasCategory) {
89
- if (hasCategory) {
90
- nodes.push({
91
- depth,
92
- headings: [],
93
- isHome: false,
94
- kind: 'category',
95
- listed: true,
96
- });
97
- } else {
98
- const source = await readFile(contentPath, 'utf8');
99
- const data = readContentFrontmatterForEditor(source);
100
- const document = await parsePageMarkdownSource(source, { label: contentPath });
101
- nodes.push({
102
- depth,
103
- headings: document.navigationHeadings,
104
- isHome: depth === 1 && entry.name === homePageDirectory,
105
- kind: 'page',
106
- listed: data.navigation?.listed !== false,
107
- });
108
- }
85
+ if (await fileExists(contentPath)) {
86
+ const source = await readFile(contentPath, 'utf8');
87
+ const data = readContentFrontmatterForEditor(source);
88
+ const document = await parsePageMarkdownSource(source, { label: contentPath });
89
+ nodes.push({
90
+ depth,
91
+ headings: document.navigationHeadings,
92
+ isHome: false,
93
+ kind: 'page',
94
+ listed: data.navigation?.listed !== false,
95
+ });
109
96
  }
110
97
 
111
98
  await visit(path.join(nodeDirectory, 'pages'), depth + 1);
112
99
  }
113
100
  };
114
101
 
115
- await visit(path.join(siteRoot, 'pages'));
102
+ const homeContent = getSiteSourcePaths(siteRoot).content;
103
+ if (await fileExists(homeContent)) {
104
+ const source = await readFile(homeContent, 'utf8');
105
+ const document = await parsePageMarkdownSource(source, { label: homeContent });
106
+ nodes.unshift({ depth: 0, headings: document.navigationHeadings, isHome: true, kind: 'page', listed: true });
107
+ }
108
+ await visit(getSiteSourcePaths(siteRoot).pages);
116
109
  return nodes;
117
110
  };
118
111
 
119
112
  const getNavigationModeForEditor = async (siteRoot) => {
120
- const config = await readEditorYaml(path.join(siteRoot, 'config.yaml'));
113
+ const config = await readEditorYaml(path.join(siteRoot, 'site-config', 'settings.yaml'));
121
114
  if (!config) return null;
122
115
  const requestedMode = config.navigation?.mode ?? 'automatic';
123
116
  if (requestedMode !== 'automatic') return requestedMode;
@@ -148,30 +141,33 @@ const findNestedYamlPropertyLine = (source, parentKey, propertyKey) => {
148
141
  return 1;
149
142
  };
150
143
 
151
- export const getThemeDiagnostics = async ({ documentPath, source }) => {
152
- let theme;
144
+ export const getThemeDiagnostics = async ({ siteRoot, documentPath, source, sources = new Map() }) => {
145
+ // Parse errors are reported by the YAML provider/schema validator.
146
+ try { const value = load(source); if (value && (typeof value !== 'object' || Array.isArray(value))) return []; }
147
+ catch { return []; }
148
+ siteRoot ??= await findNornaSiteRoot(documentPath);
149
+ if (!siteRoot) return [];
150
+ const location = getSourcePageLocation(siteRoot, path.dirname(documentPath));
151
+ if (!location) return [];
153
152
  try {
154
- theme = load(source) ?? {};
155
- } catch {
153
+ const overlays = new Map(sources); overlays.set(documentPath, source);
154
+ const { treeTheme, selected } = await selectPageTheme({
155
+ siteRoot, pageDirectory: location.pageDirectory, sources: overlays,
156
+ includePageTheme: path.basename(documentPath) === 'page-theme.yaml',
157
+ });
158
+ const theme = path.basename(documentPath) === 'tree-theme.yaml' ? treeTheme : selected;
159
+ assertSectionBackgroundPatternCompatibility(theme.config, documentPath, await getNavigationModeForEditor(siteRoot));
156
160
  return [];
161
+ } catch (error) {
162
+ const surface = error.message.includes('backgroundPattern');
163
+ return [{ code: surface ? 'tree-section-background-pattern' : 'invalid-resolved-theme',
164
+ line: surface ? findNestedYamlPropertyLine(source, 'sections', 'backgroundPattern') : 1,
165
+ message: error.message, severity: 'error' }];
157
166
  }
158
- if (!theme || typeof theme !== 'object' || Array.isArray(theme)) return [];
159
-
160
- const backgroundPattern = theme.sections?.backgroundPattern;
161
- if (backgroundPattern === undefined || backgroundPattern === 'uniform') return [];
162
- const siteRoot = await findNornaSiteRoot(documentPath);
163
- if (!siteRoot || await getNavigationModeForEditor(siteRoot) !== 'tree') return [];
164
-
165
- return [{
166
- code: 'tree-section-background-pattern',
167
- line: findNestedYamlPropertyLine(source, 'sections', 'backgroundPattern'),
168
- message: `sections.backgroundPattern "${backgroundPattern}" cannot be used because this site resolves to tree navigation. Tree navigation uses one uniform reading surface so the navigation rail and page content remain distinct. Remove sections.backgroundPattern or set it to uniform.`,
169
- severity: 'error',
170
- }];
171
167
  };
172
168
 
173
169
  const readSitewideLogoForEditor = async (siteRoot) => {
174
- const filename = await findFile(siteRoot, ['sitewide-content.yaml']);
170
+ const filename = await findFile(siteRoot, ['site-config/shared-content.yaml']);
175
171
  if (!filename) return null;
176
172
  const absolutePath = path.join(siteRoot, filename);
177
173
  const source = await readFile(absolutePath, 'utf8');
@@ -262,13 +258,14 @@ export const getSitePublicAssetStatus = async (documentPath) => {
262
258
  const getPageContext = (siteRoot, documentPath) => {
263
259
  const absoluteDocumentPath = path.resolve(documentPath);
264
260
  const relativePath = toPosixPath(path.relative(siteRoot, absoluteDocumentPath));
265
- const match = relativePath.match(/^pages\/(.+)\/content\.md$/);
261
+ if (relativePath === 'root/content.md') return { contentPath: absoluteDocumentPath, imagesRoot: getSiteSourcePaths(siteRoot).images, pageLabel: 'Home', pageDirectory: homePageDirectory };
262
+ const match = relativePath.match(/^root\/pages\/(.+)\/content\.md$/);
266
263
  if (!match) return null;
267
264
 
268
265
  const pageDirectory = match[1];
269
266
  return {
270
267
  contentPath: absoluteDocumentPath,
271
- imagesRoot: path.join(siteRoot, 'pages', ...pageDirectory.split('/'), 'images'),
268
+ imagesRoot: path.join(getSiteSourcePaths(siteRoot).pages, ...pageDirectory.split('/'), 'images'),
272
269
  pageLabel: pageDirectory,
273
270
  pageDirectory,
274
271
  };
@@ -492,7 +489,7 @@ const collectPageImageRoots = async (directory, siteRoot, roots) => {
492
489
  if (!entry.isDirectory()) continue;
493
490
  const absolutePath = path.join(directory, entry.name);
494
491
  if (entry.name === 'images') {
495
- const pageDirectory = toPosixPath(path.relative(path.join(siteRoot, 'pages'), path.dirname(absolutePath)));
492
+ const pageDirectory = toPosixPath(path.relative(getSiteSourcePaths(siteRoot).pages, path.dirname(absolutePath)));
496
493
  roots.push({ imageRoot: absolutePath, pageLabel: pageDirectory });
497
494
  continue;
498
495
  }
@@ -515,7 +512,8 @@ const getContentFiles = async (siteRoot) => {
515
512
  }
516
513
  };
517
514
 
518
- await visit(path.join(siteRoot, 'pages'));
515
+ if (await fileExists(getSiteSourcePaths(siteRoot).content)) files.push(getSiteSourcePaths(siteRoot).content);
516
+ await visit(getSiteSourcePaths(siteRoot).pages);
519
517
  return files;
520
518
  };
521
519
 
@@ -534,8 +532,8 @@ const getReferencesByFilename = async (siteRoot) => {
534
532
 
535
533
  export const createSiteImageIndex = async (siteRoot) => {
536
534
  const files = [];
537
- const pageRoots = [];
538
- await collectPageImageRoots(path.join(siteRoot, 'pages'), siteRoot, pageRoots);
535
+ const pageRoots = [{ imageRoot: getSiteSourcePaths(siteRoot).images, pageLabel: 'Home' }];
536
+ await collectPageImageRoots(getSiteSourcePaths(siteRoot).pages, siteRoot, pageRoots);
539
537
  for (const root of pageRoots) {
540
538
  await collectImageFiles(root.imageRoot, root.imageRoot, root.pageLabel, files);
541
539
  }
@@ -0,0 +1,55 @@
1
+ import { getSiteSourcePaths } from './site-conventions.mjs';
2
+ import path from 'node:path';
3
+ import { readEditorLinkState, isInside } from './editor-site-links.mjs';
4
+ import { checkedPath, snapshot } from './editor-site-files.mjs';
5
+ import { createPageMovePlan } from './page-move-plan.mjs';
6
+ import { applyPageMovePlan } from './page-move-apply.mjs';
7
+
8
+ export const siteAddressApiVersion = 1;
9
+
10
+ export const getEditorPageAddresses = async (options) => {
11
+ const { structure, graph, settings, incomplete } = await readEditorLinkState(options);
12
+ const node = structure.nodes.find((entry) => (entry.contentPath ?? entry.categoryPath) === options.sourcePath);
13
+ if (!node) throw new Error('This page is no longer in the selected site. Refresh Site Tree.');
14
+ const internalLink = node.isHome ? '/' : `/${node.pagePath}/`;
15
+ const base = settings?.url?.replace(/\/$/, '') + '/';
16
+ return {
17
+ internalLink, webAddress: settings ? new URL(internalLink.slice(1), base).href : null,
18
+ segment: node.isHome ? '' : node.pageId,
19
+ aliases: graph.pagesByPathname.get(internalLink)?.aliases ?? [],
20
+ incomplete,
21
+ };
22
+ };
23
+
24
+ export const planEditorPageAddress = async ({ siteRoot, sourcePath, segment, sources = new Map() }) => {
25
+ await checkedPath(siteRoot, sourcePath);
26
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(segment)) throw new Error('Use lowercase letters, numbers and single hyphens, for example install-norna. Enter one URL segment, without slashes.');
27
+ const state = await readEditorLinkState({ siteRoot, sources });
28
+ if (state.incomplete.length) throw new Error(`Repair the site before changing an address. ${state.incomplete.join('\n')}`);
29
+ const node = state.structure.nodes.find((entry) => entry.contentPath === sourcePath);
30
+ if (!node || node.isHome) throw new Error('Choose a content page other than the homepage. Category addresses follow their folders and cannot be changed through this action.');
31
+ const sourcePaths = [...state.structure.nodes.map((entry) => entry.contentPath ?? entry.categoryPath), path.join(siteRoot, 'site-config/settings.yaml')];
32
+ if (sourcePaths.some((filename) => sources.has(filename))) throw new Error('Save or undo unsaved page, category and site-settings edits before changing an address.');
33
+ for (const filename of sourcePaths) await checkedPath(siteRoot, filename);
34
+ const to = `/${node.parentPagePath ? node.parentPagePath + '/' : ''}${segment}/`;
35
+ const plan = await createPageMovePlan({ from: `/${node.pagePath}/`, to,
36
+ graph: state.graph, publicFiles: state.publicFiles, siteStructure: state.structure,
37
+ sitePagesDir: getSiteSourcePaths(siteRoot).pages, sitePagesLabel: `${siteRoot}/root/pages`, generatedRoutes: state.generatedRoutes });
38
+ await checkedPath(siteRoot, plan.destinationDirectory);
39
+ const watched = [...new Set([...sourcePaths, ...state.publicFiles.map(({ filePath }) => filePath)])].sort();
40
+ const snapshots = [];
41
+ for (const filename of watched) snapshots.push([filename, await snapshot(filename)]);
42
+ const addresses = await getEditorPageAddresses({ siteRoot, sourcePath });
43
+ return { ...plan, siteRoot, sourcePath, segment, generatedRoutes: state.generatedRoutes,
44
+ webFrom: addresses.webAddress,
45
+ webTo: new URL(to.slice(1), state.settings.url.replace(/\/$/, '') + '/').href,
46
+ fingerprint: JSON.stringify([await snapshot(plan.sourceDirectory), snapshots, plan.to, plan.fileChanges.map(({ updatedSource }) => updatedSource)]) };
47
+ };
48
+
49
+ export const applyEditorPageAddress = async (plan, { renameDirectory } = {}) => {
50
+ const current = await planEditorPageAddress(plan);
51
+ if (current.fingerprint !== plan.fingerprint) throw new Error('The site changed after the address preview. Start again to review the new result.');
52
+ if (!isInside(plan.siteRoot, current.sourceDirectory) || !isInside(plan.siteRoot, current.destinationDirectory)) throw new Error('The page must remain inside its site.');
53
+ await applyPageMovePlan(current, { siteRoot: plan.siteRoot, generatedRoutes: current.generatedRoutes, renameDirectory });
54
+ return { sourcePath: path.join(current.destinationDirectory, 'content.md'), url: current.to };
55
+ };