@janga/norna 0.7.22 → 0.7.24
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 +14 -18
- package/astro.config.mjs +15 -0
- package/bin/norna-cli.mjs +8 -2
- package/package.json +28 -3
- package/schemas/category.schema.json +2 -2
- package/schemas/config.schema.json +52 -24
- package/schemas/content-frontmatter.schema.json +25 -5
- package/schemas/page-theme.schema.json +53 -25
- package/schemas/sitewide-content.schema.json +19 -18
- package/schemas/theme.schema.json +385 -252
- package/scripts/build-site.mjs +1 -0
- package/scripts/check-config.mjs +36 -5
- package/scripts/deploy-site.mjs +0 -1
- package/scripts/dev-local.mjs +175 -112
- package/scripts/generate-search-index.mjs +57 -0
- package/scripts/init-site.mjs +1 -0
- package/scripts/lib/code-fence-metadata.mjs +223 -0
- package/scripts/lib/content-sync-apply.mjs +34 -0
- package/scripts/lib/content-sync-plan.mjs +179 -0
- package/scripts/lib/edit-source-link.mjs +49 -0
- package/scripts/lib/editor-language-service.mjs +160 -12
- package/scripts/lib/heading-ids.mjs +1 -1
- package/scripts/lib/image-presentation.mjs +6 -0
- package/scripts/lib/markdown-links.mjs +182 -0
- package/scripts/lib/navigation-model.mjs +44 -12
- package/scripts/lib/navigation-review.mjs +396 -0
- package/scripts/lib/norna-markdown-blocks.mjs +74 -26
- package/scripts/lib/norna-markdown-render-plugin.mjs +64 -1
- package/scripts/lib/page-aliases.mjs +184 -0
- package/scripts/lib/page-markdown.mjs +88 -7
- package/scripts/lib/page-move-plan.mjs +659 -0
- package/scripts/lib/presentation-palette-metadata.mjs +1 -1
- package/scripts/lib/presentation.mjs +10 -37
- package/scripts/lib/project-config.mjs +146 -19
- package/scripts/lib/public-asset-conventions.mjs +36 -2
- package/scripts/lib/schema-definitions.mjs +43 -15
- package/scripts/lib/schema-editor-metadata.mjs +60 -32
- package/scripts/lib/schema-value-definitions.mjs +6 -2
- package/scripts/lib/semantic-callouts.mjs +128 -0
- package/scripts/lib/site-content.mjs +28 -5
- package/scripts/lib/site-link-graph.mjs +357 -0
- package/scripts/lib/site-navigation-tree.mjs +85 -0
- package/scripts/lib/site-page-urls.mjs +10 -0
- package/scripts/lib/sitemap.mjs +34 -0
- package/scripts/lib/social-image-assets.mjs +30 -0
- package/scripts/lib/theme-presets.mjs +53 -25
- package/scripts/lib/theme-profiles.mjs +6 -8
- package/scripts/lib/yaml-config.mjs +6 -2
- package/scripts/move-site-page.mjs +256 -0
- package/scripts/review-navigation.mjs +32 -0
- package/scripts/sync-content-sections.mjs +182 -216
- package/scripts/sync-site-public.mjs +43 -3
- package/src/components/CodeBlockCopyScript.astro +97 -0
- package/src/components/DisplaySettings.astro +4 -4
- package/src/components/EditSourceLink.astro +17 -0
- package/src/components/ImageCarousel.astro +6 -3
- package/src/components/NavigationPageTree.astro +81 -86
- package/src/components/NavigationTreeControls.astro +77 -0
- package/src/components/PageAliasRedirect.astro +49 -0
- package/src/components/PageContentsNavigation.astro +21 -0
- package/src/components/PageList.astro +33 -0
- package/src/components/PageSequenceNavigation.astro +37 -0
- package/src/components/SearchPage.astro +151 -0
- package/src/components/SectionNavigationScript.astro +62 -6
- package/src/components/SiteNavigation.astro +50 -11
- package/src/components/SitePage.astro +113 -20
- package/src/components/SiteSection.astro +16 -3
- package/src/components/SiteTreeNavigation.astro +8 -2
- package/src/components/TreeNavigationScript.astro +207 -26
- package/src/content.config.ts +5 -2
- package/src/layouts/BaseLayout.astro +71 -10
- package/src/lib/readerPreferencesScript.mjs +2 -2
- package/src/lib/sectionContent.ts +32 -3
- package/src/lib/siteNavigation.ts +31 -52
- package/src/lib/sitePages.ts +3 -2
- package/src/lib/sitePublicAssets.ts +1 -0
- package/src/pages/404.astro +110 -0
- package/src/pages/[...slug].astro +45 -8
- package/src/styles/content.css +718 -0
- package/src/styles/foundations.css +284 -0
- package/src/styles/global.css +6 -2510
- package/src/styles/media.css +518 -0
- package/src/styles/navigation.css +526 -0
- package/src/styles/page-layout.css +786 -0
- package/src/styles/responsive.css +594 -0
- package/starters/basic/README.md +6 -19
- package/starters/basic/package.json +1 -0
- package/starters/basic/site/pages/000-home/content.md +13 -50
|
@@ -23,6 +23,36 @@
|
|
|
23
23
|
.filter((heading) => trackedHeadingIds.has(heading.id))
|
|
24
24
|
: [];
|
|
25
25
|
let sectionTrackingFrame: number | undefined;
|
|
26
|
+
let sectionTrackingPaused = false;
|
|
27
|
+
let sectionTrackingResumeTimer: number | undefined;
|
|
28
|
+
|
|
29
|
+
const resumeSectionTracking = () => {
|
|
30
|
+
window.clearTimeout(sectionTrackingResumeTimer);
|
|
31
|
+
window.removeEventListener('scrollend', resumeSectionTracking);
|
|
32
|
+
sectionTrackingPaused = false;
|
|
33
|
+
sectionTrackingResumeTimer = undefined;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const pauseSectionTrackingForAnchorNavigation = () => {
|
|
37
|
+
if (!sectionTrackingEnabled) return;
|
|
38
|
+
|
|
39
|
+
sectionTrackingPaused = true;
|
|
40
|
+
if (sectionTrackingFrame !== undefined) {
|
|
41
|
+
window.cancelAnimationFrame(sectionTrackingFrame);
|
|
42
|
+
sectionTrackingFrame = undefined;
|
|
43
|
+
}
|
|
44
|
+
window.clearTimeout(sectionTrackingResumeTimer);
|
|
45
|
+
window.removeEventListener('scrollend', resumeSectionTracking);
|
|
46
|
+
if ('onscrollend' in document.documentElement) {
|
|
47
|
+
window.addEventListener('scrollend', resumeSectionTracking, { once: true });
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const scrollBehavior = getComputedStyle(document.documentElement).scrollBehavior;
|
|
51
|
+
sectionTrackingResumeTimer = window.setTimeout(
|
|
52
|
+
resumeSectionTracking,
|
|
53
|
+
scrollBehavior === 'smooth' ? 2000 : 120,
|
|
54
|
+
);
|
|
55
|
+
};
|
|
26
56
|
|
|
27
57
|
const setAnchorOffset = () => {
|
|
28
58
|
if (!siteTop) return;
|
|
@@ -73,12 +103,35 @@
|
|
|
73
103
|
|
|
74
104
|
const updateReadingPosition = () => {
|
|
75
105
|
sectionTrackingFrame = undefined;
|
|
76
|
-
|
|
77
|
-
|
|
106
|
+
if (trackedHeadings.length === 0) return;
|
|
107
|
+
|
|
108
|
+
let currentHeadingId: string | undefined = trackedHeadings[0]?.id;
|
|
109
|
+
|
|
110
|
+
if (window.scrollY > 1) {
|
|
111
|
+
const viewportHeight = window.innerHeight;
|
|
112
|
+
const documentHeight = Math.max(
|
|
113
|
+
document.body.scrollHeight,
|
|
114
|
+
document.body.offsetHeight,
|
|
115
|
+
document.documentElement.clientHeight,
|
|
116
|
+
document.documentElement.scrollHeight,
|
|
117
|
+
document.documentElement.offsetHeight,
|
|
118
|
+
);
|
|
119
|
+
const maximumScroll = Math.max(0, documentHeight - viewportHeight);
|
|
120
|
+
const remainingScroll = Math.max(0, maximumScroll - window.scrollY);
|
|
121
|
+
const normalReadingLine = Math.max(
|
|
122
|
+
Math.ceil(siteTop?.getBoundingClientRect().bottom ?? 0) + 8,
|
|
123
|
+
Math.round(viewportHeight / 2),
|
|
124
|
+
);
|
|
125
|
+
const terminalReadingLine = viewportHeight - remainingScroll;
|
|
126
|
+
const readingLine = Math.min(
|
|
127
|
+
viewportHeight,
|
|
128
|
+
Math.max(normalReadingLine, terminalReadingLine),
|
|
129
|
+
);
|
|
78
130
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
131
|
+
for (const heading of trackedHeadings) {
|
|
132
|
+
if (heading.getBoundingClientRect().top > readingLine) break;
|
|
133
|
+
currentHeadingId = heading.id;
|
|
134
|
+
}
|
|
82
135
|
}
|
|
83
136
|
|
|
84
137
|
sectionLinks.forEach((link) => {
|
|
@@ -94,7 +147,7 @@
|
|
|
94
147
|
};
|
|
95
148
|
|
|
96
149
|
const scheduleReadingPositionUpdate = () => {
|
|
97
|
-
if (!sectionTrackingEnabled || sectionTrackingFrame !== undefined) return;
|
|
150
|
+
if (!sectionTrackingEnabled || sectionTrackingPaused || sectionTrackingFrame !== undefined) return;
|
|
98
151
|
sectionTrackingFrame = window.requestAnimationFrame(updateReadingPosition);
|
|
99
152
|
};
|
|
100
153
|
|
|
@@ -154,6 +207,7 @@
|
|
|
154
207
|
const target = getSamePageTarget(link);
|
|
155
208
|
if (!target) return;
|
|
156
209
|
|
|
210
|
+
pauseSectionTrackingForAnchorNavigation();
|
|
157
211
|
if (mobileMenu) mobileMenu.open = false;
|
|
158
212
|
link.closest<HTMLElement>('.site-nav-item')?.setAttribute('data-submenu-dismissed', 'true');
|
|
159
213
|
|
|
@@ -172,9 +226,11 @@
|
|
|
172
226
|
});
|
|
173
227
|
|
|
174
228
|
window.addEventListener('hashchange', () => {
|
|
229
|
+
pauseSectionTrackingForAnchorNavigation();
|
|
175
230
|
updateCurrentSection();
|
|
176
231
|
scheduleReadingPositionUpdate();
|
|
177
232
|
});
|
|
233
|
+
if (window.location.hash) pauseSectionTrackingForAnchorNavigation();
|
|
178
234
|
updateCurrentSection();
|
|
179
235
|
if (sectionTrackingEnabled) {
|
|
180
236
|
window.addEventListener('scroll', scheduleReadingPositionUpdate, { passive: true });
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
+
import { Search } from '@lucide/astro';
|
|
2
3
|
import projectConfig from '../../scripts/lib/project-config.mjs';
|
|
3
4
|
import { withBasePath } from '../lib/basePath';
|
|
4
5
|
import type { SectionNavigation } from '../lib/sectionContent';
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
import type { SiteNode, SitePage } from '../lib/sitePages';
|
|
12
13
|
import DisplaySettings from './DisplaySettings.astro';
|
|
13
14
|
import NavigationPageTree from './NavigationPageTree.astro';
|
|
15
|
+
import NavigationTreeControls from './NavigationTreeControls.astro';
|
|
14
16
|
import SectionNavigationScript from './SectionNavigationScript.astro';
|
|
15
17
|
|
|
16
18
|
type FrameColors = {
|
|
@@ -24,9 +26,11 @@ interface Props {
|
|
|
24
26
|
alt: string;
|
|
25
27
|
height?: string;
|
|
26
28
|
};
|
|
27
|
-
currentPage
|
|
29
|
+
currentPage?: SitePage;
|
|
28
30
|
navigationEntries: SiteNavigationEntry[];
|
|
29
31
|
navigationMode: 'sections' | 'top' | 'tree';
|
|
32
|
+
showTopNavigationSubmenus: boolean;
|
|
33
|
+
hasNavigationRailFrame: boolean;
|
|
30
34
|
frameColors: FrameColors;
|
|
31
35
|
readerPreferences: {
|
|
32
36
|
controls: {
|
|
@@ -40,9 +44,20 @@ interface Props {
|
|
|
40
44
|
focusReading: 'off' | 'on';
|
|
41
45
|
};
|
|
42
46
|
};
|
|
47
|
+
searchPage?: boolean;
|
|
43
48
|
}
|
|
44
49
|
|
|
45
|
-
const {
|
|
50
|
+
const {
|
|
51
|
+
logo,
|
|
52
|
+
currentPage,
|
|
53
|
+
navigationEntries,
|
|
54
|
+
navigationMode,
|
|
55
|
+
showTopNavigationSubmenus,
|
|
56
|
+
hasNavigationRailFrame,
|
|
57
|
+
frameColors,
|
|
58
|
+
readerPreferences,
|
|
59
|
+
searchPage = false,
|
|
60
|
+
} = Astro.props;
|
|
46
61
|
const siteTopBackgroundColor = frameColors.backgroundColor === 'transparent'
|
|
47
62
|
? 'var(--color-page)'
|
|
48
63
|
: frameColors.backgroundColor;
|
|
@@ -56,10 +71,15 @@ const topLevelNavigation = siteNavigationTree;
|
|
|
56
71
|
const showSiteNavigation = topLevelNavigation.some(({ node }) => !node.isHome);
|
|
57
72
|
const homeNode = navigationEntries.find(({ node }) => node.isHome)?.node;
|
|
58
73
|
const homePage = homeNode?.kind === 'page' ? homeNode : currentPage;
|
|
59
|
-
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
74
|
+
if (!homePage) throw new Error('Site navigation requires a homepage.');
|
|
75
|
+
const currentPageNavigation = currentPage
|
|
76
|
+
? navigationEntries.find(({ node }) => node.kind === 'page' && node.pathname === currentPage.pathname)
|
|
77
|
+
: undefined;
|
|
78
|
+
const isCurrentPage = (page: SitePage) => currentPage?.pathname === page.pathname;
|
|
79
|
+
const isCurrentBranch = (node: SiteNode) => Boolean(currentPage && (
|
|
80
|
+
currentPage.pageDirectory === node.pageDirectory
|
|
81
|
+
|| currentPage.pageDirectory.startsWith(`${node.pageDirectory}/pages/`)
|
|
82
|
+
));
|
|
63
83
|
const pageHref = (page: SitePage) => withBasePath(projectConfig.site.basePath, page.pathname);
|
|
64
84
|
const getNavigationPage = (node: SiteNavigationNode) => getFirstPageInNavigationNode(node);
|
|
65
85
|
const hasSectionMenu = (pageSections: SectionNavigation[]) => pageSections.length > 1;
|
|
@@ -83,7 +103,9 @@ const showPageNavigation = Boolean(
|
|
|
83
103
|
const showSinglePageTitleOnly = showSinglePageTopLink && currentPageSectionCount === 0;
|
|
84
104
|
const showMobileNavigation = showSiteNavigation || hasCurrentPageSectionMenu || hasTreePageContents;
|
|
85
105
|
const showReaderControls = Object.values(readerPreferences.controls).some(Boolean);
|
|
86
|
-
const
|
|
106
|
+
const showSearch = projectConfig.search.enabled;
|
|
107
|
+
const searchHref = withBasePath(projectConfig.site.basePath, '/search/');
|
|
108
|
+
const showDesktopNavigationRow = showSiteNavigation || Boolean(logo) || showReaderControls || showSearch;
|
|
87
109
|
const showNavigationRow = showMobileNavigation || showDesktopNavigationRow;
|
|
88
110
|
const needsNavigationScript = hasCurrentPageSectionMenu || hasTreePageContents || navigationMode === 'tree';
|
|
89
111
|
---
|
|
@@ -94,6 +116,7 @@ const needsNavigationScript = hasCurrentPageSectionMenu || hasTreePageContents |
|
|
|
94
116
|
class:list={['site-top', {
|
|
95
117
|
'site-top-has-site-nav': showSiteNavigation,
|
|
96
118
|
'site-top-has-navigation-row': showDesktopNavigationRow,
|
|
119
|
+
'site-top-has-navigation-rail-frame': hasNavigationRailFrame,
|
|
97
120
|
}]}
|
|
98
121
|
data-navigation-mode={navigationMode}
|
|
99
122
|
style={navigationStyle}
|
|
@@ -118,13 +141,13 @@ const needsNavigationScript = hasCurrentPageSectionMenu || hasTreePageContents |
|
|
|
118
141
|
const nodeIsCurrentPage = navigationNode.node.kind === 'page' && isCurrentPage(navigationNode.node);
|
|
119
142
|
return (
|
|
120
143
|
<li class:list={['site-nav-item', {
|
|
121
|
-
'site-nav-item-has-submenu':
|
|
144
|
+
'site-nav-item-has-submenu': showTopNavigationSubmenus && navigationNode.children.length > 0,
|
|
122
145
|
'site-nav-item-current-branch': !nodeIsCurrentPage && isCurrentBranch(navigationNode.node),
|
|
123
146
|
}]}>
|
|
124
147
|
<a href={pageHref(destination)} aria-current={nodeIsCurrentPage ? 'page' : undefined}>
|
|
125
148
|
{navigationNode.node.title}
|
|
126
149
|
</a>
|
|
127
|
-
{
|
|
150
|
+
{showTopNavigationSubmenus && navigationNode.children.length > 0 && (
|
|
128
151
|
<div class="site-nav-submenu">
|
|
129
152
|
<nav aria-label={`${navigationNode.node.title} ${projectConfig.locale.labels.siteNavigation}`}>
|
|
130
153
|
<NavigationPageTree nodes={[navigationNode]} currentPage={currentPage} variant="submenu" />
|
|
@@ -138,6 +161,18 @@ const needsNavigationScript = hasCurrentPageSectionMenu || hasTreePageContents |
|
|
|
138
161
|
</nav>
|
|
139
162
|
)}
|
|
140
163
|
|
|
164
|
+
{showSearch && (
|
|
165
|
+
<a
|
|
166
|
+
class="site-search-link"
|
|
167
|
+
href={searchHref}
|
|
168
|
+
aria-current={searchPage ? 'page' : undefined}
|
|
169
|
+
aria-label={projectConfig.locale.labels.search}
|
|
170
|
+
title={projectConfig.locale.labels.search}
|
|
171
|
+
>
|
|
172
|
+
<Search aria-hidden="true" />
|
|
173
|
+
</a>
|
|
174
|
+
)}
|
|
175
|
+
|
|
141
176
|
{showReaderControls && (
|
|
142
177
|
<DisplaySettings controls={readerPreferences.controls} defaults={readerPreferences.defaults} />
|
|
143
178
|
)}
|
|
@@ -151,6 +186,10 @@ const needsNavigationScript = hasCurrentPageSectionMenu || hasTreePageContents |
|
|
|
151
186
|
{showSiteNavigation && (
|
|
152
187
|
<nav class="mobile-nav-view" aria-label={projectConfig.locale.labels.siteNavigation}>
|
|
153
188
|
<h2 tabindex="-1">{projectConfig.locale.labels.siteNavigation}</h2>
|
|
189
|
+
<NavigationTreeControls
|
|
190
|
+
idPrefix="mobile-tree-navigation"
|
|
191
|
+
nodes={siteNavigationTree}
|
|
192
|
+
/>
|
|
154
193
|
<NavigationPageTree
|
|
155
194
|
nodes={siteNavigationTree}
|
|
156
195
|
currentPage={currentPage}
|
|
@@ -160,7 +199,7 @@ const needsNavigationScript = hasCurrentPageSectionMenu || hasTreePageContents |
|
|
|
160
199
|
</nav>
|
|
161
200
|
)}
|
|
162
201
|
|
|
163
|
-
{navigationMode !== 'tree' && hasCurrentPageSectionMenu && currentPageNavigation && (
|
|
202
|
+
{navigationMode !== 'tree' && hasCurrentPageSectionMenu && currentPageNavigation && currentPage && (
|
|
164
203
|
<nav class="mobile-nav-view" aria-label={projectConfig.locale.labels.pageNavigation}>
|
|
165
204
|
{!showSinglePageTopLink && (
|
|
166
205
|
<h2 tabindex="-1">{projectConfig.locale.labels.pageNavigation}</h2>
|
|
@@ -192,7 +231,7 @@ const needsNavigationScript = hasCurrentPageSectionMenu || hasTreePageContents |
|
|
|
192
231
|
</div>
|
|
193
232
|
)}
|
|
194
233
|
|
|
195
|
-
{showPageNavigation && currentPageNavigation && (
|
|
234
|
+
{showPageNavigation && currentPageNavigation && currentPage && (
|
|
196
235
|
<nav
|
|
197
236
|
class:list={['page-nav', { 'page-nav-title-only': showSinglePageTitleOnly }]}
|
|
198
237
|
aria-label={projectConfig.locale.labels.pageNavigation}
|
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { getCollection, type CollectionEntry } from 'astro:content';
|
|
3
3
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
|
4
|
+
import CodeBlockCopyScript from './CodeBlockCopyScript.astro';
|
|
5
|
+
import EditSourceLink from './EditSourceLink.astro';
|
|
4
6
|
import SiteBreadcrumbs from './SiteBreadcrumbs.astro';
|
|
5
7
|
import SiteNavigation from './SiteNavigation.astro';
|
|
6
8
|
import SiteBanners from './SiteBanners.astro';
|
|
9
|
+
import PageContentsNavigation from './PageContentsNavigation.astro';
|
|
10
|
+
import PageSequenceNavigation from './PageSequenceNavigation.astro';
|
|
7
11
|
import SiteSection from './SiteSection.astro';
|
|
8
12
|
import SiteTreeNavigation from './SiteTreeNavigation.astro';
|
|
9
13
|
import TreeNavigationScript from './TreeNavigationScript.astro';
|
|
10
14
|
import { getSectionsContent, type ResolvedSection } from '../lib/sectionContent';
|
|
11
15
|
import { getPageTheme } from '../lib/pageThemes';
|
|
12
16
|
import { getSiteModel, type SitePage } from '../lib/sitePages';
|
|
13
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
flattenSiteNavigationTree,
|
|
19
|
+
getDirectChildPages,
|
|
20
|
+
getListedSiteNavigationTree,
|
|
21
|
+
getSequentialPageNavigation,
|
|
22
|
+
} from '../lib/siteNavigation';
|
|
14
23
|
import { getToday, isVisibleToday } from '../lib/visibility';
|
|
15
24
|
import { getLogoAsset } from '../lib/sitePublicAssets';
|
|
16
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
resolveNavigationModel,
|
|
27
|
+
resolvePageContentsPlacement,
|
|
28
|
+
} from '../../scripts/lib/navigation-model.mjs';
|
|
17
29
|
import { projectConfig, resolveThemeVisualConfig } from '../../scripts/lib/project-config.mjs';
|
|
30
|
+
import { getEditSourceUrl } from '../../scripts/lib/edit-source-link.mjs';
|
|
18
31
|
import { getTextWidthCssValue, resolveFrameColors, resolvePagePresentation } from '../../scripts/lib/presentation.mjs';
|
|
19
32
|
import { mergePageThemeConfig } from '../../scripts/lib/theme-presets.mjs';
|
|
20
33
|
|
|
@@ -53,21 +66,8 @@ const logo = logoAsset
|
|
|
53
66
|
height: logoSettings?.height,
|
|
54
67
|
}
|
|
55
68
|
: undefined;
|
|
56
|
-
const siteHtml = entry.rendered?.html ?? '';
|
|
57
|
-
const resolvedSections = await getSectionsContent(siteHtml, currentPage);
|
|
58
69
|
const today = getToday();
|
|
59
70
|
const siteBanners = (sitewideData.banners ?? []).filter((banner) => isVisibleToday(banner.visible, today));
|
|
60
|
-
const hasPrioritizableImage = (section: ResolvedSection) => section.contentBlocks.some((block) => (
|
|
61
|
-
block.type === 'image-stack'
|
|
62
|
-
|| block.type === 'image-carousel'
|
|
63
|
-
));
|
|
64
|
-
const firstImageSectionIndex = resolvedSections.findIndex(hasPrioritizableImage);
|
|
65
|
-
let contentSectionIndex = 0;
|
|
66
|
-
const renderedSections = resolvedSections.map((section, index) => ({
|
|
67
|
-
section,
|
|
68
|
-
sectionIndex: section.headingLevel === 1 ? 0 : contentSectionIndex++,
|
|
69
|
-
prioritizeFirstImage: index === firstImageSectionIndex,
|
|
70
|
-
}));
|
|
71
71
|
|
|
72
72
|
const getPageContent = (page: SitePage) =>
|
|
73
73
|
page.markdownDocument.navigationHeadings.map(({ depth, id, parentId, title }) => ({
|
|
@@ -87,7 +87,33 @@ const siteNavigation = siteNodes.map((node) => {
|
|
|
87
87
|
.map(({ id, title }) => ({ id, title })),
|
|
88
88
|
};
|
|
89
89
|
});
|
|
90
|
-
const
|
|
90
|
+
const listedNavigationTree = getListedSiteNavigationTree(siteNavigation);
|
|
91
|
+
const renderedNavigationEntries = flattenSiteNavigationTree(listedNavigationTree);
|
|
92
|
+
const pageSequence = getSequentialPageNavigation(listedNavigationTree, currentPage.pagePath);
|
|
93
|
+
const childPages = getDirectChildPages(listedNavigationTree, currentPage.pagePath);
|
|
94
|
+
const siteHtml = entry.rendered?.html ?? '';
|
|
95
|
+
const resolvedSections = await getSectionsContent(siteHtml, currentPage, childPages);
|
|
96
|
+
const hasCodeBlocks = resolvedSections.some((section) => section.contentBlocks.some((block) => (
|
|
97
|
+
block.type === 'html' && /<pre\b[^>]*>[\s\S]*?<code\b/i.test(block.html)
|
|
98
|
+
)));
|
|
99
|
+
const hasPrioritizableImage = (section: ResolvedSection) => section.contentBlocks.some((block) => (
|
|
100
|
+
block.type === 'image-stack'
|
|
101
|
+
|| block.type === 'image-carousel'
|
|
102
|
+
));
|
|
103
|
+
const firstImageSectionIndex = resolvedSections.findIndex(hasPrioritizableImage);
|
|
104
|
+
let contentSectionIndex = 0;
|
|
105
|
+
const renderedSections = resolvedSections.map((section, index) => ({
|
|
106
|
+
section,
|
|
107
|
+
sectionIndex: section.headingLevel === 1 ? 0 : contentSectionIndex++,
|
|
108
|
+
prioritizeFirstImage: index === firstImageSectionIndex,
|
|
109
|
+
}));
|
|
110
|
+
const hasPageSequence = Boolean(pageSequence.previous || pageSequence.next);
|
|
111
|
+
const editSourceUrl = projectConfig.editLink
|
|
112
|
+
? getEditSourceUrl({
|
|
113
|
+
baseUrl: projectConfig.editLink.baseUrl,
|
|
114
|
+
sourcePath: currentPage.contentLabel,
|
|
115
|
+
})
|
|
116
|
+
: null;
|
|
91
117
|
const navigationModel = resolveNavigationModel({
|
|
92
118
|
mode: projectConfig.navigation.mode,
|
|
93
119
|
nodes: renderedNavigationEntries.map(({ node, headings }) => ({
|
|
@@ -96,7 +122,12 @@ const navigationModel = resolveNavigationModel({
|
|
|
96
122
|
isHome: node.isHome,
|
|
97
123
|
kind: node.kind,
|
|
98
124
|
listed: node.navigation.listed,
|
|
125
|
+
pagePath: node.pagePath,
|
|
99
126
|
})),
|
|
127
|
+
currentPage: {
|
|
128
|
+
isHome: currentPage.isHome,
|
|
129
|
+
pagePath: currentPage.pagePath,
|
|
130
|
+
},
|
|
100
131
|
});
|
|
101
132
|
const pagePresentation = resolvePagePresentation(themeData, themeLabel, {
|
|
102
133
|
navigationMode: navigationModel.mode,
|
|
@@ -104,6 +135,32 @@ const pagePresentation = resolvePagePresentation(themeData, themeLabel, {
|
|
|
104
135
|
const pageTextWidth = getTextWidthCssValue(pagePresentation.typography.values.body.width);
|
|
105
136
|
const frameColors = resolveFrameColors({ theme: siteThemeData, sourceLabel: `${siteDirectory || 'site'}/${siteTheme.id}` });
|
|
106
137
|
const hasTreeNavigation = navigationModel.mode === 'tree';
|
|
138
|
+
const currentPageNavigation = siteNavigation.find(({ node }) => (
|
|
139
|
+
node.kind === 'page' && node.pathname === currentPage.pathname
|
|
140
|
+
));
|
|
141
|
+
const pageContentsHeadings = currentPageNavigation?.headings.filter(({ depth }) => depth === 2 || depth === 3) ?? [];
|
|
142
|
+
const pageContentsPlacement = resolvePageContentsPlacement({
|
|
143
|
+
navigationMode: navigationModel.mode,
|
|
144
|
+
nodes: renderedNavigationEntries.map(({ node }) => ({
|
|
145
|
+
depth: Math.max(1, node.depth),
|
|
146
|
+
isHome: node.isHome,
|
|
147
|
+
listed: node.navigation.listed,
|
|
148
|
+
pagePath: node.pagePath,
|
|
149
|
+
})),
|
|
150
|
+
currentPage: {
|
|
151
|
+
isHome: currentPage.isHome,
|
|
152
|
+
pagePath: currentPage.pagePath,
|
|
153
|
+
},
|
|
154
|
+
headingCount: pageContentsHeadings.length,
|
|
155
|
+
});
|
|
156
|
+
const hasContentsNavigation = pageContentsPlacement.placement !== 'none';
|
|
157
|
+
const integratePageContentsInTree = pageContentsPlacement.placement === 'page-tree';
|
|
158
|
+
const showContentsRail = pageContentsPlacement.placement === 'contents-rail';
|
|
159
|
+
const hasNavigationRailFrame = navigationModel.requestedMode === 'tree'
|
|
160
|
+
|| (
|
|
161
|
+
navigationModel.requestedMode === 'automatic'
|
|
162
|
+
&& (navigationModel.hasNestedPages || navigationModel.hasCategories)
|
|
163
|
+
);
|
|
107
164
|
const showBreadcrumbs = !currentPage.isHome && currentPage.parentPagePath !== null;
|
|
108
165
|
const reserveBreadcrumbSpace = hasTreeNavigation || showBreadcrumbs;
|
|
109
166
|
|
|
@@ -117,30 +174,54 @@ const reserveBreadcrumbSpace = hasTreeNavigation || showBreadcrumbs;
|
|
|
117
174
|
frameColors={frameColors}
|
|
118
175
|
pathname={currentPage.pathname}
|
|
119
176
|
footer={sitewideData.footer}
|
|
120
|
-
|
|
177
|
+
contentsNavigation={hasContentsNavigation}
|
|
178
|
+
searchable
|
|
121
179
|
>
|
|
122
180
|
<SiteNavigation
|
|
123
181
|
logo={logo}
|
|
124
182
|
currentPage={currentPage}
|
|
125
183
|
navigationEntries={siteNavigation}
|
|
126
184
|
navigationMode={navigationModel.mode}
|
|
185
|
+
showTopNavigationSubmenus={navigationModel.requestedMode !== 'automatic' && navigationModel.mode !== 'tree'}
|
|
186
|
+
hasNavigationRailFrame={hasNavigationRailFrame}
|
|
127
187
|
frameColors={frameColors}
|
|
128
188
|
readerPreferences={pagePresentation.readerPreferences}
|
|
129
189
|
/>
|
|
130
190
|
{siteBanners.length > 0 && <SiteBanners banners={siteBanners} />}
|
|
131
191
|
|
|
132
|
-
<div class:list={['site-page-layout', {
|
|
192
|
+
<div class:list={['site-page-layout', {
|
|
193
|
+
'site-page-layout-tree': hasTreeNavigation,
|
|
194
|
+
'site-page-layout-contents': showContentsRail,
|
|
195
|
+
}]}
|
|
196
|
+
data-page-contents-placement={hasContentsNavigation ? pageContentsPlacement.placement : undefined}
|
|
197
|
+
>
|
|
133
198
|
{hasTreeNavigation && (
|
|
134
|
-
<SiteTreeNavigation
|
|
199
|
+
<SiteTreeNavigation
|
|
200
|
+
currentPage={currentPage}
|
|
201
|
+
navigationEntries={siteNavigation}
|
|
202
|
+
integratePageSections={integratePageContentsInTree}
|
|
203
|
+
/>
|
|
135
204
|
)}
|
|
136
205
|
<main
|
|
137
|
-
class:list={['site-content', {
|
|
206
|
+
class:list={['site-content', {
|
|
207
|
+
'site-content-has-breadcrumbs': reserveBreadcrumbSpace,
|
|
208
|
+
'site-content-has-edit-source': Boolean(editSourceUrl),
|
|
209
|
+
'site-content-has-page-sequence': hasPageSequence,
|
|
210
|
+
}]}
|
|
138
211
|
id="main-content"
|
|
212
|
+
data-pagefind-body
|
|
139
213
|
style={`--page-text-width: ${pageTextWidth}`}
|
|
140
214
|
>
|
|
141
215
|
{showBreadcrumbs
|
|
142
216
|
? <SiteBreadcrumbs currentPage={currentPage} nodes={siteNodes} />
|
|
143
217
|
: reserveBreadcrumbSpace && <div class="site-breadcrumbs" aria-hidden="true"></div>}
|
|
218
|
+
{showContentsRail && (
|
|
219
|
+
<PageContentsNavigation
|
|
220
|
+
headings={pageContentsHeadings}
|
|
221
|
+
pageTitle={currentPage.title}
|
|
222
|
+
variant="inline"
|
|
223
|
+
/>
|
|
224
|
+
)}
|
|
144
225
|
{renderedSections.map(({ section, sectionIndex, prioritizeFirstImage }) => (
|
|
145
226
|
<SiteSection
|
|
146
227
|
section={section}
|
|
@@ -150,7 +231,19 @@ const reserveBreadcrumbSpace = hasTreeNavigation || showBreadcrumbs;
|
|
|
150
231
|
prioritizeFirstImage={prioritizeFirstImage}
|
|
151
232
|
/>
|
|
152
233
|
))}
|
|
234
|
+
{editSourceUrl && (
|
|
235
|
+
<EditSourceLink href={editSourceUrl} label={projectConfig.locale.labels.editSource} />
|
|
236
|
+
)}
|
|
237
|
+
<PageSequenceNavigation previous={pageSequence.previous} next={pageSequence.next} />
|
|
153
238
|
</main>
|
|
239
|
+
{showContentsRail && (
|
|
240
|
+
<PageContentsNavigation
|
|
241
|
+
headings={pageContentsHeadings}
|
|
242
|
+
pageTitle={currentPage.title}
|
|
243
|
+
variant="rail"
|
|
244
|
+
/>
|
|
245
|
+
)}
|
|
154
246
|
</div>
|
|
155
247
|
{hasTreeNavigation && <TreeNavigationScript />}
|
|
248
|
+
{hasCodeBlocks && <CodeBlockCopyScript />}
|
|
156
249
|
</BaseLayout>
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
import CardList from './CardList.astro';
|
|
10
10
|
import ImageCarousel from './ImageCarousel.astro';
|
|
11
11
|
import ImageStack from './ImageStack.astro';
|
|
12
|
+
import PageList from './PageList.astro';
|
|
12
13
|
import type { ResolvedSection } from '../lib/sectionContent';
|
|
13
14
|
|
|
14
15
|
type ResponsiveValue<T> = { desktop: T; mobile: T };
|
|
@@ -33,10 +34,15 @@ type ResolvedPagePresentation = {
|
|
|
33
34
|
};
|
|
34
35
|
type VisualTheme = {
|
|
35
36
|
images: {
|
|
37
|
+
presentation: 'prose-aligned' | 'centered-fit';
|
|
36
38
|
maxAvailableWidthPercent: {
|
|
37
39
|
desktop: number;
|
|
38
40
|
mobile: number;
|
|
39
41
|
};
|
|
42
|
+
maxAvailableHeightPercent?: {
|
|
43
|
+
desktop: number;
|
|
44
|
+
mobile: number;
|
|
45
|
+
};
|
|
40
46
|
};
|
|
41
47
|
};
|
|
42
48
|
|
|
@@ -108,6 +114,7 @@ const isStructuredBlock = (block: ResolvedSection['contentBlocks'][number]) => (
|
|
|
108
114
|
block.type === 'image-stack'
|
|
109
115
|
|| block.type === 'image-carousel'
|
|
110
116
|
|| block.type === 'card-list'
|
|
117
|
+
|| block.type === 'page-list'
|
|
111
118
|
);
|
|
112
119
|
const hasMedia = section.contentBlocks.some((block) => (
|
|
113
120
|
block.type === 'image-stack'
|
|
@@ -116,7 +123,7 @@ const hasMedia = section.contentBlocks.some((block) => (
|
|
|
116
123
|
));
|
|
117
124
|
const contentGroups: Array<
|
|
118
125
|
| { type: 'reading'; blocks: ResolvedSection['contentBlocks'] }
|
|
119
|
-
| { type: '
|
|
126
|
+
| { type: 'structured'; block: Extract<ResolvedSection['contentBlocks'][number], { type: 'image-stack' | 'image-carousel' | 'card-list' | 'page-list' }> }
|
|
120
127
|
> = [];
|
|
121
128
|
let readingBlocks: ResolvedSection['contentBlocks'] = [];
|
|
122
129
|
const flushReadingBlocks = () => {
|
|
@@ -128,14 +135,17 @@ const flushReadingBlocks = () => {
|
|
|
128
135
|
for (const block of section.contentBlocks) {
|
|
129
136
|
if (isStructuredBlock(block)) {
|
|
130
137
|
flushReadingBlocks();
|
|
131
|
-
contentGroups.push({ type: '
|
|
138
|
+
contentGroups.push({ type: 'structured', block });
|
|
132
139
|
} else {
|
|
133
140
|
readingBlocks = [...readingBlocks, block];
|
|
134
141
|
}
|
|
135
142
|
}
|
|
136
143
|
|
|
137
144
|
flushReadingBlocks();
|
|
138
|
-
const firstMediaGroupIndex = contentGroups.findIndex((group) =>
|
|
145
|
+
const firstMediaGroupIndex = contentGroups.findIndex((group) => (
|
|
146
|
+
group.type === 'structured'
|
|
147
|
+
&& (group.block.type === 'image-stack' || group.block.type === 'image-carousel')
|
|
148
|
+
));
|
|
139
149
|
const markdownHeadingStyle = (level: 'h3' | 'h4') => {
|
|
140
150
|
const headingConfig = markdownHeadings[level];
|
|
141
151
|
const fontSize = getHeadingTypographySizeValue(level, headingConfig.size);
|
|
@@ -235,6 +245,9 @@ const sectionStyle = [
|
|
|
235
245
|
/>
|
|
236
246
|
);
|
|
237
247
|
}
|
|
248
|
+
if (block.type === 'page-list') {
|
|
249
|
+
return <PageList pages={block.pages} />;
|
|
250
|
+
}
|
|
238
251
|
|
|
239
252
|
return (
|
|
240
253
|
<ImageStack
|
|
@@ -3,13 +3,15 @@ import projectConfig from '../../scripts/lib/project-config.mjs';
|
|
|
3
3
|
import { getListedSiteNavigationTree, type SiteNavigationEntry } from '../lib/siteNavigation';
|
|
4
4
|
import type { SitePage } from '../lib/sitePages';
|
|
5
5
|
import NavigationPageTree from './NavigationPageTree.astro';
|
|
6
|
+
import NavigationTreeControls from './NavigationTreeControls.astro';
|
|
6
7
|
|
|
7
8
|
interface Props {
|
|
8
9
|
currentPage: SitePage;
|
|
9
10
|
navigationEntries: SiteNavigationEntry[];
|
|
11
|
+
integratePageSections?: boolean;
|
|
10
12
|
}
|
|
11
13
|
|
|
12
|
-
const { currentPage, navigationEntries } = Astro.props;
|
|
14
|
+
const { currentPage, navigationEntries, integratePageSections = false } = Astro.props;
|
|
13
15
|
const roots = getListedSiteNavigationTree(navigationEntries);
|
|
14
16
|
const activeRoot = roots.find((node) => (
|
|
15
17
|
currentPage.pageDirectory === node.node.pageDirectory
|
|
@@ -24,12 +26,16 @@ const navigationRoot = activeRoot?.node.pagePath ? `/${activeRoot.node.pagePath}
|
|
|
24
26
|
class="tree-local-navigation"
|
|
25
27
|
data-navigation-root={navigationRoot}
|
|
26
28
|
>
|
|
29
|
+
<NavigationTreeControls
|
|
30
|
+
idPrefix="desktop-tree-navigation"
|
|
31
|
+
nodes={[activeRoot]}
|
|
32
|
+
/>
|
|
27
33
|
<nav aria-label={`${activeRoot.node.title}: ${projectConfig.locale.labels.siteNavigation}`}>
|
|
28
34
|
<NavigationPageTree
|
|
29
35
|
nodes={[activeRoot]}
|
|
30
36
|
currentPage={currentPage}
|
|
31
37
|
variant="sidebar"
|
|
32
|
-
integratePageSections
|
|
38
|
+
integratePageSections={integratePageSections}
|
|
33
39
|
/>
|
|
34
40
|
</nav>
|
|
35
41
|
</aside>
|