@janga/norna 0.7.24 → 0.7.26
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 +5 -4
- package/astro.config.mjs +27 -11
- package/bin/norna-cli.mjs +12 -0
- package/package.json +26 -5
- package/schemas/card-list.schema.json +121 -0
- package/schemas/category.schema.json +2 -2
- package/schemas/config.schema.json +342 -33
- package/schemas/content-frontmatter.schema.json +7 -7
- package/schemas/image-carousel.schema.json +43 -0
- package/schemas/image-stack.schema.json +43 -0
- package/schemas/manifest.json +6 -1
- package/schemas/page-theme.schema.json +26 -26
- package/schemas/sitewide-content.schema.json +18 -18
- package/schemas/theme.schema.json +237 -262
- package/scripts/check-config.mjs +5 -1
- package/scripts/dev-local.mjs +130 -13
- package/scripts/generate-schemas.mjs +15 -3
- package/scripts/init-site.mjs +2 -0
- package/scripts/lib/category-destinations.mjs +48 -0
- package/scripts/lib/code-fence-metadata.mjs +25 -43
- package/scripts/lib/content-tabs.mjs +155 -0
- package/scripts/lib/details-headings.mjs +58 -0
- package/scripts/lib/docusaurus-migration-audit.mjs +603 -0
- package/scripts/lib/edit-source-link.mjs +53 -0
- package/scripts/lib/editor-language-service.mjs +238 -116
- package/scripts/lib/locale-registry.mjs +597 -0
- package/scripts/lib/locales/az-Latn.mjs +78 -0
- package/scripts/lib/locales/be.mjs +78 -0
- package/scripts/lib/locales/bg.mjs +78 -0
- package/scripts/lib/locales/bs.mjs +78 -0
- package/scripts/lib/locales/ca.mjs +78 -0
- package/scripts/lib/locales/cnr.mjs +78 -0
- package/scripts/lib/locales/cs.mjs +78 -0
- package/scripts/lib/locales/da.mjs +78 -0
- package/scripts/lib/locales/de.mjs +78 -0
- package/scripts/lib/locales/el.mjs +78 -0
- package/scripts/lib/locales/en.mjs +78 -0
- package/scripts/lib/locales/es.mjs +78 -0
- package/scripts/lib/locales/et.mjs +78 -0
- package/scripts/lib/locales/fi.mjs +78 -0
- package/scripts/lib/locales/fil.mjs +78 -0
- package/scripts/lib/locales/fr.mjs +78 -0
- package/scripts/lib/locales/ga.mjs +78 -0
- package/scripts/lib/locales/ha.mjs +78 -0
- package/scripts/lib/locales/hr.mjs +78 -0
- package/scripts/lib/locales/hu.mjs +78 -0
- package/scripts/lib/locales/id.mjs +78 -0
- package/scripts/lib/locales/is.mjs +78 -0
- package/scripts/lib/locales/it.mjs +78 -0
- package/scripts/lib/locales/jv.mjs +78 -0
- package/scripts/lib/locales/lb.mjs +78 -0
- package/scripts/lib/locales/lt.mjs +78 -0
- package/scripts/lib/locales/lv.mjs +78 -0
- package/scripts/lib/locales/mk.mjs +78 -0
- package/scripts/lib/locales/mt.mjs +78 -0
- package/scripts/lib/locales/nb.mjs +78 -0
- package/scripts/lib/locales/nl.mjs +78 -0
- package/scripts/lib/locales/nn.mjs +78 -0
- package/scripts/lib/locales/pcm.mjs +78 -0
- package/scripts/lib/locales/pl.mjs +78 -0
- package/scripts/lib/locales/pt.mjs +78 -0
- package/scripts/lib/locales/ro.mjs +78 -0
- package/scripts/lib/locales/ru.mjs +78 -0
- package/scripts/lib/locales/sk.mjs +78 -0
- package/scripts/lib/locales/sl.mjs +78 -0
- package/scripts/lib/locales/sq.mjs +78 -0
- package/scripts/lib/locales/sr-Cyrl.mjs +78 -0
- package/scripts/lib/locales/sr-Latn.mjs +78 -0
- package/scripts/lib/locales/sv.mjs +78 -0
- package/scripts/lib/locales/sw.mjs +78 -0
- package/scripts/lib/locales/tr.mjs +78 -0
- package/scripts/lib/locales/uk.mjs +78 -0
- package/scripts/lib/locales/vi.mjs +78 -0
- package/scripts/lib/markdown-links.mjs +4 -25
- package/scripts/lib/markdown-notes-render-plugin.mjs +75 -0
- package/scripts/lib/markdown-notes.mjs +165 -0
- package/scripts/lib/navigation-model.mjs +1 -1
- package/scripts/lib/norna-markdown-blocks.mjs +123 -447
- package/scripts/lib/norna-markdown-render-plugin.mjs +4 -0
- package/scripts/lib/page-aliases.mjs +12 -1
- package/scripts/lib/page-markdown.mjs +63 -16
- package/scripts/lib/page-move-plan.mjs +22 -5
- package/scripts/lib/pagefind-translations.mjs +620 -0
- package/scripts/lib/presentation-contract.mjs +6 -1
- package/scripts/lib/presentation.mjs +29 -30
- package/scripts/lib/project-config.mjs +22 -163
- package/scripts/lib/schema-definitions.mjs +13 -12
- package/scripts/lib/schema-editor-metadata.mjs +42 -46
- package/scripts/lib/schema-value-definitions.mjs +7 -8
- package/scripts/lib/semantic-callouts.mjs +4 -2
- package/scripts/lib/site-content.mjs +4 -2
- package/scripts/lib/site-link-graph.mjs +16 -6
- package/scripts/lib/site-navigation-tree.mjs +0 -11
- package/scripts/lib/site-paths.mjs +14 -2
- package/scripts/lib/sitemap.mjs +5 -3
- package/scripts/lib/structured-blocks.mjs +108 -0
- package/scripts/lib/table-render-plugin.mjs +76 -0
- package/scripts/lib/table-row-headers.mjs +218 -0
- package/scripts/lib/theme-presets.mjs +3 -10
- package/scripts/lib/yaml-config.mjs +2 -6
- package/scripts/migrate-check.mjs +19 -0
- package/scripts/sync-content-sections.mjs +39 -10
- package/scripts/sync-site-public.mjs +7 -1
- package/src/components/CardList.astro +1 -0
- package/src/components/CodeBlockCopyScript.astro +49 -6
- package/src/components/ContentBlocks.astro +39 -0
- package/src/components/ContentTabsScript.astro +60 -0
- package/src/components/DisplaySettings.astro +17 -19
- package/src/components/ImageCarousel.astro +4 -1
- package/src/components/ImageStack.astro +34 -9
- package/src/components/ImageStackEnhancement.astro +331 -0
- package/src/components/NavigationPageTree.astro +1 -1
- package/src/components/NoteNavigationScript.astro +4 -0
- package/src/components/PageAliasRedirect.astro +5 -4
- package/src/components/PageContentsNavigation.astro +2 -3
- package/src/components/SearchNavigationScript.astro +4 -0
- package/src/components/SearchPage.astro +43 -2
- package/src/components/SectionNavigationScript.astro +86 -16
- package/src/components/SiteBreadcrumbs.astro +1 -1
- package/src/components/SiteNavigation.astro +98 -65
- package/src/components/SitePage.astro +73 -33
- package/src/components/SiteSection.astro +5 -81
- package/src/components/SiteTreeNavigation.astro +5 -2
- package/src/components/TableOverflowScript.astro +579 -0
- package/src/components/TopPageMenu.astro +47 -0
- package/src/components/TreeNavigationScript.astro +86 -23
- package/src/layouts/BaseLayout.astro +34 -12
- package/src/lib/generatedImages.ts +18 -0
- package/src/lib/navigationFollowing.ts +142 -0
- package/src/lib/noteNavigation.ts +50 -0
- package/src/lib/searchNavigation.ts +107 -0
- package/src/lib/sectionContent.ts +23 -116
- package/src/lib/siteNavigation.ts +0 -5
- package/src/lib/sitePages.ts +18 -4
- package/src/lib/table-sort-tooltip.ts +109 -0
- package/src/pages/[...slug].astro +16 -6
- package/src/styles/content.css +563 -26
- package/src/styles/foundations.css +9 -0
- package/src/styles/media.css +192 -3
- package/src/styles/navigation.css +214 -60
- package/src/styles/page-layout.css +55 -16
- package/src/styles/responsive.css +62 -20
|
@@ -1,19 +1,20 @@
|
|
|
1
1
|
---
|
|
2
|
-
import { Search } from '@lucide/astro';
|
|
2
|
+
import { Menu, Search, X } from '@lucide/astro';
|
|
3
3
|
import projectConfig from '../../scripts/lib/project-config.mjs';
|
|
4
4
|
import { withBasePath } from '../lib/basePath';
|
|
5
5
|
import type { SectionNavigation } from '../lib/sectionContent';
|
|
6
6
|
import {
|
|
7
|
-
getFirstPageInNavigationNode,
|
|
8
7
|
getListedSiteNavigationTree,
|
|
9
8
|
type SiteNavigationEntry,
|
|
10
9
|
type SiteNavigationNode,
|
|
11
10
|
} from '../lib/siteNavigation';
|
|
12
|
-
import type { SiteNode
|
|
11
|
+
import type { SiteNode } from '../lib/sitePages';
|
|
13
12
|
import DisplaySettings from './DisplaySettings.astro';
|
|
14
13
|
import NavigationPageTree from './NavigationPageTree.astro';
|
|
15
14
|
import NavigationTreeControls from './NavigationTreeControls.astro';
|
|
15
|
+
import TopPageMenu from './TopPageMenu.astro';
|
|
16
16
|
import SectionNavigationScript from './SectionNavigationScript.astro';
|
|
17
|
+
import SearchNavigationScript from './SearchNavigationScript.astro';
|
|
17
18
|
|
|
18
19
|
type FrameColors = {
|
|
19
20
|
backgroundColor: string;
|
|
@@ -26,7 +27,7 @@ interface Props {
|
|
|
26
27
|
alt: string;
|
|
27
28
|
height?: string;
|
|
28
29
|
};
|
|
29
|
-
currentPage?:
|
|
30
|
+
currentPage?: SiteNode;
|
|
30
31
|
navigationEntries: SiteNavigationEntry[];
|
|
31
32
|
navigationMode: 'sections' | 'top' | 'tree';
|
|
32
33
|
showTopNavigationSubmenus: boolean;
|
|
@@ -67,6 +68,12 @@ const navigationStyle = [
|
|
|
67
68
|
...(logo?.height ? [`--site-brand-logo-height: ${logo.height}`] : []),
|
|
68
69
|
].join('; ');
|
|
69
70
|
const siteNavigationTree = getListedSiteNavigationTree(navigationEntries);
|
|
71
|
+
const mobileNavigationTree = navigationMode === 'top'
|
|
72
|
+
? getListedSiteNavigationTree(navigationEntries.map((entry) => ({
|
|
73
|
+
...entry,
|
|
74
|
+
headings: entry.headings.filter((heading) => heading.depth === 2),
|
|
75
|
+
})))
|
|
76
|
+
: siteNavigationTree;
|
|
70
77
|
const topLevelNavigation = siteNavigationTree;
|
|
71
78
|
const showSiteNavigation = topLevelNavigation.some(({ node }) => !node.isHome);
|
|
72
79
|
const homeNode = navigationEntries.find(({ node }) => node.isHome)?.node;
|
|
@@ -75,13 +82,13 @@ if (!homePage) throw new Error('Site navigation requires a homepage.');
|
|
|
75
82
|
const currentPageNavigation = currentPage
|
|
76
83
|
? navigationEntries.find(({ node }) => node.kind === 'page' && node.pathname === currentPage.pathname)
|
|
77
84
|
: undefined;
|
|
78
|
-
const isCurrentPage = (page:
|
|
85
|
+
const isCurrentPage = (page: SiteNode) => currentPage?.pathname === page.pathname;
|
|
79
86
|
const isCurrentBranch = (node: SiteNode) => Boolean(currentPage && (
|
|
80
87
|
currentPage.pageDirectory === node.pageDirectory
|
|
81
88
|
|| currentPage.pageDirectory.startsWith(`${node.pageDirectory}/pages/`)
|
|
82
89
|
));
|
|
83
|
-
const pageHref = (page:
|
|
84
|
-
const getNavigationPage = (node: SiteNavigationNode) =>
|
|
90
|
+
const pageHref = (page: SiteNode) => withBasePath(projectConfig.site.basePath, page.pathname);
|
|
91
|
+
const getNavigationPage = (node: SiteNavigationNode) => node.node;
|
|
85
92
|
const hasSectionMenu = (pageSections: SectionNavigation[]) => pageSections.length > 1;
|
|
86
93
|
const showSinglePageTopLink = !showSiteNavigation;
|
|
87
94
|
const currentPageSectionCount = currentPageNavigation?.sections.length ?? 0;
|
|
@@ -97,6 +104,7 @@ const hasTreePageContents = Boolean(
|
|
|
97
104
|
);
|
|
98
105
|
const showPageNavigation = Boolean(
|
|
99
106
|
navigationMode !== 'tree'
|
|
107
|
+
&& (navigationMode !== 'top' || showSinglePageTopLink)
|
|
100
108
|
&& currentPageNavigation
|
|
101
109
|
&& (showSinglePageTopLink || hasCurrentPageSectionMenu),
|
|
102
110
|
);
|
|
@@ -107,7 +115,7 @@ const showSearch = projectConfig.search.enabled;
|
|
|
107
115
|
const searchHref = withBasePath(projectConfig.site.basePath, '/search/');
|
|
108
116
|
const showDesktopNavigationRow = showSiteNavigation || Boolean(logo) || showReaderControls || showSearch;
|
|
109
117
|
const showNavigationRow = showMobileNavigation || showDesktopNavigationRow;
|
|
110
|
-
const needsNavigationScript =
|
|
118
|
+
const needsNavigationScript = showMobileNavigation || hasCurrentPageSectionMenu || hasTreePageContents;
|
|
111
119
|
---
|
|
112
120
|
|
|
113
121
|
<a class="skip-link" href="#main-content">{projectConfig.locale.labels.skipToContent}</a>
|
|
@@ -138,22 +146,20 @@ const needsNavigationScript = hasCurrentPageSectionMenu || hasTreePageContents |
|
|
|
138
146
|
{topLevelNavigation.map((navigationNode) => {
|
|
139
147
|
const destination = getNavigationPage(navigationNode);
|
|
140
148
|
if (!destination) return null;
|
|
141
|
-
const nodeIsCurrentPage =
|
|
149
|
+
const nodeIsCurrentPage = isCurrentPage(navigationNode.node);
|
|
142
150
|
return (
|
|
143
151
|
<li class:list={['site-nav-item', {
|
|
144
|
-
'site-nav-item-has-submenu': showTopNavigationSubmenus && navigationNode.children.length > 0,
|
|
145
152
|
'site-nav-item-current-branch': !nodeIsCurrentPage && isCurrentBranch(navigationNode.node),
|
|
146
153
|
}]}>
|
|
147
154
|
<a href={pageHref(destination)} aria-current={nodeIsCurrentPage ? 'page' : undefined}>
|
|
148
155
|
{navigationNode.node.title}
|
|
149
156
|
</a>
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
)}
|
|
157
|
+
<TopPageMenu
|
|
158
|
+
node={navigationNode}
|
|
159
|
+
currentPage={currentPage}
|
|
160
|
+
showSections={navigationMode === 'top'}
|
|
161
|
+
showChildren={showTopNavigationSubmenus}
|
|
162
|
+
/>
|
|
157
163
|
</li>
|
|
158
164
|
);
|
|
159
165
|
})}
|
|
@@ -165,6 +171,8 @@ const needsNavigationScript = hasCurrentPageSectionMenu || hasTreePageContents |
|
|
|
165
171
|
<a
|
|
166
172
|
class="site-search-link"
|
|
167
173
|
href={searchHref}
|
|
174
|
+
data-search-base={projectConfig.site.basePath}
|
|
175
|
+
data-search-source-title={currentPage?.title}
|
|
168
176
|
aria-current={searchPage ? 'page' : undefined}
|
|
169
177
|
aria-label={projectConfig.locale.labels.search}
|
|
170
178
|
title={projectConfig.locale.labels.search}
|
|
@@ -177,56 +185,80 @@ const needsNavigationScript = hasCurrentPageSectionMenu || hasTreePageContents |
|
|
|
177
185
|
<DisplaySettings controls={readerPreferences.controls} defaults={readerPreferences.defaults} />
|
|
178
186
|
)}
|
|
179
187
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
188
|
+
{showMobileNavigation && (
|
|
189
|
+
<details class="mobile-nav-menu" data-compact-navigation>
|
|
190
|
+
<summary>
|
|
191
|
+
<span>{projectConfig.locale.labels.navigationMenu}</span>
|
|
192
|
+
<Menu class="mobile-nav-trigger-icon mobile-nav-trigger-open" aria-hidden="true" />
|
|
193
|
+
<X class="mobile-nav-trigger-icon mobile-nav-trigger-close" aria-hidden="true" />
|
|
194
|
+
</summary>
|
|
195
|
+
<div
|
|
196
|
+
class="mobile-nav-panel"
|
|
197
|
+
data-compact-navigation-panel
|
|
198
|
+
role="dialog"
|
|
199
|
+
aria-modal="true"
|
|
200
|
+
aria-label={projectConfig.locale.labels.navigationMenu}
|
|
201
|
+
>
|
|
202
|
+
<div class="mobile-nav-panel-actions">
|
|
203
|
+
<button
|
|
204
|
+
type="button"
|
|
205
|
+
class="mobile-nav-close"
|
|
206
|
+
data-compact-navigation-close
|
|
207
|
+
aria-label={projectConfig.locale.labels.closeNavigation}
|
|
208
|
+
>
|
|
209
|
+
<X aria-hidden="true" />
|
|
210
|
+
</button>
|
|
211
|
+
</div>
|
|
212
|
+
<div
|
|
213
|
+
class="mobile-site-nav"
|
|
214
|
+
data-navigation-root={pageHref(homePage)}
|
|
215
|
+
data-navigation-state-root={pageHref(homePage)}
|
|
216
|
+
>
|
|
217
|
+
{showSiteNavigation && (
|
|
218
|
+
<nav class="mobile-nav-view" aria-label={projectConfig.locale.labels.siteNavigation}>
|
|
219
|
+
<h2 tabindex="-1">{projectConfig.locale.labels.siteNavigation}</h2>
|
|
220
|
+
<NavigationTreeControls
|
|
221
|
+
idPrefix="mobile-tree-navigation"
|
|
222
|
+
nodes={mobileNavigationTree}
|
|
223
|
+
/>
|
|
224
|
+
<NavigationPageTree
|
|
225
|
+
nodes={mobileNavigationTree}
|
|
226
|
+
currentPage={currentPage}
|
|
227
|
+
variant="mobile"
|
|
228
|
+
integratePageSections={navigationMode === 'tree' || navigationMode === 'top'}
|
|
229
|
+
/>
|
|
230
|
+
</nav>
|
|
231
|
+
)}
|
|
201
232
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
233
|
+
{(navigationMode === 'sections' || !showSiteNavigation) && hasCurrentPageSectionMenu && currentPageNavigation && currentPage && (
|
|
234
|
+
<nav class="mobile-nav-view" aria-label={projectConfig.locale.labels.pageNavigation}>
|
|
235
|
+
{!showSinglePageTopLink && (
|
|
236
|
+
<h2 tabindex="-1">{projectConfig.locale.labels.pageNavigation}</h2>
|
|
237
|
+
)}
|
|
238
|
+
<ul class="mobile-nav-sections">
|
|
239
|
+
{showSinglePageTopLink && (
|
|
240
|
+
<li class="mobile-nav-page-top-item">
|
|
241
|
+
<a
|
|
242
|
+
class="mobile-nav-destination mobile-nav-page-top"
|
|
243
|
+
data-page-top
|
|
244
|
+
href={pageHref(currentPage)}
|
|
245
|
+
>
|
|
246
|
+
{currentPage.title}
|
|
247
|
+
</a>
|
|
248
|
+
</li>
|
|
249
|
+
)}
|
|
250
|
+
{currentPageNavigation.sections.map((section) => (
|
|
251
|
+
<li>
|
|
252
|
+
<a class="mobile-nav-destination" href={`#${section.id}`}>{section.title}</a>
|
|
253
|
+
</li>
|
|
254
|
+
))}
|
|
255
|
+
</ul>
|
|
256
|
+
</nav>
|
|
257
|
+
)}
|
|
258
|
+
</div>
|
|
259
|
+
</div>
|
|
260
|
+
</details>
|
|
226
261
|
)}
|
|
227
|
-
</div>
|
|
228
|
-
</div>
|
|
229
|
-
</details>}
|
|
230
262
|
</div>
|
|
231
263
|
</div>
|
|
232
264
|
)}
|
|
@@ -261,3 +293,4 @@ const needsNavigationScript = hasCurrentPageSectionMenu || hasTreePageContents |
|
|
|
261
293
|
</header>
|
|
262
294
|
|
|
263
295
|
{needsNavigationScript && <SectionNavigationScript />}
|
|
296
|
+
{showSearch && <SearchNavigationScript />}
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
import { getCollection, type CollectionEntry } from 'astro:content';
|
|
3
3
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
|
4
4
|
import CodeBlockCopyScript from './CodeBlockCopyScript.astro';
|
|
5
|
+
import ContentTabsScript from './ContentTabsScript.astro';
|
|
6
|
+
import NoteNavigationScript from './NoteNavigationScript.astro';
|
|
5
7
|
import EditSourceLink from './EditSourceLink.astro';
|
|
8
|
+
import ImageStackEnhancement from './ImageStackEnhancement.astro';
|
|
6
9
|
import SiteBreadcrumbs from './SiteBreadcrumbs.astro';
|
|
7
10
|
import SiteNavigation from './SiteNavigation.astro';
|
|
8
11
|
import SiteBanners from './SiteBanners.astro';
|
|
@@ -10,8 +13,9 @@ import PageContentsNavigation from './PageContentsNavigation.astro';
|
|
|
10
13
|
import PageSequenceNavigation from './PageSequenceNavigation.astro';
|
|
11
14
|
import SiteSection from './SiteSection.astro';
|
|
12
15
|
import SiteTreeNavigation from './SiteTreeNavigation.astro';
|
|
16
|
+
import TableOverflowScript from './TableOverflowScript.astro';
|
|
13
17
|
import TreeNavigationScript from './TreeNavigationScript.astro';
|
|
14
|
-
import { getSectionsContent, type ResolvedSection } from '../lib/sectionContent';
|
|
18
|
+
import { flattenContentBlocks, getSectionsContent, type ResolvedSection } from '../lib/sectionContent';
|
|
15
19
|
import { getPageTheme } from '../lib/pageThemes';
|
|
16
20
|
import { getSiteModel, type SitePage } from '../lib/sitePages';
|
|
17
21
|
import {
|
|
@@ -27,22 +31,25 @@ import {
|
|
|
27
31
|
resolvePageContentsPlacement,
|
|
28
32
|
} from '../../scripts/lib/navigation-model.mjs';
|
|
29
33
|
import { projectConfig, resolveThemeVisualConfig } from '../../scripts/lib/project-config.mjs';
|
|
30
|
-
import {
|
|
34
|
+
import { resolveEditSourceTarget } from '../../scripts/lib/edit-source-link.mjs';
|
|
31
35
|
import { getTextWidthCssValue, resolveFrameColors, resolvePagePresentation } from '../../scripts/lib/presentation.mjs';
|
|
32
36
|
import { mergePageThemeConfig } from '../../scripts/lib/theme-presets.mjs';
|
|
33
37
|
|
|
34
38
|
type SiteEntry = CollectionEntry<'site'>;
|
|
35
39
|
|
|
36
40
|
interface Props {
|
|
37
|
-
entry
|
|
41
|
+
entry?: SiteEntry;
|
|
42
|
+
categoryPath?: string;
|
|
38
43
|
}
|
|
39
44
|
|
|
40
|
-
const { entry } = Astro.props;
|
|
45
|
+
const { entry, categoryPath } = Astro.props;
|
|
41
46
|
const siteDirectory = (process.env.NORNA_SITE_DIR ?? 'site').trim();
|
|
42
47
|
const siteModel = await getSiteModel(await getCollection('site'));
|
|
43
48
|
const { nodes: siteNodes, pages: sitePages } = siteModel;
|
|
44
|
-
const
|
|
45
|
-
|
|
49
|
+
const categoryDestination = categoryPath ? siteModel.categoryDestinations.byPathname.get(categoryPath) : null;
|
|
50
|
+
const currentPage = categoryDestination?.category
|
|
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.`);
|
|
46
53
|
const [siteTheme] = await getCollection('theme');
|
|
47
54
|
if (!siteTheme) {
|
|
48
55
|
throw new Error('site/theme.yaml is required. Add a root theme before building the site.');
|
|
@@ -91,11 +98,35 @@ const listedNavigationTree = getListedSiteNavigationTree(siteNavigation);
|
|
|
91
98
|
const renderedNavigationEntries = flattenSiteNavigationTree(listedNavigationTree);
|
|
92
99
|
const pageSequence = getSequentialPageNavigation(listedNavigationTree, currentPage.pagePath);
|
|
93
100
|
const childPages = getDirectChildPages(listedNavigationTree, currentPage.pagePath);
|
|
94
|
-
const siteHtml = entry
|
|
95
|
-
const resolvedSections =
|
|
96
|
-
|
|
101
|
+
const siteHtml = entry?.rendered?.html ?? '';
|
|
102
|
+
const resolvedSections: ResolvedSection[] = categoryDestination ? [{
|
|
103
|
+
id: null,
|
|
104
|
+
title: currentPage.title,
|
|
105
|
+
titleHtml: currentPage.title.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>'),
|
|
106
|
+
headingLevel: 1,
|
|
107
|
+
contentBlocks: [{
|
|
108
|
+
type: 'page-list',
|
|
109
|
+
pages: categoryDestination.children.map((node) => ({
|
|
110
|
+
title: node.title,
|
|
111
|
+
pathname: node.pathname,
|
|
112
|
+
description: node.kind === 'page' ? node.entry.data.page?.description : undefined,
|
|
113
|
+
})),
|
|
114
|
+
}],
|
|
115
|
+
}] : await getSectionsContent(siteHtml, currentPage, childPages);
|
|
116
|
+
const allContentBlocks = resolvedSections.flatMap((section) => flattenContentBlocks(section.contentBlocks));
|
|
117
|
+
const hasTabs = allContentBlocks.some((block) => block.type === 'tabs');
|
|
118
|
+
const hasNotes = allContentBlocks.some((block) => (
|
|
119
|
+
block.type === 'html' && /class="[^"]*\bsection-note-ref\b|\bdata-footnote-ref(?:="")?/.test(block.html)
|
|
120
|
+
));
|
|
121
|
+
const hasCodeBlocks = allContentBlocks.some((block) => (
|
|
97
122
|
block.type === 'html' && /<pre\b[^>]*>[\s\S]*?<code\b/i.test(block.html)
|
|
98
|
-
))
|
|
123
|
+
));
|
|
124
|
+
const hasTables = allContentBlocks.some((block) => (
|
|
125
|
+
block.type === 'html' && /\bdata-table-frame(?:="")?/i.test(block.html)
|
|
126
|
+
));
|
|
127
|
+
const hasImageStack = allContentBlocks.some((block) => (
|
|
128
|
+
block.type === 'image-stack'
|
|
129
|
+
));
|
|
99
130
|
const hasPrioritizableImage = (section: ResolvedSection) => section.contentBlocks.some((block) => (
|
|
100
131
|
block.type === 'image-stack'
|
|
101
132
|
|| block.type === 'image-carousel'
|
|
@@ -108,10 +139,13 @@ const renderedSections = resolvedSections.map((section, index) => ({
|
|
|
108
139
|
prioritizeFirstImage: index === firstImageSectionIndex,
|
|
109
140
|
}));
|
|
110
141
|
const hasPageSequence = Boolean(pageSequence.previous || pageSequence.next);
|
|
111
|
-
const
|
|
112
|
-
?
|
|
113
|
-
|
|
114
|
-
|
|
142
|
+
const editSourceTarget = projectConfig.editLink && currentPage.kind === 'page'
|
|
143
|
+
? resolveEditSourceTarget({
|
|
144
|
+
...projectConfig.editLink,
|
|
145
|
+
development: import.meta.env.DEV,
|
|
146
|
+
hostname: Astro.url.hostname,
|
|
147
|
+
sourceLabel: currentPage.contentLabel,
|
|
148
|
+
sourcePath: currentPage.contentPath,
|
|
115
149
|
})
|
|
116
150
|
: null;
|
|
117
151
|
const navigationModel = resolveNavigationModel({
|
|
@@ -135,6 +169,7 @@ const pagePresentation = resolvePagePresentation(themeData, themeLabel, {
|
|
|
135
169
|
const pageTextWidth = getTextWidthCssValue(pagePresentation.typography.values.body.width);
|
|
136
170
|
const frameColors = resolveFrameColors({ theme: siteThemeData, sourceLabel: `${siteDirectory || 'site'}/${siteTheme.id}` });
|
|
137
171
|
const hasTreeNavigation = navigationModel.mode === 'tree';
|
|
172
|
+
const showTreeNavigation = hasTreeNavigation && !currentPage.isHome;
|
|
138
173
|
const currentPageNavigation = siteNavigation.find(({ node }) => (
|
|
139
174
|
node.kind === 'page' && node.pathname === currentPage.pathname
|
|
140
175
|
));
|
|
@@ -154,27 +189,31 @@ const pageContentsPlacement = resolvePageContentsPlacement({
|
|
|
154
189
|
headingCount: pageContentsHeadings.length,
|
|
155
190
|
});
|
|
156
191
|
const hasContentsNavigation = pageContentsPlacement.placement !== 'none';
|
|
157
|
-
const
|
|
192
|
+
const hasTrackedSectionNavigation = hasContentsNavigation
|
|
193
|
+
|| (hasTreeNavigation && currentPage.isHome && pageContentsHeadings.length >= 2);
|
|
194
|
+
const integratePageContentsInTree = hasTreeNavigation;
|
|
158
195
|
const showContentsRail = pageContentsPlacement.placement === 'contents-rail';
|
|
159
|
-
const hasNavigationRailFrame =
|
|
196
|
+
const hasNavigationRailFrame = !currentPage.isHome && (
|
|
197
|
+
navigationModel.requestedMode === 'tree'
|
|
160
198
|
|| (
|
|
161
199
|
navigationModel.requestedMode === 'automatic'
|
|
162
200
|
&& (navigationModel.hasNestedPages || navigationModel.hasCategories)
|
|
163
|
-
)
|
|
201
|
+
)
|
|
202
|
+
);
|
|
164
203
|
const showBreadcrumbs = !currentPage.isHome && currentPage.parentPagePath !== null;
|
|
165
|
-
const reserveBreadcrumbSpace =
|
|
204
|
+
const reserveBreadcrumbSpace = showTreeNavigation || showBreadcrumbs;
|
|
166
205
|
|
|
167
206
|
---
|
|
168
207
|
|
|
169
208
|
<BaseLayout
|
|
170
209
|
title={currentPage.title}
|
|
171
|
-
description={entry
|
|
210
|
+
description={entry?.data.page?.description}
|
|
172
211
|
presentation={pagePresentation}
|
|
173
212
|
visualTheme={visualTheme}
|
|
174
213
|
frameColors={frameColors}
|
|
175
214
|
pathname={currentPage.pathname}
|
|
176
215
|
footer={sitewideData.footer}
|
|
177
|
-
contentsNavigation={
|
|
216
|
+
contentsNavigation={hasTrackedSectionNavigation}
|
|
178
217
|
searchable
|
|
179
218
|
>
|
|
180
219
|
<SiteNavigation
|
|
@@ -190,12 +229,12 @@ const reserveBreadcrumbSpace = hasTreeNavigation || showBreadcrumbs;
|
|
|
190
229
|
{siteBanners.length > 0 && <SiteBanners banners={siteBanners} />}
|
|
191
230
|
|
|
192
231
|
<div class:list={['site-page-layout', {
|
|
193
|
-
'site-page-layout-tree':
|
|
232
|
+
'site-page-layout-tree': showTreeNavigation,
|
|
194
233
|
'site-page-layout-contents': showContentsRail,
|
|
195
234
|
}]}
|
|
196
235
|
data-page-contents-placement={hasContentsNavigation ? pageContentsPlacement.placement : undefined}
|
|
197
236
|
>
|
|
198
|
-
{
|
|
237
|
+
{showTreeNavigation && (
|
|
199
238
|
<SiteTreeNavigation
|
|
200
239
|
currentPage={currentPage}
|
|
201
240
|
navigationEntries={siteNavigation}
|
|
@@ -205,7 +244,7 @@ const reserveBreadcrumbSpace = hasTreeNavigation || showBreadcrumbs;
|
|
|
205
244
|
<main
|
|
206
245
|
class:list={['site-content', {
|
|
207
246
|
'site-content-has-breadcrumbs': reserveBreadcrumbSpace,
|
|
208
|
-
'site-content-has-edit-source': Boolean(
|
|
247
|
+
'site-content-has-edit-source': Boolean(editSourceTarget),
|
|
209
248
|
'site-content-has-page-sequence': hasPageSequence,
|
|
210
249
|
}]}
|
|
211
250
|
id="main-content"
|
|
@@ -215,13 +254,6 @@ const reserveBreadcrumbSpace = hasTreeNavigation || showBreadcrumbs;
|
|
|
215
254
|
{showBreadcrumbs
|
|
216
255
|
? <SiteBreadcrumbs currentPage={currentPage} nodes={siteNodes} />
|
|
217
256
|
: 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
|
-
)}
|
|
225
257
|
{renderedSections.map(({ section, sectionIndex, prioritizeFirstImage }) => (
|
|
226
258
|
<SiteSection
|
|
227
259
|
section={section}
|
|
@@ -231,8 +263,13 @@ const reserveBreadcrumbSpace = hasTreeNavigation || showBreadcrumbs;
|
|
|
231
263
|
prioritizeFirstImage={prioritizeFirstImage}
|
|
232
264
|
/>
|
|
233
265
|
))}
|
|
234
|
-
{
|
|
235
|
-
<EditSourceLink
|
|
266
|
+
{editSourceTarget && (
|
|
267
|
+
<EditSourceLink
|
|
268
|
+
href={editSourceTarget.href}
|
|
269
|
+
label={editSourceTarget.kind === 'local'
|
|
270
|
+
? projectConfig.locale.labels.openInVsCode
|
|
271
|
+
: projectConfig.locale.labels.editSource}
|
|
272
|
+
/>
|
|
236
273
|
)}
|
|
237
274
|
<PageSequenceNavigation previous={pageSequence.previous} next={pageSequence.next} />
|
|
238
275
|
</main>
|
|
@@ -240,10 +277,13 @@ const reserveBreadcrumbSpace = hasTreeNavigation || showBreadcrumbs;
|
|
|
240
277
|
<PageContentsNavigation
|
|
241
278
|
headings={pageContentsHeadings}
|
|
242
279
|
pageTitle={currentPage.title}
|
|
243
|
-
variant="rail"
|
|
244
280
|
/>
|
|
245
281
|
)}
|
|
246
282
|
</div>
|
|
247
283
|
{hasTreeNavigation && <TreeNavigationScript />}
|
|
248
284
|
{hasCodeBlocks && <CodeBlockCopyScript />}
|
|
285
|
+
{hasTabs && <ContentTabsScript />}
|
|
286
|
+
{hasNotes && <NoteNavigationScript />}
|
|
287
|
+
{hasTables && <TableOverflowScript />}
|
|
288
|
+
{hasImageStack && <ImageStackEnhancement />}
|
|
249
289
|
</BaseLayout>
|
|
@@ -6,11 +6,8 @@ import {
|
|
|
6
6
|
getHeadingTypographySizeValue,
|
|
7
7
|
getTypographySizeValue,
|
|
8
8
|
} from '../../scripts/lib/typography.mjs';
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
import ImageStack from './ImageStack.astro';
|
|
12
|
-
import PageList from './PageList.astro';
|
|
13
|
-
import type { ResolvedSection } from '../lib/sectionContent';
|
|
9
|
+
import ContentBlocks from './ContentBlocks.astro';
|
|
10
|
+
import { flattenContentBlocks, type ResolvedSection } from '../lib/sectionContent';
|
|
14
11
|
|
|
15
12
|
type ResponsiveValue<T> = { desktop: T; mobile: T };
|
|
16
13
|
type ResponsiveOverride<T> = { desktop?: T; mobile?: T };
|
|
@@ -110,42 +107,11 @@ const bodyWidth = {
|
|
|
110
107
|
desktop: bodyWidthValue,
|
|
111
108
|
mobile: bodyWidthValue,
|
|
112
109
|
};
|
|
113
|
-
const
|
|
114
|
-
block.type === 'image-stack'
|
|
115
|
-
|| block.type === 'image-carousel'
|
|
116
|
-
|| block.type === 'card-list'
|
|
117
|
-
|| block.type === 'page-list'
|
|
118
|
-
);
|
|
119
|
-
const hasMedia = section.contentBlocks.some((block) => (
|
|
110
|
+
const hasMedia = flattenContentBlocks(section.contentBlocks).some((block) => (
|
|
120
111
|
block.type === 'image-stack'
|
|
121
112
|
|| block.type === 'image-carousel'
|
|
122
113
|
|| (block.type === 'card-list' && block.cards.some((card) => card.image))
|
|
123
114
|
));
|
|
124
|
-
const contentGroups: Array<
|
|
125
|
-
| { type: 'reading'; blocks: ResolvedSection['contentBlocks'] }
|
|
126
|
-
| { type: 'structured'; block: Extract<ResolvedSection['contentBlocks'][number], { type: 'image-stack' | 'image-carousel' | 'card-list' | 'page-list' }> }
|
|
127
|
-
> = [];
|
|
128
|
-
let readingBlocks: ResolvedSection['contentBlocks'] = [];
|
|
129
|
-
const flushReadingBlocks = () => {
|
|
130
|
-
if (readingBlocks.length === 0) return;
|
|
131
|
-
contentGroups.push({ type: 'reading', blocks: readingBlocks });
|
|
132
|
-
readingBlocks = [];
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
for (const block of section.contentBlocks) {
|
|
136
|
-
if (isStructuredBlock(block)) {
|
|
137
|
-
flushReadingBlocks();
|
|
138
|
-
contentGroups.push({ type: 'structured', block });
|
|
139
|
-
} else {
|
|
140
|
-
readingBlocks = [...readingBlocks, block];
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
flushReadingBlocks();
|
|
145
|
-
const firstMediaGroupIndex = contentGroups.findIndex((group) => (
|
|
146
|
-
group.type === 'structured'
|
|
147
|
-
&& (group.block.type === 'image-stack' || group.block.type === 'image-carousel')
|
|
148
|
-
));
|
|
149
115
|
const markdownHeadingStyle = (level: 'h3' | 'h4') => {
|
|
150
116
|
const headingConfig = markdownHeadings[level];
|
|
151
117
|
const fontSize = getHeadingTypographySizeValue(level, headingConfig.size);
|
|
@@ -209,55 +175,13 @@ const sectionStyle = [
|
|
|
209
175
|
style={sectionStyle}
|
|
210
176
|
>
|
|
211
177
|
<div class="section-card">
|
|
178
|
+
<slot name="before-heading" />
|
|
212
179
|
<header class="section-header">
|
|
213
180
|
<HeadingTag id={headingId} set:html={section.titleHtml} />
|
|
214
181
|
</header>
|
|
215
182
|
|
|
216
183
|
<div class:list={['section-body', { 'section-body-has-media': hasMedia, 'section-body-text-only': !hasMedia }]}>
|
|
217
|
-
{
|
|
218
|
-
if (group.type === 'reading') {
|
|
219
|
-
return group.blocks.map((block) => block.type === 'html'
|
|
220
|
-
&& block.html
|
|
221
|
-
&& <div class="section-markdown" set:html={block.html} />);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const block = group.block;
|
|
225
|
-
if (block.type === 'image-carousel') {
|
|
226
|
-
return (
|
|
227
|
-
<ImageCarousel
|
|
228
|
-
title={section.title}
|
|
229
|
-
images={block.images}
|
|
230
|
-
maxAvailableWidthPercent={visualTheme.images.maxAvailableWidthPercent}
|
|
231
|
-
maxAvailableHeightPercent={visualTheme.images.maxAvailableHeightPercent}
|
|
232
|
-
priorityFirstImage={prioritizeFirstImage && groupIndex === firstMediaGroupIndex}
|
|
233
|
-
/>
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (block.type === 'card-list') {
|
|
238
|
-
return (
|
|
239
|
-
<CardList
|
|
240
|
-
cards={block.cards}
|
|
241
|
-
layout={block.layout}
|
|
242
|
-
flow={block.flow}
|
|
243
|
-
size={block.size}
|
|
244
|
-
width={block.width ?? pagePresentation.blocks.cardList.width}
|
|
245
|
-
/>
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
if (block.type === 'page-list') {
|
|
249
|
-
return <PageList pages={block.pages} />;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
return (
|
|
253
|
-
<ImageStack
|
|
254
|
-
title={section.title}
|
|
255
|
-
images={block.images}
|
|
256
|
-
maxAvailableWidthPercent={visualTheme.images.maxAvailableWidthPercent}
|
|
257
|
-
priorityFirstImage={prioritizeFirstImage && groupIndex === firstMediaGroupIndex}
|
|
258
|
-
/>
|
|
259
|
-
);
|
|
260
|
-
})}
|
|
184
|
+
<ContentBlocks blocks={section.contentBlocks} title={section.title} images={visualTheme.images} cardWidth={pagePresentation.blocks.cardList.width} prioritizeFirstImage={prioritizeFirstImage} />
|
|
261
185
|
</div>
|
|
262
186
|
</div>
|
|
263
187
|
</section>
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
---
|
|
2
2
|
import projectConfig from '../../scripts/lib/project-config.mjs';
|
|
3
|
+
import { withBasePath } from '../lib/basePath';
|
|
3
4
|
import { getListedSiteNavigationTree, type SiteNavigationEntry } from '../lib/siteNavigation';
|
|
4
|
-
import type {
|
|
5
|
+
import type { SiteNode } from '../lib/sitePages';
|
|
5
6
|
import NavigationPageTree from './NavigationPageTree.astro';
|
|
6
7
|
import NavigationTreeControls from './NavigationTreeControls.astro';
|
|
7
8
|
|
|
8
9
|
interface Props {
|
|
9
|
-
currentPage:
|
|
10
|
+
currentPage: SiteNode;
|
|
10
11
|
navigationEntries: SiteNavigationEntry[];
|
|
11
12
|
integratePageSections?: boolean;
|
|
12
13
|
}
|
|
@@ -18,6 +19,7 @@ const activeRoot = roots.find((node) => (
|
|
|
18
19
|
|| currentPage.pageDirectory.startsWith(`${node.node.pageDirectory}/pages/`)
|
|
19
20
|
));
|
|
20
21
|
const navigationRoot = activeRoot?.node.pagePath ? `/${activeRoot.node.pagePath}/` : '/';
|
|
22
|
+
const navigationStateRoot = withBasePath(projectConfig.site.basePath, '/');
|
|
21
23
|
---
|
|
22
24
|
|
|
23
25
|
{activeRoot && (
|
|
@@ -25,6 +27,7 @@ const navigationRoot = activeRoot?.node.pagePath ? `/${activeRoot.node.pagePath}
|
|
|
25
27
|
id="tree-local-navigation"
|
|
26
28
|
class="tree-local-navigation"
|
|
27
29
|
data-navigation-root={navigationRoot}
|
|
30
|
+
data-navigation-state-root={navigationStateRoot}
|
|
28
31
|
>
|
|
29
32
|
<NavigationTreeControls
|
|
30
33
|
idPrefix="desktop-tree-navigation"
|