@janga/norna 0.7.27 → 0.7.29
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 +41 -29
- package/astro.config.mjs +33 -3
- package/bin/norna-cli.mjs +5 -3
- package/package.json +18 -12
- package/schemas/card-list.schema.json +1 -1
- package/schemas/config.schema.json +81 -66
- package/schemas/content-frontmatter.schema.json +23 -7
- package/schemas/manifest.json +49 -3
- package/schemas/page-theme.schema.json +1585 -41
- package/schemas/root-theme.schema.json +1767 -0
- package/schemas/sitewide-content.schema.json +18 -18
- package/schemas/theme.schema.json +243 -243
- package/scripts/add-site-node.mjs +31 -250
- package/scripts/build-site.mjs +11 -6
- package/scripts/check-config.mjs +7 -23
- package/scripts/dev-local.mjs +51 -17
- package/scripts/export-theme-preset.mjs +2 -2
- package/scripts/generate-images.mjs +89 -70
- package/scripts/generate-schemas.mjs +9 -6
- package/scripts/generate-search-index.mjs +3 -2
- package/scripts/lib/attachment-render.mjs +23 -0
- package/scripts/lib/category-destinations.mjs +13 -12
- package/scripts/lib/dev-server-identity.mjs +30 -0
- package/scripts/lib/documentation-links.mjs +1 -1
- package/scripts/lib/documentation-routes.json +70 -62
- package/scripts/lib/docusaurus-migration-audit.mjs +15 -12
- package/scripts/lib/editor-attachments.mjs +69 -0
- package/scripts/lib/editor-file-policy.mjs +20 -0
- package/scripts/lib/editor-language-service.mjs +74 -73
- package/scripts/lib/editor-page-addresses.mjs +57 -0
- package/scripts/lib/editor-page-placement.mjs +102 -0
- package/scripts/lib/editor-resource-actions.mjs +216 -0
- package/scripts/lib/editor-site-editing-tree.mjs +181 -0
- package/scripts/lib/editor-site-files.mjs +154 -0
- package/scripts/lib/editor-site-links.mjs +81 -0
- package/scripts/lib/editor-site-preview.mjs +149 -0
- package/scripts/lib/editor-site-tree.mjs +158 -0
- package/scripts/lib/editor-source-files.mjs +87 -0
- package/scripts/lib/editor-theme-help.mjs +43 -0
- package/scripts/lib/locale-registry.mjs +1 -1
- package/scripts/lib/locales/en.mjs +1 -1
- package/scripts/lib/logo-assets.mjs +2 -1
- package/scripts/lib/markdown-images.mjs +59 -0
- package/scripts/lib/markdown-links.mjs +29 -9
- package/scripts/lib/navigation-model.mjs +2 -44
- package/scripts/lib/navigation-review.mjs +14 -50
- package/scripts/lib/norna-markdown-blocks.mjs +1 -58
- package/scripts/lib/norna-markdown-render-plugin.mjs +29 -0
- package/scripts/lib/page-aliases.mjs +8 -1
- package/scripts/lib/page-attachments.mjs +59 -0
- package/scripts/lib/page-markdown.mjs +11 -7
- package/scripts/lib/page-model.mjs +19 -36
- package/scripts/lib/page-move-apply.mjs +119 -0
- package/scripts/lib/page-move-plan.mjs +64 -78
- package/scripts/lib/page-order-source.mjs +22 -0
- package/scripts/lib/page-order.mjs +45 -0
- package/scripts/lib/page-placement-cli.mjs +50 -0
- package/scripts/lib/presentation.mjs +3 -3
- package/scripts/lib/project-config.mjs +10 -8
- package/scripts/lib/publication-paths.mjs +72 -0
- package/scripts/lib/schema-definitions.mjs +15 -38
- package/scripts/lib/schema-editor-metadata.mjs +16 -60
- package/scripts/lib/schema-value-definitions.mjs +1 -1
- package/scripts/lib/site-content.mjs +8 -25
- package/scripts/lib/site-conventions.mjs +19 -1
- package/scripts/lib/site-link-graph.mjs +88 -18
- package/scripts/lib/site-navigation-tree.mjs +2 -7
- package/scripts/lib/site-node-create.mjs +182 -0
- package/scripts/lib/site-paths.mjs +24 -12
- package/scripts/lib/site-source-format.mjs +12 -0
- package/scripts/lib/site-structure.mjs +86 -95
- package/scripts/lib/site-upgrade.mjs +32 -0
- package/scripts/lib/social-image-assets.mjs +2 -1
- package/scripts/lib/source-files.mjs +34 -0
- package/scripts/lib/structured-blocks.mjs +3 -1
- package/scripts/lib/theme-config.mjs +26 -68
- package/scripts/lib/theme-packages.mjs +47 -0
- package/scripts/lib/theme-presets.mjs +20 -53
- package/scripts/lib/typography.mjs +2 -2
- package/scripts/lib/yaml-config.mjs +1 -1
- package/scripts/lib/yaml-source-edit.mjs +94 -0
- package/scripts/move-site-page.mjs +46 -254
- package/scripts/show-typography.mjs +32 -17
- package/scripts/sync-content-sections.mjs +17 -17
- package/scripts/sync-site-public.mjs +44 -66
- package/scripts/upgrade-site.mjs +17 -0
- package/src/components/AreaNavigationMenu.astro +24 -0
- package/src/components/AreaNavigationPanel.astro +55 -0
- package/src/components/AreaNavigationSwitcher.astro +30 -0
- package/src/components/ContentBlocks.astro +2 -1
- package/src/components/ImageStack.astro +7 -5
- package/src/components/ImageStackEnhancement.astro +0 -7
- package/src/components/NavigationContinuity.astro +26 -0
- package/src/components/NavigationPageTree.astro +13 -4
- package/src/components/NavigationState.astro +11 -0
- package/src/components/NavigationTreeControls.astro +3 -2
- package/src/components/SearchPage.astro +11 -2
- package/src/components/SectionNavigationScript.astro +106 -26
- package/src/components/SiteNavigation.astro +45 -20
- package/src/components/SitePage.astro +38 -61
- package/src/components/SiteSection.astro +1 -0
- package/src/components/SiteTreeNavigation.astro +38 -18
- package/src/components/TopPageMenu.astro +4 -3
- package/src/components/TreeNavigationScript.astro +50 -57
- package/src/content.config.ts +11 -12
- package/src/layouts/BaseLayout.astro +15 -39
- package/src/lib/areaNavigation.ts +42 -0
- package/src/lib/imagePresentation.ts +3 -0
- package/src/lib/markdownImages.ts +101 -0
- package/src/lib/navigationContinuity.ts +212 -0
- package/src/lib/navigationFollowing.ts +28 -18
- package/src/lib/navigationState.mjs +88 -0
- package/src/lib/navigationStateScript.mjs +76 -0
- package/src/lib/navigationVisibility.ts +9 -0
- package/src/lib/pageThemes.ts +5 -42
- package/src/lib/sectionContent.ts +67 -21
- package/src/lib/siteNavigation.ts +6 -0
- package/src/lib/sitePages.ts +13 -36
- package/src/lib/sitePublicAssets.ts +2 -1
- package/src/pages/404.astro +1 -1
- package/src/pages/[...slug].astro +7 -16
- package/src/pages/index.astro +1 -3
- package/src/styles/area-navigation.css +408 -0
- package/src/styles/content.css +8 -29
- package/src/styles/media.css +24 -0
- package/src/styles/navigation-continuity.css +163 -0
- package/src/styles/page-layout.css +57 -68
- package/src/styles/responsive.css +0 -4
- package/starters/basic/README.md +9 -8
- package/starters/basic/site/{pages/000-home → root}/content.md +2 -2
- package/starters/basic/site/{config.yaml → site-config/settings.yaml} +1 -0
- package/schemas/category.schema.json +0 -20
- package/src/components/PageContentsNavigation.astro +0 -20
- /package/starters/basic/site/{pages/000-home → root}/images/.gitkeep +0 -0
- /package/starters/basic/site/{theme.yaml → root/tree-theme.yaml} +0 -0
- /package/starters/basic/site/{sitewide-content.yaml → site-config/shared-content.yaml} +0 -0
|
@@ -66,17 +66,6 @@
|
|
|
66
66
|
document.documentElement.style.setProperty('--site-top-anchor-offset', `${offset}px`);
|
|
67
67
|
};
|
|
68
68
|
|
|
69
|
-
const alignInitialHashTarget = () => {
|
|
70
|
-
if (!window.location.hash) return;
|
|
71
|
-
|
|
72
|
-
try {
|
|
73
|
-
const target = document.getElementById(decodeURIComponent(window.location.hash.slice(1)));
|
|
74
|
-
target?.scrollIntoView({ block: 'start' });
|
|
75
|
-
} catch {
|
|
76
|
-
// Invalid URL encoding is left to the browser's native hash handling.
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
69
|
const getSamePageTarget = (link: HTMLAnchorElement) => {
|
|
81
70
|
const linkUrl = new URL(link.href, window.location.href);
|
|
82
71
|
if (linkUrl.pathname !== window.location.pathname || !linkUrl.hash) return undefined;
|
|
@@ -158,35 +147,118 @@
|
|
|
158
147
|
sectionTrackingFrame = window.requestAnimationFrame(updateReadingPosition);
|
|
159
148
|
};
|
|
160
149
|
|
|
150
|
+
const positionTopPageMenu = (menu: HTMLDetailsElement) => {
|
|
151
|
+
const panel = menu.querySelector<HTMLElement>('.top-page-panel');
|
|
152
|
+
if (!panel) return;
|
|
153
|
+
panel.style.removeProperty('--top-page-panel-shift');
|
|
154
|
+
const rectangle = panel.getBoundingClientRect();
|
|
155
|
+
let shift = Math.max(0, 16 - rectangle.left)
|
|
156
|
+
- Math.max(0, rectangle.right - document.documentElement.clientWidth + 16);
|
|
157
|
+
if (menu.hasAttribute('data-area-menu')) {
|
|
158
|
+
const trigger = menu.querySelector('summary')!.getBoundingClientRect();
|
|
159
|
+
const row = menu.closest('.site-nav-row')?.getBoundingClientRect();
|
|
160
|
+
const rightEdge = Math.min(row?.right ?? Infinity, document.documentElement.clientWidth - 16);
|
|
161
|
+
const left = Math.max(16, Math.min(trigger.left, rightEdge - rectangle.width));
|
|
162
|
+
shift = left - rectangle.left;
|
|
163
|
+
}
|
|
164
|
+
panel.style.setProperty('--top-page-panel-shift', `${shift}px`);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const closeTopPageMenu = new Map<HTMLDetailsElement, () => void>();
|
|
161
168
|
topPageMenus.forEach((menu) => {
|
|
169
|
+
let openTimer: number | undefined;
|
|
170
|
+
let closeTimer: number | undefined;
|
|
171
|
+
const clearTimers = () => {
|
|
172
|
+
window.clearTimeout(openTimer);
|
|
173
|
+
window.clearTimeout(closeTimer);
|
|
174
|
+
};
|
|
175
|
+
const close = () => {
|
|
176
|
+
clearTimers();
|
|
177
|
+
menu.open = false;
|
|
178
|
+
};
|
|
179
|
+
closeTopPageMenu.set(menu, close);
|
|
162
180
|
menu.addEventListener('toggle', () => {
|
|
163
181
|
if (!menu.open) return;
|
|
164
|
-
topPageMenus.forEach((other) => { if (other !== menu) other
|
|
165
|
-
|
|
166
|
-
if (!panel) return;
|
|
167
|
-
panel.style.removeProperty('--top-page-panel-shift');
|
|
168
|
-
const rectangle = panel.getBoundingClientRect();
|
|
169
|
-
const shift = Math.max(0, 16 - rectangle.left)
|
|
170
|
-
- Math.max(0, rectangle.right - document.documentElement.clientWidth + 16);
|
|
171
|
-
panel.style.setProperty('--top-page-panel-shift', `${shift}px`);
|
|
182
|
+
topPageMenus.forEach((other) => { if (other !== menu) closeTopPageMenu.get(other)?.(); });
|
|
183
|
+
positionTopPageMenu(menu);
|
|
172
184
|
});
|
|
173
185
|
menu.addEventListener('keydown', (event) => {
|
|
174
186
|
if (event.key !== 'Escape' || !menu.open) return;
|
|
175
187
|
event.preventDefault();
|
|
176
|
-
|
|
188
|
+
close();
|
|
177
189
|
menu.querySelector('summary')?.focus();
|
|
178
190
|
});
|
|
179
191
|
menu.addEventListener('focusout', (event) => {
|
|
180
|
-
if (event.relatedTarget instanceof Node && !menu.contains(event.relatedTarget))
|
|
192
|
+
if (event.relatedTarget instanceof Node && !menu.contains(event.relatedTarget)) close();
|
|
181
193
|
});
|
|
182
194
|
menu.querySelectorAll('a').forEach((link) => {
|
|
183
|
-
link.addEventListener('click',
|
|
195
|
+
link.addEventListener('click', close);
|
|
184
196
|
});
|
|
197
|
+
if (menu.hasAttribute('data-area-switcher')) {
|
|
198
|
+
menu.addEventListener('keydown', (event) => {
|
|
199
|
+
if (event.key !== 'ArrowDown' || event.target !== menu.querySelector('summary')) return;
|
|
200
|
+
event.preventDefault();
|
|
201
|
+
menu.open = true;
|
|
202
|
+
menu.querySelector('a')?.focus();
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
if (menu.hasAttribute('data-area-menu')) {
|
|
206
|
+
const canHover = window.matchMedia('(any-hover: hover)');
|
|
207
|
+
const hoverRegion = menu.closest<HTMLElement>('.site-nav-row') ?? menu;
|
|
208
|
+
const isHoverPointer = (event: PointerEvent) => event.pointerType !== 'touch' && canHover.matches;
|
|
209
|
+
menu.addEventListener('pointerenter', (event) => {
|
|
210
|
+
if (!isHoverPointer(event)) return;
|
|
211
|
+
clearTimers();
|
|
212
|
+
openTimer = window.setTimeout(() => { menu.open = true; }, 120);
|
|
213
|
+
});
|
|
214
|
+
menu.addEventListener('pointerleave', (event) => {
|
|
215
|
+
if (isHoverPointer(event)) window.clearTimeout(openTimer);
|
|
216
|
+
});
|
|
217
|
+
// The row and its descendant panel form one continuous hover region.
|
|
218
|
+
// Crossing or pausing in its padding must not start a closing timer.
|
|
219
|
+
// Other titles remain real hit targets; no invisible overlay is needed.
|
|
220
|
+
hoverRegion.addEventListener('pointerenter', (event) => {
|
|
221
|
+
if (isHoverPointer(event)) window.clearTimeout(closeTimer);
|
|
222
|
+
});
|
|
223
|
+
hoverRegion.addEventListener('pointerleave', (event) => {
|
|
224
|
+
if (!isHoverPointer(event)) return;
|
|
225
|
+
clearTimers();
|
|
226
|
+
closeTimer = window.setTimeout(() => {
|
|
227
|
+
const focus = document.activeElement;
|
|
228
|
+
if (focus instanceof Element && menu.contains(focus) && focus.matches(':focus-visible')) return;
|
|
229
|
+
close();
|
|
230
|
+
}, 180);
|
|
231
|
+
});
|
|
232
|
+
menu.querySelector('summary')?.addEventListener('click', (event) => {
|
|
233
|
+
// Mouse users open on hover; a click must not immediately fold it
|
|
234
|
+
// away. Native activation remains available for touch and keyboard.
|
|
235
|
+
if (event.detail === 0 || event.pointerType === 'touch' || !canHover.matches) return;
|
|
236
|
+
event.preventDefault();
|
|
237
|
+
clearTimers();
|
|
238
|
+
menu.open = true;
|
|
239
|
+
});
|
|
240
|
+
menu.addEventListener('keydown', (event) => {
|
|
241
|
+
if (event.key !== 'ArrowDown' || event.target !== menu.querySelector('summary')) return;
|
|
242
|
+
event.preventDefault();
|
|
243
|
+
clearTimers();
|
|
244
|
+
menu.open = true;
|
|
245
|
+
menu.querySelector('a')?.focus();
|
|
246
|
+
});
|
|
247
|
+
}
|
|
185
248
|
});
|
|
186
249
|
if (topPageMenus.length > 0) {
|
|
250
|
+
// Hover does not move focus into the panel, so Escape must also work
|
|
251
|
+
// while focus remains in the article or elsewhere on the page.
|
|
252
|
+
document.addEventListener('keydown', (event) => {
|
|
253
|
+
if (event.key !== 'Escape') return;
|
|
254
|
+
topPageMenus.filter((menu) => menu.hasAttribute('data-area-menu') || menu.hasAttribute('data-area-switcher')).forEach((menu) => {
|
|
255
|
+
if (menu.open) event.preventDefault();
|
|
256
|
+
closeTopPageMenu.get(menu)?.();
|
|
257
|
+
});
|
|
258
|
+
});
|
|
187
259
|
document.addEventListener('pointerdown', (event) => {
|
|
188
260
|
topPageMenus.forEach((menu) => {
|
|
189
|
-
if (event.target instanceof Node && !menu.contains(event.target)) menu
|
|
261
|
+
if (event.target instanceof Node && !menu.contains(event.target)) closeTopPageMenu.get(menu)?.();
|
|
190
262
|
});
|
|
191
263
|
});
|
|
192
264
|
}
|
|
@@ -309,12 +381,20 @@
|
|
|
309
381
|
}
|
|
310
382
|
|
|
311
383
|
setAnchorOffset();
|
|
312
|
-
if (window.location.hash) window.requestAnimationFrame(alignInitialHashTarget);
|
|
313
384
|
|
|
314
385
|
if (siteTop && 'ResizeObserver' in window) {
|
|
315
|
-
new ResizeObserver(() => {
|
|
386
|
+
const navigationResizeObserver = new ResizeObserver(() => {
|
|
316
387
|
setAnchorOffset();
|
|
317
388
|
scheduleReadingPositionUpdate();
|
|
318
|
-
|
|
389
|
+
topPageMenus.filter((menu) => menu.open).forEach((menu) => {
|
|
390
|
+
if (!menu.getClientRects().length) closeTopPageMenu.get(menu)?.();
|
|
391
|
+
else positionTopPageMenu(menu);
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
navigationResizeObserver.observe(siteTop);
|
|
395
|
+
if (topPageMenus.some((menu) => menu.hasAttribute('data-area-menu') || menu.hasAttribute('data-area-switcher'))) {
|
|
396
|
+
const row = siteTop.querySelector('.site-nav-row');
|
|
397
|
+
if (row) navigationResizeObserver.observe(row);
|
|
398
|
+
}
|
|
319
399
|
}
|
|
320
400
|
</script>
|
|
@@ -1,18 +1,24 @@
|
|
|
1
1
|
---
|
|
2
|
+
import NavigationState from './NavigationState.astro';
|
|
2
3
|
import { Menu, Search, X } from '@lucide/astro';
|
|
3
4
|
import projectConfig from '../../scripts/lib/project-config.mjs';
|
|
4
5
|
import { withBasePath } from '../lib/basePath';
|
|
5
6
|
import type { SectionNavigation } from '../lib/sectionContent';
|
|
6
7
|
import {
|
|
7
8
|
getListedSiteNavigationTree,
|
|
9
|
+
getNavigationDestination,
|
|
10
|
+
type CategoryDestinationMap,
|
|
8
11
|
type SiteNavigationEntry,
|
|
9
|
-
type SiteNavigationNode,
|
|
10
12
|
} from '../lib/siteNavigation';
|
|
11
13
|
import type { SiteNode } from '../lib/sitePages';
|
|
12
14
|
import DisplaySettings from './DisplaySettings.astro';
|
|
13
15
|
import NavigationPageTree from './NavigationPageTree.astro';
|
|
14
16
|
import NavigationTreeControls from './NavigationTreeControls.astro';
|
|
15
17
|
import TopPageMenu from './TopPageMenu.astro';
|
|
18
|
+
import AreaNavigationMenu from './AreaNavigationMenu.astro';
|
|
19
|
+
import AreaNavigationSwitcher from './AreaNavigationSwitcher.astro';
|
|
20
|
+
import SiteBreadcrumbs from './SiteBreadcrumbs.astro';
|
|
21
|
+
import { hasLargeAreaMenu, withH2Outlines } from '../lib/areaNavigation';
|
|
16
22
|
import SectionNavigationScript from './SectionNavigationScript.astro';
|
|
17
23
|
import SearchNavigationScript from './SearchNavigationScript.astro';
|
|
18
24
|
|
|
@@ -29,6 +35,7 @@ interface Props {
|
|
|
29
35
|
};
|
|
30
36
|
currentPage?: SiteNode;
|
|
31
37
|
navigationEntries: SiteNavigationEntry[];
|
|
38
|
+
categoryDestinations?: CategoryDestinationMap;
|
|
32
39
|
navigationMode: 'sections' | 'top' | 'tree';
|
|
33
40
|
showTopNavigationSubmenus: boolean;
|
|
34
41
|
hasNavigationRailFrame: boolean;
|
|
@@ -46,18 +53,21 @@ interface Props {
|
|
|
46
53
|
};
|
|
47
54
|
};
|
|
48
55
|
searchPage?: boolean;
|
|
56
|
+
areaReadingFrame?: boolean;
|
|
49
57
|
}
|
|
50
58
|
|
|
51
59
|
const {
|
|
52
60
|
logo,
|
|
53
61
|
currentPage,
|
|
54
62
|
navigationEntries,
|
|
63
|
+
categoryDestinations,
|
|
55
64
|
navigationMode,
|
|
56
65
|
showTopNavigationSubmenus,
|
|
57
66
|
hasNavigationRailFrame,
|
|
58
67
|
frameColors,
|
|
59
68
|
readerPreferences,
|
|
60
69
|
searchPage = false,
|
|
70
|
+
areaReadingFrame = false,
|
|
61
71
|
} = Astro.props;
|
|
62
72
|
const siteTopBackgroundColor = frameColors.backgroundColor === 'transparent'
|
|
63
73
|
? 'var(--color-page)'
|
|
@@ -68,12 +78,7 @@ const navigationStyle = [
|
|
|
68
78
|
...(logo?.height ? [`--site-brand-logo-height: ${logo.height}`] : []),
|
|
69
79
|
].join('; ');
|
|
70
80
|
const siteNavigationTree = getListedSiteNavigationTree(navigationEntries);
|
|
71
|
-
const mobileNavigationTree =
|
|
72
|
-
? getListedSiteNavigationTree(navigationEntries.map((entry) => ({
|
|
73
|
-
...entry,
|
|
74
|
-
headings: entry.headings.filter((heading) => heading.depth === 2),
|
|
75
|
-
})))
|
|
76
|
-
: siteNavigationTree;
|
|
81
|
+
const mobileNavigationTree = withH2Outlines(siteNavigationTree);
|
|
77
82
|
const topLevelNavigation = siteNavigationTree;
|
|
78
83
|
const showSiteNavigation = topLevelNavigation.some(({ node }) => !node.isHome);
|
|
79
84
|
const homeNode = navigationEntries.find(({ node }) => node.isHome)?.node;
|
|
@@ -88,7 +93,6 @@ const isCurrentBranch = (node: SiteNode) => Boolean(currentPage && (
|
|
|
88
93
|
|| currentPage.pageDirectory.startsWith(`${node.pageDirectory}/pages/`)
|
|
89
94
|
));
|
|
90
95
|
const pageHref = (page: SiteNode) => withBasePath(projectConfig.site.basePath, page.pathname);
|
|
91
|
-
const getNavigationPage = (node: SiteNavigationNode) => node.node;
|
|
92
96
|
const hasSectionMenu = (pageSections: SectionNavigation[]) => pageSections.length > 1;
|
|
93
97
|
const showSinglePageTopLink = !showSiteNavigation;
|
|
94
98
|
const currentPageSectionCount = currentPageNavigation?.sections.length ?? 0;
|
|
@@ -131,35 +135,41 @@ const needsNavigationScript = showMobileNavigation || hasCurrentPageSectionMenu
|
|
|
131
135
|
>
|
|
132
136
|
{showNavigationRow && (
|
|
133
137
|
<div class:list={['site-nav-row', { 'site-nav-row-mobile-only': !showDesktopNavigationRow }]}>
|
|
134
|
-
{logo && (
|
|
138
|
+
{(logo || areaReadingFrame) && (
|
|
135
139
|
<div class="site-nav-leading">
|
|
136
|
-
<a class="site-brand" href={pageHref(homePage)}>
|
|
137
|
-
<img class="site-brand-logo" src={withBasePath(projectConfig.site.basePath, logo.src)} alt={logo.alt} />
|
|
140
|
+
<a class="site-brand" href={pageHref(homePage)} aria-label={projectConfig.locale.labels.returnHome} title={projectConfig.locale.labels.returnHome}>
|
|
141
|
+
{logo ? <img class="site-brand-logo" src={withBasePath(projectConfig.site.basePath, logo.src)} alt={logo.alt} /> : homePage.title}
|
|
138
142
|
</a>
|
|
139
143
|
</div>
|
|
140
144
|
)}
|
|
141
145
|
|
|
142
146
|
<div class="site-nav-actions">
|
|
143
|
-
{showSiteNavigation && (
|
|
147
|
+
{showSiteNavigation && !areaReadingFrame && (
|
|
144
148
|
<nav class="site-nav" aria-label={projectConfig.locale.labels.siteNavigation}>
|
|
145
149
|
<ul>
|
|
146
150
|
{topLevelNavigation.map((navigationNode) => {
|
|
147
|
-
const destination =
|
|
151
|
+
const destination = getNavigationDestination(navigationNode.node, categoryDestinations);
|
|
148
152
|
if (!destination) return null;
|
|
149
153
|
const nodeIsCurrentPage = isCurrentPage(navigationNode.node);
|
|
154
|
+
const useAreaMenu = navigationMode === 'tree';
|
|
150
155
|
return (
|
|
151
156
|
<li class:list={['site-nav-item', {
|
|
152
|
-
'site-nav-item-current-branch': !nodeIsCurrentPage && isCurrentBranch(navigationNode.node),
|
|
157
|
+
'site-nav-item-current-branch': (!nodeIsCurrentPage || useAreaMenu) && isCurrentBranch(navigationNode.node),
|
|
153
158
|
}]}>
|
|
154
|
-
|
|
159
|
+
{useAreaMenu && navigationNode.children.length > 0 && !hasLargeAreaMenu(navigationNode) ? <AreaNavigationMenu
|
|
160
|
+
node={navigationNode}
|
|
161
|
+
currentPage={currentPage}
|
|
162
|
+
categoryDestinations={categoryDestinations}
|
|
163
|
+
/> : <a href={pageHref(destination)} aria-current={nodeIsCurrentPage ? 'page' : undefined}>
|
|
155
164
|
{navigationNode.node.title}
|
|
156
|
-
</a>
|
|
157
|
-
<TopPageMenu
|
|
165
|
+
</a>}
|
|
166
|
+
{!useAreaMenu && <TopPageMenu
|
|
158
167
|
node={navigationNode}
|
|
159
168
|
currentPage={currentPage}
|
|
169
|
+
categoryDestinations={categoryDestinations}
|
|
160
170
|
showSections={navigationMode === 'top'}
|
|
161
171
|
showChildren={showTopNavigationSubmenus}
|
|
162
|
-
/>
|
|
172
|
+
/>}
|
|
163
173
|
</li>
|
|
164
174
|
);
|
|
165
175
|
})}
|
|
@@ -211,9 +221,10 @@ const needsNavigationScript = showMobileNavigation || hasCurrentPageSectionMenu
|
|
|
211
221
|
</div>
|
|
212
222
|
<div
|
|
213
223
|
class="mobile-site-nav"
|
|
214
|
-
data-navigation-root={pageHref(homePage)}
|
|
215
|
-
data-navigation-state-root={pageHref(homePage)}
|
|
224
|
+
data-navigation-root={`area:${pageHref(homePage)}`}
|
|
225
|
+
data-navigation-state-root={`area:${pageHref(homePage)}`}
|
|
216
226
|
>
|
|
227
|
+
<NavigationState scope="mobile" />
|
|
217
228
|
{showSiteNavigation && (
|
|
218
229
|
<nav class="mobile-nav-view" aria-label={projectConfig.locale.labels.siteNavigation}>
|
|
219
230
|
<h2 tabindex="-1">{projectConfig.locale.labels.siteNavigation}</h2>
|
|
@@ -224,6 +235,7 @@ const needsNavigationScript = showMobileNavigation || hasCurrentPageSectionMenu
|
|
|
224
235
|
<NavigationPageTree
|
|
225
236
|
nodes={mobileNavigationTree}
|
|
226
237
|
currentPage={currentPage}
|
|
238
|
+
categoryDestinations={categoryDestinations}
|
|
227
239
|
variant="mobile"
|
|
228
240
|
integratePageSections={navigationMode === 'tree' || navigationMode === 'top'}
|
|
229
241
|
/>
|
|
@@ -260,6 +272,19 @@ const needsNavigationScript = showMobileNavigation || hasCurrentPageSectionMenu
|
|
|
260
272
|
</details>
|
|
261
273
|
)}
|
|
262
274
|
</div>
|
|
275
|
+
{areaReadingFrame && (
|
|
276
|
+
<div class="site-reading-location">
|
|
277
|
+
<AreaNavigationSwitcher
|
|
278
|
+
nodes={siteNavigationTree}
|
|
279
|
+
currentPage={currentPage}
|
|
280
|
+
categoryDestinations={categoryDestinations}
|
|
281
|
+
label={searchPage ? projectConfig.locale.labels.search : undefined}
|
|
282
|
+
/>
|
|
283
|
+
{currentPage
|
|
284
|
+
? <SiteBreadcrumbs currentPage={currentPage} nodes={navigationEntries.map(({ node }) => node)} />
|
|
285
|
+
: <span class="site-reading-location-label">{projectConfig.locale.labels.search}</span>}
|
|
286
|
+
</div>
|
|
287
|
+
)}
|
|
263
288
|
</div>
|
|
264
289
|
)}
|
|
265
290
|
|
|
@@ -9,7 +9,6 @@ import ImageStackEnhancement from './ImageStackEnhancement.astro';
|
|
|
9
9
|
import SiteBreadcrumbs from './SiteBreadcrumbs.astro';
|
|
10
10
|
import SiteNavigation from './SiteNavigation.astro';
|
|
11
11
|
import SiteBanners from './SiteBanners.astro';
|
|
12
|
-
import PageContentsNavigation from './PageContentsNavigation.astro';
|
|
13
12
|
import PageSequenceNavigation from './PageSequenceNavigation.astro';
|
|
14
13
|
import SiteSection from './SiteSection.astro';
|
|
15
14
|
import SiteTreeNavigation from './SiteTreeNavigation.astro';
|
|
@@ -22,47 +21,37 @@ import {
|
|
|
22
21
|
flattenSiteNavigationTree,
|
|
23
22
|
getDirectChildPages,
|
|
24
23
|
getListedSiteNavigationTree,
|
|
24
|
+
getNavigationDestination,
|
|
25
25
|
getSequentialPageNavigation,
|
|
26
26
|
} from '../lib/siteNavigation';
|
|
27
27
|
import { getToday, isVisibleToday } from '../lib/visibility';
|
|
28
28
|
import { getLogoAsset } from '../lib/sitePublicAssets';
|
|
29
29
|
import {
|
|
30
30
|
resolveNavigationModel,
|
|
31
|
-
resolvePageContentsPlacement,
|
|
32
31
|
} from '../../scripts/lib/navigation-model.mjs';
|
|
33
32
|
import { projectConfig, resolveThemeVisualConfig } from '../../scripts/lib/project-config.mjs';
|
|
34
33
|
import { resolveEditSourceTarget } from '../../scripts/lib/edit-source-link.mjs';
|
|
35
34
|
import { getTextWidthCssValue, resolveFrameColors, resolvePagePresentation } from '../../scripts/lib/presentation.mjs';
|
|
36
|
-
import { mergePageThemeConfig } from '../../scripts/lib/theme-presets.mjs';
|
|
37
35
|
|
|
38
36
|
type SiteEntry = CollectionEntry<'site'>;
|
|
39
37
|
|
|
40
38
|
interface Props {
|
|
41
39
|
entry?: SiteEntry;
|
|
42
|
-
categoryPath?: string;
|
|
43
40
|
}
|
|
44
41
|
|
|
45
|
-
const { entry
|
|
42
|
+
const { entry } = Astro.props;
|
|
46
43
|
const siteDirectory = (process.env.NORNA_SITE_DIR ?? 'site').trim();
|
|
47
44
|
const siteModel = await getSiteModel(await getCollection('site'));
|
|
45
|
+
const categoryDestinations = siteModel.categoryDestinations.byPathname;
|
|
48
46
|
const { nodes: siteNodes, pages: sitePages } = siteModel;
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
?? sitePages.find((page) => page.entry.id === entry?.id);
|
|
52
|
-
if (!currentPage) throw new Error(`Page entry "${entry?.id ?? categoryPath}" is missing from the site page model.`);
|
|
53
|
-
const [siteTheme] = await getCollection('theme');
|
|
54
|
-
if (!siteTheme) {
|
|
55
|
-
throw new Error('site/theme.yaml is required. Add a root theme before building the site.');
|
|
56
|
-
}
|
|
47
|
+
const currentPage = sitePages.find((page) => page.entry.id === entry?.id);
|
|
48
|
+
if (!currentPage) throw new Error(`Page entry "${entry?.id}" is missing from the site page model.`);
|
|
57
49
|
const [sitewide] = await getCollection('sitewide');
|
|
58
50
|
const sitewideData = sitewide?.data ?? {};
|
|
59
51
|
const homePage = sitePages.find((page) => page.isHome) ?? currentPage;
|
|
60
|
-
const siteThemeData = siteTheme.data;
|
|
61
52
|
const pageTheme = await getPageTheme(currentPage.pageDirectory);
|
|
62
|
-
const themeData =
|
|
63
|
-
const themeLabel = pageTheme
|
|
64
|
-
? `${siteDirectory || 'site'}/${pageTheme.id}`
|
|
65
|
-
: `${siteDirectory || 'site'}/${siteTheme.id}`;
|
|
53
|
+
const themeData = pageTheme.data;
|
|
54
|
+
const themeLabel = pageTheme.id;
|
|
66
55
|
const visualTheme = resolveThemeVisualConfig(themeData, themeLabel);
|
|
67
56
|
const logoAsset = getLogoAsset();
|
|
68
57
|
const logoSettings = sitewideData.logo;
|
|
@@ -99,20 +88,20 @@ const renderedNavigationEntries = flattenSiteNavigationTree(listedNavigationTree
|
|
|
99
88
|
const pageSequence = getSequentialPageNavigation(listedNavigationTree, currentPage.pagePath);
|
|
100
89
|
const childPages = getDirectChildPages(listedNavigationTree, currentPage.pagePath);
|
|
101
90
|
const siteHtml = entry?.rendered?.html ?? '';
|
|
102
|
-
const resolvedSections: ResolvedSection[] =
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
})
|
|
114
|
-
}
|
|
115
|
-
}
|
|
91
|
+
const resolvedSections: ResolvedSection[] = await getSectionsContent(siteHtml, currentPage, childPages);
|
|
92
|
+
if (currentPage.kind === 'page' && currentPage.entry.data.page?.listChildren) {
|
|
93
|
+
const lastSection = resolvedSections.at(-1);
|
|
94
|
+
if (lastSection && childPages.length > 0) {
|
|
95
|
+
lastSection.contentBlocks.push({
|
|
96
|
+
type: 'page-list',
|
|
97
|
+
pages: childPages.map((child) => ({
|
|
98
|
+
title: child.title,
|
|
99
|
+
pathname: child.pathname,
|
|
100
|
+
description: child.entry.data.page?.description,
|
|
101
|
+
})),
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
116
105
|
const allContentBlocks = resolvedSections.flatMap((section) => flattenContentBlocks(section.contentBlocks));
|
|
117
106
|
const hasTabs = allContentBlocks.some((block) => block.type === 'tabs');
|
|
118
107
|
const hasNotes = allContentBlocks.some((block) => (
|
|
@@ -126,6 +115,7 @@ const hasTables = allContentBlocks.some((block) => (
|
|
|
126
115
|
));
|
|
127
116
|
const hasImageStack = allContentBlocks.some((block) => (
|
|
128
117
|
block.type === 'image-stack'
|
|
118
|
+
|| (block.type === 'html' && block.html.includes('data-norna-managed-image='))
|
|
129
119
|
));
|
|
130
120
|
const hasPrioritizableImage = (section: ResolvedSection) => section.contentBlocks.some((block) => (
|
|
131
121
|
block.type === 'image-stack'
|
|
@@ -167,33 +157,21 @@ const pagePresentation = resolvePagePresentation(themeData, themeLabel, {
|
|
|
167
157
|
navigationMode: navigationModel.mode,
|
|
168
158
|
});
|
|
169
159
|
const pageTextWidth = getTextWidthCssValue(pagePresentation.typography.values.body.width);
|
|
170
|
-
const frameColors = resolveFrameColors({ theme:
|
|
160
|
+
const frameColors = resolveFrameColors({ theme: themeData, sourceLabel: themeLabel });
|
|
171
161
|
const hasTreeNavigation = navigationModel.mode === 'tree';
|
|
172
|
-
|
|
162
|
+
// Keep one reading frame across tree-mode pages, including pages with no H2.
|
|
163
|
+
// Home remains the site's entry point with the global navigation row.
|
|
164
|
+
const areaReadingFrame = hasTreeNavigation && !currentPage.isHome;
|
|
173
165
|
const currentPageNavigation = siteNavigation.find(({ node }) => (
|
|
174
166
|
node.kind === 'page' && node.pathname === currentPage.pathname
|
|
175
167
|
));
|
|
176
168
|
const pageContentsHeadings = currentPageNavigation?.headings.filter(({ depth }) => depth === 2 || depth === 3) ?? [];
|
|
177
|
-
const
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
depth: Math.max(1, node.depth),
|
|
181
|
-
isHome: node.isHome,
|
|
182
|
-
listed: node.navigation.listed,
|
|
183
|
-
pagePath: node.pagePath,
|
|
184
|
-
})),
|
|
185
|
-
currentPage: {
|
|
186
|
-
isHome: currentPage.isHome,
|
|
187
|
-
pagePath: currentPage.pagePath,
|
|
188
|
-
},
|
|
189
|
-
headingCount: pageContentsHeadings.length,
|
|
190
|
-
});
|
|
191
|
-
const hasContentsNavigation = pageContentsPlacement.placement !== 'none';
|
|
192
|
-
const hasTrackedSectionNavigation = hasContentsNavigation
|
|
193
|
-
|| (hasTreeNavigation && currentPage.isHome && pageContentsHeadings.length >= 2);
|
|
169
|
+
const showTreeNavigation = hasTreeNavigation && !currentPage.isHome;
|
|
170
|
+
const hasContentsNavigation = showTreeNavigation && pageContentsHeadings.some(({ depth }) => depth === 2);
|
|
171
|
+
const hasTrackedSectionNavigation = hasTreeNavigation && pageContentsHeadings.some(({ depth }) => depth === 2);
|
|
194
172
|
const integratePageContentsInTree = hasTreeNavigation;
|
|
195
|
-
|
|
196
|
-
const hasNavigationRailFrame =
|
|
173
|
+
// Home's global row and the reading layout share the site's wide coordinates.
|
|
174
|
+
const hasNavigationRailFrame = (
|
|
197
175
|
navigationModel.requestedMode === 'tree'
|
|
198
176
|
|| (
|
|
199
177
|
navigationModel.requestedMode === 'automatic'
|
|
@@ -215,30 +193,35 @@ const reserveBreadcrumbSpace = showTreeNavigation || showBreadcrumbs;
|
|
|
215
193
|
footer={sitewideData.footer}
|
|
216
194
|
contentsNavigation={hasTrackedSectionNavigation}
|
|
217
195
|
searchable
|
|
196
|
+
areaNavigation={hasTreeNavigation}
|
|
197
|
+
areaReadingFrame={areaReadingFrame}
|
|
218
198
|
>
|
|
219
199
|
<SiteNavigation
|
|
220
200
|
logo={logo}
|
|
221
201
|
currentPage={currentPage}
|
|
222
202
|
navigationEntries={siteNavigation}
|
|
203
|
+
categoryDestinations={categoryDestinations}
|
|
223
204
|
navigationMode={navigationModel.mode}
|
|
224
205
|
showTopNavigationSubmenus={navigationModel.requestedMode !== 'automatic' && navigationModel.mode !== 'tree'}
|
|
225
206
|
hasNavigationRailFrame={hasNavigationRailFrame}
|
|
226
207
|
frameColors={frameColors}
|
|
227
208
|
readerPreferences={pagePresentation.readerPreferences}
|
|
209
|
+
areaReadingFrame={areaReadingFrame}
|
|
228
210
|
/>
|
|
229
211
|
{siteBanners.length > 0 && <SiteBanners banners={siteBanners} />}
|
|
230
212
|
|
|
231
213
|
<div class:list={['site-page-layout', {
|
|
232
214
|
'site-page-layout-tree': showTreeNavigation,
|
|
233
|
-
'site-page-layout-contents': showContentsRail,
|
|
234
215
|
}]}
|
|
235
|
-
data-page-contents-placement={hasContentsNavigation ?
|
|
216
|
+
data-page-contents-placement={hasContentsNavigation ? 'page-tree' : undefined}
|
|
236
217
|
>
|
|
237
218
|
{showTreeNavigation && (
|
|
238
219
|
<SiteTreeNavigation
|
|
239
220
|
currentPage={currentPage}
|
|
240
221
|
navigationEntries={siteNavigation}
|
|
222
|
+
categoryDestinations={categoryDestinations}
|
|
241
223
|
integratePageSections={integratePageContentsInTree}
|
|
224
|
+
areaReadingFrame={areaReadingFrame}
|
|
242
225
|
/>
|
|
243
226
|
)}
|
|
244
227
|
<main
|
|
@@ -273,12 +256,6 @@ const reserveBreadcrumbSpace = showTreeNavigation || showBreadcrumbs;
|
|
|
273
256
|
)}
|
|
274
257
|
<PageSequenceNavigation previous={pageSequence.previous} next={pageSequence.next} />
|
|
275
258
|
</main>
|
|
276
|
-
{showContentsRail && (
|
|
277
|
-
<PageContentsNavigation
|
|
278
|
-
headings={pageContentsHeadings}
|
|
279
|
-
pageTitle={currentPage.title}
|
|
280
|
-
/>
|
|
281
|
-
)}
|
|
282
259
|
</div>
|
|
283
260
|
{hasTreeNavigation && <TreeNavigationScript />}
|
|
284
261
|
{hasCodeBlocks && <CodeBlockCopyScript />}
|
|
@@ -109,6 +109,7 @@ const bodyWidth = {
|
|
|
109
109
|
};
|
|
110
110
|
const hasMedia = flattenContentBlocks(section.contentBlocks).some((block) => (
|
|
111
111
|
block.type === 'image-stack'
|
|
112
|
+
|| (block.type === 'html' && block.html.includes('data-norna-managed-image='))
|
|
112
113
|
|| block.type === 'image-carousel'
|
|
113
114
|
|| (block.type === 'card-list' && block.cards.some((card) => card.image))
|
|
114
115
|
));
|
|
@@ -1,45 +1,65 @@
|
|
|
1
1
|
---
|
|
2
2
|
import projectConfig from '../../scripts/lib/project-config.mjs';
|
|
3
3
|
import { withBasePath } from '../lib/basePath';
|
|
4
|
-
import { getListedSiteNavigationTree, type SiteNavigationEntry } from '../lib/siteNavigation';
|
|
4
|
+
import { getListedSiteNavigationTree, getNavigationDestination, type CategoryDestinationMap, type SiteNavigationEntry } from '../lib/siteNavigation';
|
|
5
|
+
import { resolveAreaNavigation, withH2Outlines } from '../lib/areaNavigation';
|
|
5
6
|
import type { SiteNode } from '../lib/sitePages';
|
|
6
7
|
import NavigationPageTree from './NavigationPageTree.astro';
|
|
7
8
|
import NavigationTreeControls from './NavigationTreeControls.astro';
|
|
9
|
+
import NavigationState from './NavigationState.astro';
|
|
10
|
+
import PageContentsLinks from './PageContentsLinks.astro';
|
|
8
11
|
|
|
9
12
|
interface Props {
|
|
10
13
|
currentPage: SiteNode;
|
|
11
14
|
navigationEntries: SiteNavigationEntry[];
|
|
15
|
+
categoryDestinations?: CategoryDestinationMap;
|
|
12
16
|
integratePageSections?: boolean;
|
|
17
|
+
areaReadingFrame?: boolean;
|
|
13
18
|
}
|
|
14
19
|
|
|
15
|
-
const { currentPage, navigationEntries, integratePageSections = false } = Astro.props;
|
|
16
|
-
const roots = getListedSiteNavigationTree(navigationEntries);
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
))
|
|
21
|
-
|
|
20
|
+
const { currentPage, navigationEntries, categoryDestinations, integratePageSections = false, areaReadingFrame = false } = Astro.props;
|
|
21
|
+
const roots = withH2Outlines(getListedSiteNavigationTree(navigationEntries));
|
|
22
|
+
const area = resolveAreaNavigation(roots, currentPage);
|
|
23
|
+
const activeRoot = area.localRoot;
|
|
24
|
+
const ownOutline = !activeRoot
|
|
25
|
+
? navigationEntries.find(({ node }) => node.pagePath === currentPage.pagePath)?.headings.filter(({ depth }) => depth === 2) ?? []
|
|
26
|
+
: [];
|
|
27
|
+
const navigationPage = activeRoot?.node ?? currentPage;
|
|
28
|
+
const navigationRoot = navigationPage.pagePath ? `/${navigationPage.pagePath}/` : '/';
|
|
22
29
|
const navigationStateRoot = withBasePath(projectConfig.site.basePath, '/');
|
|
30
|
+
const showAreaTitle = activeRoot?.node.kind === 'category';
|
|
31
|
+
const showReadingAreaTitle = areaReadingFrame
|
|
32
|
+
&& navigationPage.pagePath !== area?.globalRoot?.node.pagePath;
|
|
33
|
+
const localNodes = activeRoot ? (showAreaTitle ? activeRoot.children : [activeRoot]) : [];
|
|
34
|
+
// Retain the reviewed state namespace so existing sessions keep their position.
|
|
23
35
|
---
|
|
24
36
|
|
|
25
|
-
{activeRoot && (
|
|
37
|
+
{(activeRoot || ownOutline.length > 0) && (
|
|
26
38
|
<aside
|
|
27
39
|
id="tree-local-navigation"
|
|
28
40
|
class="tree-local-navigation"
|
|
29
|
-
data-navigation-root={navigationRoot}
|
|
30
|
-
data-navigation-state-root={navigationStateRoot}
|
|
41
|
+
data-navigation-root={`area:${navigationRoot}`}
|
|
42
|
+
data-navigation-state-root={`area:${navigationStateRoot}`}
|
|
43
|
+
data-navigation-area={activeRoot?.node.pagePath}
|
|
44
|
+
data-navigation-outline={ownOutline.length > 0 ? currentPage.pagePath : undefined}
|
|
31
45
|
>
|
|
32
|
-
<
|
|
46
|
+
<NavigationState scope="desktop" />
|
|
47
|
+
{showReadingAreaTitle && <p class="navigation-area-title">{navigationPage.title}</p>}
|
|
48
|
+
{!areaReadingFrame && (showAreaTitle || ownOutline.length > 0) && <a class="navigation-area-title" href={withBasePath(projectConfig.site.basePath,
|
|
49
|
+
getNavigationDestination(navigationPage, categoryDestinations).pathname)}>{navigationPage.title}</a>}
|
|
50
|
+
{activeRoot && <NavigationTreeControls
|
|
33
51
|
idPrefix="desktop-tree-navigation"
|
|
34
|
-
nodes={
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
52
|
+
nodes={localNodes}
|
|
53
|
+
forceVisible
|
|
54
|
+
/>}
|
|
55
|
+
<nav aria-label={`${navigationPage.title}: ${projectConfig.locale.labels.siteNavigation}`}>
|
|
56
|
+
{activeRoot ? <NavigationPageTree
|
|
57
|
+
nodes={localNodes}
|
|
39
58
|
currentPage={currentPage}
|
|
59
|
+
categoryDestinations={categoryDestinations}
|
|
40
60
|
variant="sidebar"
|
|
41
61
|
integratePageSections={integratePageSections}
|
|
42
|
-
/>
|
|
62
|
+
/> : <PageContentsLinks headings={ownOutline} variant="desktop" />}
|
|
43
63
|
</nav>
|
|
44
64
|
</aside>
|
|
45
65
|
)}
|
|
@@ -2,17 +2,18 @@
|
|
|
2
2
|
import { ChevronDown } from '@lucide/astro';
|
|
3
3
|
import projectConfig from '../../scripts/lib/project-config.mjs';
|
|
4
4
|
import { withBasePath } from '../lib/basePath';
|
|
5
|
-
import type { SiteNavigationNode } from '../lib/siteNavigation';
|
|
5
|
+
import type { CategoryDestinationMap, SiteNavigationNode } from '../lib/siteNavigation';
|
|
6
6
|
import type { SiteNode } from '../lib/sitePages';
|
|
7
7
|
import NavigationPageTree from './NavigationPageTree.astro';
|
|
8
8
|
|
|
9
9
|
interface Props {
|
|
10
10
|
node: SiteNavigationNode;
|
|
11
11
|
currentPage?: SiteNode;
|
|
12
|
+
categoryDestinations?: CategoryDestinationMap;
|
|
12
13
|
showSections: boolean;
|
|
13
14
|
showChildren: boolean;
|
|
14
15
|
}
|
|
15
|
-
const { node, currentPage, showSections, showChildren } = Astro.props;
|
|
16
|
+
const { node, currentPage, categoryDestinations, showSections, showChildren } = Astro.props;
|
|
16
17
|
const sections = showSections ? node.sections : [];
|
|
17
18
|
const children = showChildren ? node.children : [];
|
|
18
19
|
const pageHref = node.node.kind === 'page'
|
|
@@ -39,7 +40,7 @@ const label = `${projectConfig.locale.labels.navigationMenu}: ${node.node.title}
|
|
|
39
40
|
{children.length > 0 && (
|
|
40
41
|
<nav class="top-page-children" aria-label={`${projectConfig.locale.labels.navigationChildren}: ${node.node.title}`}>
|
|
41
42
|
{sections.length > 0 && <p>{projectConfig.locale.labels.navigationChildren}</p>}
|
|
42
|
-
<NavigationPageTree nodes={children} currentPage={currentPage} variant="submenu" />
|
|
43
|
+
<NavigationPageTree nodes={children} currentPage={currentPage} categoryDestinations={categoryDestinations} variant="submenu" />
|
|
43
44
|
</nav>
|
|
44
45
|
)}
|
|
45
46
|
</div>
|