@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
|
@@ -20,9 +20,9 @@ interface Props {
|
|
|
20
20
|
const { controls, defaults } = Astro.props;
|
|
21
21
|
const labels = projectConfig.locale.labels;
|
|
22
22
|
const appearanceOptions = [
|
|
23
|
-
{ value: 'system', label: labels.
|
|
24
|
-
{ value: 'light', label: labels.
|
|
25
|
-
{ value: 'dark', label: labels.
|
|
23
|
+
{ value: 'system', label: labels.appearanceSystem },
|
|
24
|
+
{ value: 'light', label: labels.appearanceLight },
|
|
25
|
+
{ value: 'dark', label: labels.appearanceDark },
|
|
26
26
|
] as const;
|
|
27
27
|
const widthOptions = [
|
|
28
28
|
{ value: 'narrow', label: labels.readingWidthNarrow },
|
|
@@ -51,7 +51,7 @@ const widthOptions = [
|
|
|
51
51
|
|
|
52
52
|
{controls.appearance && (
|
|
53
53
|
<fieldset>
|
|
54
|
-
<legend>{labels.
|
|
54
|
+
<legend>{labels.appearance}</legend>
|
|
55
55
|
<div class="display-settings-segmented">
|
|
56
56
|
{appearanceOptions.map((option) => (
|
|
57
57
|
<label>
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { PencilLine } from '@lucide/astro';
|
|
3
|
+
|
|
4
|
+
interface Props {
|
|
5
|
+
href: string;
|
|
6
|
+
label: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { href, label } = Astro.props;
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<p class="edit-source-link" data-pagefind-ignore="all">
|
|
13
|
+
<a href={href}>
|
|
14
|
+
<PencilLine aria-hidden="true" />
|
|
15
|
+
<span>{label}</span>
|
|
16
|
+
</a>
|
|
17
|
+
</p>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
import projectConfig from '../../scripts/lib/project-config.mjs';
|
|
3
|
+
import { defaultImageMaxAvailableHeightPercent } from '../../scripts/lib/image-presentation.mjs';
|
|
3
4
|
import { getImageAttributes } from '../lib/generatedImages';
|
|
4
5
|
import {
|
|
5
6
|
getCaptionId,
|
|
@@ -17,7 +18,7 @@ interface Props {
|
|
|
17
18
|
desktop: number;
|
|
18
19
|
mobile: number;
|
|
19
20
|
};
|
|
20
|
-
maxAvailableHeightPercent
|
|
21
|
+
maxAvailableHeightPercent?: {
|
|
21
22
|
desktop: number;
|
|
22
23
|
mobile: number;
|
|
23
24
|
};
|
|
@@ -33,11 +34,13 @@ const {
|
|
|
33
34
|
} = Astro.props;
|
|
34
35
|
const managedImageSizes = getManagedImageSizes(maxAvailableWidthPercent);
|
|
35
36
|
const carouselAspectRatio = getCarouselAspectRatio(images);
|
|
37
|
+
const carouselMaxAvailableHeightPercent = maxAvailableHeightPercent
|
|
38
|
+
?? defaultImageMaxAvailableHeightPercent;
|
|
36
39
|
const carouselStyle = carouselAspectRatio
|
|
37
40
|
? [
|
|
38
41
|
`--image-carousel-aspect-ratio: ${carouselAspectRatio}`,
|
|
39
|
-
`--image-carousel-width-from-height-desktop: ${carouselAspectRatio *
|
|
40
|
-
`--image-carousel-width-from-height-mobile: ${carouselAspectRatio *
|
|
42
|
+
`--image-carousel-width-from-height-desktop: ${carouselAspectRatio * carouselMaxAvailableHeightPercent.desktop}svh`,
|
|
43
|
+
`--image-carousel-width-from-height-mobile: ${carouselAspectRatio * carouselMaxAvailableHeightPercent.mobile}svh`,
|
|
41
44
|
].join('; ')
|
|
42
45
|
: undefined;
|
|
43
46
|
---
|
|
@@ -7,7 +7,7 @@ import PageContentsLinks from './PageContentsLinks.astro';
|
|
|
7
7
|
|
|
8
8
|
interface Props {
|
|
9
9
|
nodes: SiteNavigationNode[];
|
|
10
|
-
currentPage
|
|
10
|
+
currentPage?: SitePage;
|
|
11
11
|
variant: 'mobile' | 'sidebar' | 'submenu';
|
|
12
12
|
integratePageSections?: boolean;
|
|
13
13
|
}
|
|
@@ -15,21 +15,31 @@ interface Props {
|
|
|
15
15
|
const { nodes, currentPage, variant, integratePageSections = false } = Astro.props;
|
|
16
16
|
const pageHref = (page: SitePage) => withBasePath(projectConfig.site.basePath, page.pathname);
|
|
17
17
|
const hasBranch = (node: SiteNavigationNode) => node.children.length > 0;
|
|
18
|
-
const isCurrentPage = (page: SitePage) =>
|
|
19
|
-
const isCurrentBranch = (node: SiteNode) => (
|
|
18
|
+
const isCurrentPage = (page: SitePage) => currentPage?.pathname === page.pathname;
|
|
19
|
+
const isCurrentBranch = (node: SiteNode) => Boolean(currentPage && (
|
|
20
20
|
currentPage.pageDirectory === node.pageDirectory
|
|
21
21
|
|| currentPage.pageDirectory.startsWith(`${node.pageDirectory}/pages/`)
|
|
22
|
-
);
|
|
22
|
+
));
|
|
23
23
|
const getBranchId = (node: SiteNode) => [
|
|
24
24
|
'navigation',
|
|
25
25
|
variant,
|
|
26
26
|
node.pagePath || 'home',
|
|
27
27
|
'children',
|
|
28
28
|
].join('-').replace(/[^a-z0-9-]+/gi, '-');
|
|
29
|
+
const getSectionsId = (node: SiteNode) => [
|
|
30
|
+
'navigation',
|
|
31
|
+
variant,
|
|
32
|
+
node.pagePath || 'home',
|
|
33
|
+
'sections',
|
|
34
|
+
].join('-').replace(/[^a-z0-9-]+/gi, '-');
|
|
29
35
|
const hasPageSections = (node: SiteNavigationNode) => (
|
|
30
36
|
node.node.kind === 'page' && node.headings.some((heading) => heading.depth === 2)
|
|
31
37
|
);
|
|
32
|
-
const
|
|
38
|
+
const showsPageSections = (node: SiteNavigationNode) => (
|
|
39
|
+
integratePageSections
|
|
40
|
+
&& hasPageSections(node)
|
|
41
|
+
);
|
|
42
|
+
const usesPageDisclosure = (node: SiteNavigationNode) => variant !== 'submenu' && hasBranch(node);
|
|
33
43
|
---
|
|
34
44
|
|
|
35
45
|
<ul class:list={['navigation-page-tree', `navigation-page-tree-${variant}`]}>
|
|
@@ -37,9 +47,13 @@ const hasPageDisclosure = (node: SiteNavigationNode) => hasBranch(node) || hasPa
|
|
|
37
47
|
<li
|
|
38
48
|
class:list={['navigation-page-node', {
|
|
39
49
|
'navigation-page-node-category': node.node.kind === 'category',
|
|
50
|
+
'navigation-page-node-page-disclosure': node.node.kind === 'page' && usesPageDisclosure(node),
|
|
40
51
|
'navigation-page-node-current': node.node.kind === 'page' && isCurrentPage(node.node),
|
|
41
52
|
'navigation-page-node-current-branch': isCurrentBranch(node.node),
|
|
42
53
|
}]}
|
|
54
|
+
data-navigation-title={node.node.title}
|
|
55
|
+
data-navigation-kind={node.node.kind}
|
|
56
|
+
data-navigation-current-page={node.node.kind === 'page' && isCurrentPage(node.node) ? 'true' : undefined}
|
|
43
57
|
>
|
|
44
58
|
{node.node.kind === 'category' ? (
|
|
45
59
|
<details
|
|
@@ -69,103 +83,84 @@ const hasPageDisclosure = (node: SiteNavigationNode) => hasBranch(node) || hasPa
|
|
|
69
83
|
/>
|
|
70
84
|
</div>
|
|
71
85
|
</details>
|
|
72
|
-
) : variant !== 'submenu' &&
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
aria-controls={getBranchId(node.node)}
|
|
85
|
-
>
|
|
86
|
-
<span class="navigation-page-summary-title">{node.node.title}</span>
|
|
87
|
-
<span class="navigation-page-chevron" aria-hidden="true"></span>
|
|
88
|
-
</summary>
|
|
89
|
-
<a
|
|
90
|
-
class:list={['navigation-page-open-link', { 'mobile-nav-destination': variant === 'mobile' }]}
|
|
91
|
-
href={pageHref(node.node)}
|
|
92
|
-
aria-current={isCurrentPage(node.node) ? 'page' : undefined}
|
|
93
|
-
>
|
|
94
|
-
{node.node.title}
|
|
95
|
-
</a>
|
|
96
|
-
<div id={getBranchId(node.node)} class="navigation-page-branch-content">
|
|
97
|
-
{hasPageSections(node) && (
|
|
98
|
-
<nav
|
|
99
|
-
class="navigation-page-sections"
|
|
100
|
-
aria-label={`${projectConfig.locale.labels.pageNavigation}: ${node.node.title}`}
|
|
101
|
-
>
|
|
102
|
-
<PageContentsLinks
|
|
103
|
-
headings={node.headings}
|
|
104
|
-
variant={variant === 'mobile' ? 'mobile' : 'desktop'}
|
|
105
|
-
pageHref={isCurrentPage(node.node) ? undefined : pageHref(node.node)}
|
|
106
|
-
/>
|
|
107
|
-
</nav>
|
|
108
|
-
)}
|
|
109
|
-
{hasBranch(node) && (
|
|
110
|
-
<Astro.self
|
|
111
|
-
nodes={node.children}
|
|
112
|
-
currentPage={currentPage}
|
|
113
|
-
variant={variant}
|
|
114
|
-
integratePageSections={integratePageSections}
|
|
115
|
-
/>
|
|
116
|
-
)}
|
|
117
|
-
</div>
|
|
118
|
-
</details>
|
|
119
|
-
) : (
|
|
120
|
-
<a
|
|
121
|
-
class:list={['navigation-page-link', { 'mobile-nav-destination': variant === 'mobile' }]}
|
|
122
|
-
href={pageHref(node.node)}
|
|
123
|
-
aria-current={isCurrentPage(node.node) ? 'page' : undefined}
|
|
124
|
-
>
|
|
125
|
-
{node.node.title}
|
|
126
|
-
</a>
|
|
127
|
-
)
|
|
128
|
-
) : variant !== 'submenu' ? (
|
|
129
|
-
<>
|
|
130
|
-
<a
|
|
131
|
-
class:list={['navigation-page-link', { 'mobile-nav-destination': variant === 'mobile' }]}
|
|
132
|
-
href={pageHref(node.node)}
|
|
133
|
-
aria-current={isCurrentPage(node.node) ? 'page' : undefined}
|
|
86
|
+
) : variant !== 'submenu' && usesPageDisclosure(node) ? (
|
|
87
|
+
<details
|
|
88
|
+
class:list={['navigation-page-branch', 'navigation-page-disclosure', `navigation-page-disclosure-${variant}`]}
|
|
89
|
+
data-page-path={node.node.pagePath || '/'}
|
|
90
|
+
data-current-branch={isCurrentBranch(node.node) ? 'true' : undefined}
|
|
91
|
+
data-current-page={isCurrentPage(node.node) ? 'true' : undefined}
|
|
92
|
+
open={isCurrentBranch(node.node)}
|
|
93
|
+
>
|
|
94
|
+
<summary
|
|
95
|
+
class="navigation-page-summary"
|
|
96
|
+
aria-controls={getBranchId(node.node)}
|
|
97
|
+
aria-label={`${projectConfig.locale.labels.navigationChildren}: ${node.node.title}`}
|
|
134
98
|
>
|
|
135
|
-
|
|
136
|
-
</
|
|
137
|
-
|
|
99
|
+
<span class="navigation-page-chevron" aria-hidden="true"></span>
|
|
100
|
+
</summary>
|
|
101
|
+
</details>
|
|
102
|
+
<a
|
|
103
|
+
class:list={['navigation-page-open-link', { 'mobile-nav-destination': variant === 'mobile' }]}
|
|
104
|
+
href={pageHref(node.node)}
|
|
105
|
+
aria-current={isCurrentPage(node.node) ? 'page' : undefined}
|
|
106
|
+
>
|
|
107
|
+
{node.node.title}
|
|
108
|
+
</a>
|
|
109
|
+
<div id={getBranchId(node.node)} class="navigation-page-branch-content">
|
|
110
|
+
{showsPageSections(node) && (
|
|
138
111
|
<nav
|
|
139
112
|
class="navigation-page-sections"
|
|
140
113
|
aria-label={`${projectConfig.locale.labels.pageNavigation}: ${node.node.title}`}
|
|
141
114
|
>
|
|
142
115
|
<PageContentsLinks
|
|
143
116
|
headings={node.headings}
|
|
144
|
-
variant=
|
|
117
|
+
variant={variant === 'mobile' ? 'mobile' : 'desktop'}
|
|
118
|
+
pageHref={isCurrentPage(node.node) ? undefined : pageHref(node.node)}
|
|
145
119
|
/>
|
|
146
120
|
</nav>
|
|
147
121
|
)}
|
|
148
122
|
{hasBranch(node) && (
|
|
123
|
+
<Astro.self
|
|
124
|
+
nodes={node.children}
|
|
125
|
+
currentPage={currentPage}
|
|
126
|
+
variant={variant}
|
|
127
|
+
integratePageSections={integratePageSections}
|
|
128
|
+
/>
|
|
129
|
+
)}
|
|
130
|
+
</div>
|
|
131
|
+
) : variant !== 'submenu' ? (
|
|
132
|
+
<>
|
|
133
|
+
<a
|
|
134
|
+
class:list={['navigation-page-link', { 'mobile-nav-destination': variant === 'mobile' }]}
|
|
135
|
+
href={pageHref(node.node)}
|
|
136
|
+
aria-current={isCurrentPage(node.node) ? 'page' : undefined}
|
|
137
|
+
>
|
|
138
|
+
{node.node.title}
|
|
139
|
+
</a>
|
|
140
|
+
{showsPageSections(node) && (
|
|
149
141
|
<details
|
|
150
|
-
class="navigation-page-
|
|
142
|
+
class="navigation-page-sections-disclosure"
|
|
151
143
|
data-page-path={node.node.pagePath || '/'}
|
|
152
|
-
data-current-
|
|
153
|
-
open={
|
|
144
|
+
data-current-page={isCurrentPage(node.node) ? 'true' : undefined}
|
|
145
|
+
open={isCurrentPage(node.node)}
|
|
154
146
|
>
|
|
155
|
-
<summary
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
<span aria-hidden="true"></span>
|
|
147
|
+
<summary
|
|
148
|
+
aria-controls={getSectionsId(node.node)}
|
|
149
|
+
aria-label={`${projectConfig.locale.labels.pageSections}: ${node.node.title}`}
|
|
150
|
+
>
|
|
151
|
+
<span class="navigation-page-chevron" aria-hidden="true"></span>
|
|
160
152
|
</summary>
|
|
161
|
-
<
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
153
|
+
<nav
|
|
154
|
+
id={getSectionsId(node.node)}
|
|
155
|
+
class="navigation-page-sections"
|
|
156
|
+
aria-label={`${projectConfig.locale.labels.pageNavigation}: ${node.node.title}`}
|
|
157
|
+
>
|
|
158
|
+
<PageContentsLinks
|
|
159
|
+
headings={node.headings}
|
|
160
|
+
variant={variant === 'mobile' ? 'mobile' : 'desktop'}
|
|
161
|
+
pageHref={isCurrentPage(node.node) ? undefined : pageHref(node.node)}
|
|
167
162
|
/>
|
|
168
|
-
</
|
|
163
|
+
</nav>
|
|
169
164
|
</details>
|
|
170
165
|
)}
|
|
171
166
|
</>
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { ChevronsDownUp, ChevronsUpDown, ListFilter, LocateFixed } from '@lucide/astro';
|
|
3
|
+
import projectConfig from '../../scripts/lib/project-config.mjs';
|
|
4
|
+
import type { SiteNavigationNode } from '../lib/siteNavigation';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
idPrefix: string;
|
|
8
|
+
nodes: SiteNavigationNode[];
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { idPrefix, nodes } = Astro.props;
|
|
12
|
+
const countNodes = (tree: SiteNavigationNode[]): number => tree.reduce(
|
|
13
|
+
(total, node) => total + 1 + countNodes(node.children),
|
|
14
|
+
0,
|
|
15
|
+
);
|
|
16
|
+
const minimumUsefulNodeCount = 12;
|
|
17
|
+
const showControls = countNodes(nodes) >= minimumUsefulNodeCount;
|
|
18
|
+
const labels = projectConfig.locale.labels;
|
|
19
|
+
const filterId = `${idPrefix}-filter`;
|
|
20
|
+
const statusId = `${idPrefix}-status`;
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
{showControls && (
|
|
24
|
+
<div
|
|
25
|
+
class="navigation-tree-controls"
|
|
26
|
+
data-tree-controls
|
|
27
|
+
data-tree-label-collapsed={labels.navigationCollapsedAll}
|
|
28
|
+
data-tree-label-expanded={labels.navigationExpandedAll}
|
|
29
|
+
data-tree-label-filter-matches={labels.navigationFilterMatches}
|
|
30
|
+
data-tree-label-located={labels.navigationLocatedCurrent}
|
|
31
|
+
hidden
|
|
32
|
+
>
|
|
33
|
+
<label class="navigation-tree-filter" for={filterId}>
|
|
34
|
+
<ListFilter aria-hidden="true" />
|
|
35
|
+
<span class="visually-hidden">{labels.navigationFilter}</span>
|
|
36
|
+
<input
|
|
37
|
+
id={filterId}
|
|
38
|
+
type="search"
|
|
39
|
+
autocomplete="off"
|
|
40
|
+
spellcheck="false"
|
|
41
|
+
placeholder={labels.navigationFilter}
|
|
42
|
+
aria-describedby={statusId}
|
|
43
|
+
data-tree-filter
|
|
44
|
+
/>
|
|
45
|
+
</label>
|
|
46
|
+
<div class="navigation-tree-commands" role="group" aria-label={labels.navigationControls}>
|
|
47
|
+
<button
|
|
48
|
+
type="button"
|
|
49
|
+
aria-label={labels.navigationExpandAll}
|
|
50
|
+
title={labels.navigationExpandAll}
|
|
51
|
+
data-tree-expand-all
|
|
52
|
+
>
|
|
53
|
+
<ChevronsUpDown aria-hidden="true" />
|
|
54
|
+
</button>
|
|
55
|
+
<button
|
|
56
|
+
type="button"
|
|
57
|
+
aria-label={labels.navigationCollapseAll}
|
|
58
|
+
title={labels.navigationCollapseAll}
|
|
59
|
+
data-tree-collapse-all
|
|
60
|
+
>
|
|
61
|
+
<ChevronsDownUp aria-hidden="true" />
|
|
62
|
+
</button>
|
|
63
|
+
<button
|
|
64
|
+
type="button"
|
|
65
|
+
aria-label={labels.navigationLocateCurrent}
|
|
66
|
+
title={labels.navigationLocateCurrent}
|
|
67
|
+
data-tree-locate-current
|
|
68
|
+
>
|
|
69
|
+
<LocateFixed aria-hidden="true" />
|
|
70
|
+
</button>
|
|
71
|
+
</div>
|
|
72
|
+
<p class="navigation-tree-filter-empty" data-tree-filter-empty hidden>
|
|
73
|
+
{labels.navigationFilterEmpty}
|
|
74
|
+
</p>
|
|
75
|
+
<p id={statusId} class="visually-hidden" role="status" aria-live="polite" data-tree-status></p>
|
|
76
|
+
</div>
|
|
77
|
+
)}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
---
|
|
2
|
+
import projectConfig from '../../scripts/lib/project-config.mjs';
|
|
3
|
+
import { getAbsolutePageUrl } from '../../scripts/lib/site-page-urls.mjs';
|
|
4
|
+
import { withBasePath } from '../lib/basePath';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
targetPathname: string;
|
|
8
|
+
targetTitle: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const { targetPathname, targetTitle } = Astro.props;
|
|
12
|
+
const { labels, lang } = projectConfig.locale;
|
|
13
|
+
const targetHref = withBasePath(projectConfig.site.basePath, targetPathname);
|
|
14
|
+
const canonicalUrl = getAbsolutePageUrl(projectConfig.site.url, targetPathname);
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
<!doctype html>
|
|
18
|
+
<html lang={lang} data-pagefind-ignore="all">
|
|
19
|
+
<head>
|
|
20
|
+
<meta charset="utf-8" />
|
|
21
|
+
<meta name="viewport" content="width=device-width" />
|
|
22
|
+
<meta name="robots" content="noindex, follow" />
|
|
23
|
+
<meta http-equiv="refresh" content={`0; url=${targetHref}`} />
|
|
24
|
+
<link rel="canonical" href={canonicalUrl} />
|
|
25
|
+
<title>{labels.pageMoved}: {targetTitle}</title>
|
|
26
|
+
<style is:inline>
|
|
27
|
+
:root {
|
|
28
|
+
color-scheme: light dark;
|
|
29
|
+
font-family: system-ui, sans-serif;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
body {
|
|
33
|
+
margin: 0;
|
|
34
|
+
padding: 2rem;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
main {
|
|
38
|
+
max-width: 42rem;
|
|
39
|
+
margin-inline: auto;
|
|
40
|
+
}
|
|
41
|
+
</style>
|
|
42
|
+
</head>
|
|
43
|
+
<body>
|
|
44
|
+
<main>
|
|
45
|
+
<h1>{labels.pageMoved}</h1>
|
|
46
|
+
<p>{labels.pageMovedText} <a href={targetHref}>{targetTitle}</a>.</p>
|
|
47
|
+
</main>
|
|
48
|
+
</body>
|
|
49
|
+
</html>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
import projectConfig from '../../scripts/lib/project-config.mjs';
|
|
3
|
+
import type { HeadingNavigation } from '../lib/sectionContent';
|
|
4
|
+
import PageContentsLinks from './PageContentsLinks.astro';
|
|
5
|
+
|
|
6
|
+
interface Props {
|
|
7
|
+
headings: HeadingNavigation[];
|
|
8
|
+
pageTitle: string;
|
|
9
|
+
variant: 'inline' | 'rail';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { headings, pageTitle, variant } = Astro.props;
|
|
13
|
+
const label = `${projectConfig.locale.labels.pageNavigation}: ${pageTitle}`;
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
<aside class={`page-contents-navigation page-contents-navigation-${variant}`}>
|
|
17
|
+
<nav aria-label={label}>
|
|
18
|
+
<p class="page-contents-navigation-label">{projectConfig.locale.labels.pageNavigation}</p>
|
|
19
|
+
<PageContentsLinks headings={headings} variant="desktop" />
|
|
20
|
+
</nav>
|
|
21
|
+
</aside>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { ArrowRight } from '@lucide/astro';
|
|
3
|
+
import projectConfig from '../../scripts/lib/project-config.mjs';
|
|
4
|
+
import { withBasePath } from '../lib/basePath';
|
|
5
|
+
|
|
6
|
+
type PageListItem = {
|
|
7
|
+
title: string;
|
|
8
|
+
pathname: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
pages: PageListItem[];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const { pages } = Astro.props;
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
<nav class="child-page-list" aria-label={projectConfig.locale.labels.navigationChildren}>
|
|
20
|
+
<ul>
|
|
21
|
+
{pages.map((page) => (
|
|
22
|
+
<li>
|
|
23
|
+
<a href={withBasePath(projectConfig.site.basePath, page.pathname)}>
|
|
24
|
+
<span class="child-page-list-copy">
|
|
25
|
+
<strong>{page.title}</strong>
|
|
26
|
+
{page.description && <span>{page.description}</span>}
|
|
27
|
+
</span>
|
|
28
|
+
<ArrowRight aria-hidden="true" />
|
|
29
|
+
</a>
|
|
30
|
+
</li>
|
|
31
|
+
))}
|
|
32
|
+
</ul>
|
|
33
|
+
</nav>
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { ArrowLeft, ArrowRight } from '@lucide/astro';
|
|
3
|
+
import projectConfig from '../../scripts/lib/project-config.mjs';
|
|
4
|
+
import { withBasePath } from '../lib/basePath';
|
|
5
|
+
import type { SitePage } from '../lib/sitePages';
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
previous: SitePage | null;
|
|
9
|
+
next: SitePage | null;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const { previous, next } = Astro.props;
|
|
13
|
+
const pageHref = (page: SitePage) => withBasePath(projectConfig.site.basePath, page.pathname);
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
{(previous || next) && (
|
|
17
|
+
<nav class="page-sequence-navigation" aria-label={projectConfig.locale.labels.pageSequence}>
|
|
18
|
+
{previous ? (
|
|
19
|
+
<a class="page-sequence-link page-sequence-previous" href={pageHref(previous)} rel="prev">
|
|
20
|
+
<ArrowLeft aria-hidden="true" />
|
|
21
|
+
<span>
|
|
22
|
+
<span class="page-sequence-direction">{projectConfig.locale.labels.previousPage}</span>
|
|
23
|
+
<span class="page-sequence-title">{previous.title}</span>
|
|
24
|
+
</span>
|
|
25
|
+
</a>
|
|
26
|
+
) : <span class="page-sequence-empty" aria-hidden="true"></span>}
|
|
27
|
+
{next ? (
|
|
28
|
+
<a class="page-sequence-link page-sequence-next" href={pageHref(next)} rel="next">
|
|
29
|
+
<span>
|
|
30
|
+
<span class="page-sequence-direction">{projectConfig.locale.labels.nextPage}</span>
|
|
31
|
+
<span class="page-sequence-title">{next.title}</span>
|
|
32
|
+
</span>
|
|
33
|
+
<ArrowRight aria-hidden="true" />
|
|
34
|
+
</a>
|
|
35
|
+
) : <span class="page-sequence-empty" aria-hidden="true"></span>}
|
|
36
|
+
</nav>
|
|
37
|
+
)}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { getCollection } from 'astro:content';
|
|
3
|
+
import { resolveNavigationModel } from '../../scripts/lib/navigation-model.mjs';
|
|
4
|
+
import { getTextWidthCssValue, resolveFrameColors, resolvePagePresentation } from '../../scripts/lib/presentation.mjs';
|
|
5
|
+
import { projectConfig, resolveThemeVisualConfig } from '../../scripts/lib/project-config.mjs';
|
|
6
|
+
import SiteBanners from './SiteBanners.astro';
|
|
7
|
+
import SiteNavigation from './SiteNavigation.astro';
|
|
8
|
+
import SiteSection from './SiteSection.astro';
|
|
9
|
+
import BaseLayout from '../layouts/BaseLayout.astro';
|
|
10
|
+
import type { ResolvedSection } from '../lib/sectionContent';
|
|
11
|
+
import { flattenSiteNavigationTree, getListedSiteNavigationTree } from '../lib/siteNavigation';
|
|
12
|
+
import { getSiteModel, type SitePage } from '../lib/sitePages';
|
|
13
|
+
import { getLogoAsset } from '../lib/sitePublicAssets';
|
|
14
|
+
import { getToday, isVisibleToday } from '../lib/visibility';
|
|
15
|
+
import { withBasePath } from '../lib/basePath';
|
|
16
|
+
|
|
17
|
+
const siteDirectory = (process.env.NORNA_SITE_DIR ?? 'site').trim();
|
|
18
|
+
const siteModel = await getSiteModel(await getCollection('site'));
|
|
19
|
+
const { nodes: siteNodes, pages: sitePages } = siteModel;
|
|
20
|
+
const homePage = sitePages.find((page) => page.isHome);
|
|
21
|
+
if (!homePage) throw new Error('The required homepage is missing from the site page model.');
|
|
22
|
+
|
|
23
|
+
const [siteTheme] = await getCollection('theme');
|
|
24
|
+
if (!siteTheme) throw new Error('site/theme.yaml is required. Add a root theme before building the site.');
|
|
25
|
+
const [sitewide] = await getCollection('sitewide');
|
|
26
|
+
const sitewideData = sitewide?.data ?? {};
|
|
27
|
+
const siteThemeData = siteTheme.data;
|
|
28
|
+
const themeLabel = `${siteDirectory || 'site'}/${siteTheme.id}`;
|
|
29
|
+
const visualTheme = resolveThemeVisualConfig(siteThemeData, themeLabel);
|
|
30
|
+
const getPageContent = (page: SitePage) => page.markdownDocument.navigationHeadings.map((heading) => ({ ...heading }));
|
|
31
|
+
const siteNavigation = siteNodes.map((node) => {
|
|
32
|
+
const headings = node.kind === 'page' ? getPageContent(node) : [];
|
|
33
|
+
return {
|
|
34
|
+
node,
|
|
35
|
+
headings,
|
|
36
|
+
sections: headings
|
|
37
|
+
.filter((heading) => heading.depth === 2)
|
|
38
|
+
.map(({ id, title }) => ({ id, title })),
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
const renderedNavigationEntries = flattenSiteNavigationTree(getListedSiteNavigationTree(siteNavigation));
|
|
42
|
+
const navigationModel = resolveNavigationModel({
|
|
43
|
+
mode: projectConfig.navigation.mode,
|
|
44
|
+
nodes: renderedNavigationEntries.map(({ node, headings }) => ({
|
|
45
|
+
depth: Math.max(1, node.depth),
|
|
46
|
+
headings,
|
|
47
|
+
isHome: node.isHome,
|
|
48
|
+
kind: node.kind,
|
|
49
|
+
listed: node.navigation.listed,
|
|
50
|
+
pagePath: node.pagePath,
|
|
51
|
+
})),
|
|
52
|
+
});
|
|
53
|
+
const pagePresentation = resolvePagePresentation(siteThemeData, themeLabel, {
|
|
54
|
+
navigationMode: navigationModel.mode,
|
|
55
|
+
});
|
|
56
|
+
const pageTextWidth = getTextWidthCssValue(pagePresentation.typography.values.body.width);
|
|
57
|
+
const frameColors = resolveFrameColors({ theme: siteThemeData, sourceLabel: themeLabel });
|
|
58
|
+
const logoAsset = getLogoAsset();
|
|
59
|
+
const logo = logoAsset
|
|
60
|
+
? {
|
|
61
|
+
src: logoAsset.href,
|
|
62
|
+
alt: homePage.title,
|
|
63
|
+
height: sitewideData.logo?.height,
|
|
64
|
+
}
|
|
65
|
+
: undefined;
|
|
66
|
+
const today = getToday();
|
|
67
|
+
const siteBanners = (sitewideData.banners ?? []).filter((banner) => isVisibleToday(banner.visible, today));
|
|
68
|
+
const labels = projectConfig.locale.labels;
|
|
69
|
+
const searchSection: ResolvedSection = {
|
|
70
|
+
id: null,
|
|
71
|
+
title: labels.search,
|
|
72
|
+
titleHtml: labels.search,
|
|
73
|
+
headingLevel: 1,
|
|
74
|
+
contentBlocks: [{
|
|
75
|
+
type: 'html',
|
|
76
|
+
html: `<p>${labels.searchDescription}</p><div class="norna-search" id="norna-search"><p data-search-status>${labels.searchLoading}</p><noscript><p>${labels.searchNoScript}</p></noscript></div>`,
|
|
77
|
+
}],
|
|
78
|
+
};
|
|
79
|
+
const hasNavigationRailFrame = navigationModel.requestedMode === 'tree'
|
|
80
|
+
|| (
|
|
81
|
+
navigationModel.requestedMode === 'automatic'
|
|
82
|
+
&& (navigationModel.hasNestedPages || navigationModel.hasCategories)
|
|
83
|
+
);
|
|
84
|
+
const bundlePath = withBasePath(projectConfig.site.basePath, '/pagefind/');
|
|
85
|
+
const searchScript = `(() => {
|
|
86
|
+
const root = document.querySelector('#norna-search');
|
|
87
|
+
const status = root?.querySelector('[data-search-status]');
|
|
88
|
+
if (!root) return;
|
|
89
|
+
const script = document.createElement('script');
|
|
90
|
+
script.src = ${JSON.stringify(`${bundlePath}pagefind-ui.js`)};
|
|
91
|
+
script.onload = () => {
|
|
92
|
+
status?.remove();
|
|
93
|
+
new window.PagefindUI({
|
|
94
|
+
element: '#norna-search',
|
|
95
|
+
bundlePath: ${JSON.stringify(bundlePath)},
|
|
96
|
+
baseUrl: ${JSON.stringify(projectConfig.site.basePath)},
|
|
97
|
+
showImages: false,
|
|
98
|
+
showSubResults: true
|
|
99
|
+
});
|
|
100
|
+
const labelInput = () => {
|
|
101
|
+
const input = root.querySelector('.pagefind-ui__search-input');
|
|
102
|
+
if (!input) return false;
|
|
103
|
+
input.setAttribute('aria-label', ${JSON.stringify(labels.search)});
|
|
104
|
+
return true;
|
|
105
|
+
};
|
|
106
|
+
if (!labelInput()) {
|
|
107
|
+
const observer = new MutationObserver(() => {
|
|
108
|
+
if (labelInput()) observer.disconnect();
|
|
109
|
+
});
|
|
110
|
+
observer.observe(root, { childList: true, subtree: true });
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
script.onerror = () => {
|
|
114
|
+
if (status) status.textContent = ${JSON.stringify(labels.searchUnavailable)};
|
|
115
|
+
};
|
|
116
|
+
document.head.append(script);
|
|
117
|
+
})();`;
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
<BaseLayout
|
|
121
|
+
title={labels.search}
|
|
122
|
+
description={labels.searchDescription}
|
|
123
|
+
presentation={pagePresentation}
|
|
124
|
+
visualTheme={visualTheme}
|
|
125
|
+
frameColors={frameColors}
|
|
126
|
+
pathname="/search/"
|
|
127
|
+
footer={sitewideData.footer}
|
|
128
|
+
indexable={false}
|
|
129
|
+
>
|
|
130
|
+
<link slot="head" rel="stylesheet" href={`${bundlePath}pagefind-ui.css`} />
|
|
131
|
+
<SiteNavigation
|
|
132
|
+
logo={logo}
|
|
133
|
+
navigationEntries={siteNavigation}
|
|
134
|
+
navigationMode={navigationModel.mode}
|
|
135
|
+
showTopNavigationSubmenus={navigationModel.requestedMode !== 'automatic' && navigationModel.mode !== 'tree'}
|
|
136
|
+
hasNavigationRailFrame={hasNavigationRailFrame}
|
|
137
|
+
frameColors={frameColors}
|
|
138
|
+
readerPreferences={pagePresentation.readerPreferences}
|
|
139
|
+
searchPage
|
|
140
|
+
/>
|
|
141
|
+
{siteBanners.length > 0 && <SiteBanners banners={siteBanners} />}
|
|
142
|
+
<main class="site-content search-page-content" id="main-content" style={`--page-text-width: ${pageTextWidth}`}>
|
|
143
|
+
<SiteSection
|
|
144
|
+
section={searchSection}
|
|
145
|
+
pagePresentation={pagePresentation}
|
|
146
|
+
visualTheme={visualTheme}
|
|
147
|
+
sectionIndex={0}
|
|
148
|
+
/>
|
|
149
|
+
</main>
|
|
150
|
+
<script is:inline set:html={searchScript} />
|
|
151
|
+
</BaseLayout>
|