@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
package/src/lib/sitePages.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { sitePagesDir, sitePagesLabel } from '../../scripts/lib/site-paths.mjs';
|
|
|
10
10
|
type SiteEntry = CollectionEntry<'site'>;
|
|
11
11
|
|
|
12
12
|
type SiteNodeBase = {
|
|
13
|
+
aliases: string[];
|
|
13
14
|
kind: 'category' | 'page';
|
|
14
15
|
isHome: boolean;
|
|
15
16
|
navigation: {
|
|
@@ -106,6 +107,7 @@ const createSitePage = async (entry: SiteEntry): Promise<SitePage> => {
|
|
|
106
107
|
const navigation = entry.data.navigation ?? {};
|
|
107
108
|
|
|
108
109
|
return {
|
|
110
|
+
aliases: entry.data.page?.aliases ?? [],
|
|
109
111
|
kind: 'page',
|
|
110
112
|
entry,
|
|
111
113
|
isHome,
|
|
@@ -131,6 +133,7 @@ const createSitePage = async (entry: SiteEntry): Promise<SitePage> => {
|
|
|
131
133
|
};
|
|
132
134
|
|
|
133
135
|
const createSiteCategory = (category: Awaited<ReturnType<typeof getSiteStructure>>['categories'][number]): SiteCategory => ({
|
|
136
|
+
aliases: [],
|
|
134
137
|
kind: 'category',
|
|
135
138
|
isHome: false,
|
|
136
139
|
depth: category.depth,
|
|
@@ -226,5 +229,3 @@ export const getSiteModel = async (entries: SiteEntry[]) => {
|
|
|
226
229
|
warnings: structure.warnings,
|
|
227
230
|
};
|
|
228
231
|
};
|
|
229
|
-
|
|
230
|
-
export const getSitePages = async (entries: SiteEntry[]) => (await getSiteModel(entries)).pages;
|
|
@@ -4,6 +4,7 @@ import { sitePublicDir } from '../../scripts/lib/site-paths.mjs';
|
|
|
4
4
|
import { browserIconAssetDefinitions } from '../../scripts/lib/public-asset-conventions.mjs';
|
|
5
5
|
import projectConfig from '../../scripts/lib/project-config.mjs';
|
|
6
6
|
export { getLogoAsset } from '../../scripts/lib/logo-assets.mjs';
|
|
7
|
+
export { getSocialImageAsset } from '../../scripts/lib/social-image-assets.mjs';
|
|
7
8
|
import { withBasePath } from './basePath';
|
|
8
9
|
|
|
9
10
|
type IconLink = {
|
|
@@ -0,0 +1,110 @@
|
|
|
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 '../components/SiteBanners.astro';
|
|
7
|
+
import SiteNavigation from '../components/SiteNavigation.astro';
|
|
8
|
+
import SiteSection from '../components/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
|
+
|
|
16
|
+
const siteDirectory = (process.env.NORNA_SITE_DIR ?? 'site').trim();
|
|
17
|
+
const siteModel = await getSiteModel(await getCollection('site'));
|
|
18
|
+
const { nodes: siteNodes, pages: sitePages } = siteModel;
|
|
19
|
+
const homePage = sitePages.find((page) => page.isHome);
|
|
20
|
+
if (!homePage) throw new Error('The required homepage is missing from the site page model.');
|
|
21
|
+
|
|
22
|
+
const [siteTheme] = await getCollection('theme');
|
|
23
|
+
if (!siteTheme) throw new Error('site/theme.yaml is required. Add a root theme before building the site.');
|
|
24
|
+
const [sitewide] = await getCollection('sitewide');
|
|
25
|
+
const sitewideData = sitewide?.data ?? {};
|
|
26
|
+
const siteThemeData = siteTheme.data;
|
|
27
|
+
const themeLabel = `${siteDirectory || 'site'}/${siteTheme.id}`;
|
|
28
|
+
const visualTheme = resolveThemeVisualConfig(siteThemeData, themeLabel);
|
|
29
|
+
const getPageContent = (page: SitePage) => page.markdownDocument.navigationHeadings.map((heading) => ({ ...heading }));
|
|
30
|
+
const siteNavigation = siteNodes.map((node) => {
|
|
31
|
+
const headings = node.kind === 'page' ? getPageContent(node) : [];
|
|
32
|
+
return {
|
|
33
|
+
node,
|
|
34
|
+
headings,
|
|
35
|
+
sections: headings
|
|
36
|
+
.filter((heading) => heading.depth === 2)
|
|
37
|
+
.map(({ id, title }) => ({ id, title })),
|
|
38
|
+
};
|
|
39
|
+
});
|
|
40
|
+
const renderedNavigationEntries = flattenSiteNavigationTree(getListedSiteNavigationTree(siteNavigation));
|
|
41
|
+
const navigationModel = resolveNavigationModel({
|
|
42
|
+
mode: projectConfig.navigation.mode,
|
|
43
|
+
nodes: renderedNavigationEntries.map(({ node, headings }) => ({
|
|
44
|
+
depth: Math.max(1, node.depth),
|
|
45
|
+
headings,
|
|
46
|
+
isHome: node.isHome,
|
|
47
|
+
kind: node.kind,
|
|
48
|
+
listed: node.navigation.listed,
|
|
49
|
+
pagePath: node.pagePath,
|
|
50
|
+
})),
|
|
51
|
+
});
|
|
52
|
+
const pagePresentation = resolvePagePresentation(siteThemeData, themeLabel, {
|
|
53
|
+
navigationMode: navigationModel.mode,
|
|
54
|
+
});
|
|
55
|
+
const pageTextWidth = getTextWidthCssValue(pagePresentation.typography.values.body.width);
|
|
56
|
+
const frameColors = resolveFrameColors({ theme: siteThemeData, sourceLabel: themeLabel });
|
|
57
|
+
const logoAsset = getLogoAsset();
|
|
58
|
+
const logo = logoAsset
|
|
59
|
+
? {
|
|
60
|
+
src: logoAsset.href,
|
|
61
|
+
alt: homePage.title,
|
|
62
|
+
height: sitewideData.logo?.height,
|
|
63
|
+
}
|
|
64
|
+
: undefined;
|
|
65
|
+
const today = getToday();
|
|
66
|
+
const siteBanners = (sitewideData.banners ?? []).filter((banner) => isVisibleToday(banner.visible, today));
|
|
67
|
+
const notFoundSection: ResolvedSection = {
|
|
68
|
+
id: null,
|
|
69
|
+
title: projectConfig.locale.labels.notFound,
|
|
70
|
+
titleHtml: projectConfig.locale.labels.notFound,
|
|
71
|
+
headingLevel: 1,
|
|
72
|
+
contentBlocks: [{
|
|
73
|
+
type: 'html',
|
|
74
|
+
html: `<p>${projectConfig.locale.labels.notFoundText}</p><p><a href="${projectConfig.site.basePath}">${projectConfig.locale.labels.returnHome}</a></p>`,
|
|
75
|
+
}],
|
|
76
|
+
};
|
|
77
|
+
const hasNavigationRailFrame = navigationModel.requestedMode === 'tree'
|
|
78
|
+
|| (
|
|
79
|
+
navigationModel.requestedMode === 'automatic'
|
|
80
|
+
&& (navigationModel.hasNestedPages || navigationModel.hasCategories)
|
|
81
|
+
);
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
<BaseLayout
|
|
85
|
+
title={projectConfig.locale.labels.notFound}
|
|
86
|
+
presentation={pagePresentation}
|
|
87
|
+
visualTheme={visualTheme}
|
|
88
|
+
frameColors={frameColors}
|
|
89
|
+
footer={sitewideData.footer}
|
|
90
|
+
indexable={false}
|
|
91
|
+
>
|
|
92
|
+
<SiteNavigation
|
|
93
|
+
logo={logo}
|
|
94
|
+
navigationEntries={siteNavigation}
|
|
95
|
+
navigationMode={navigationModel.mode}
|
|
96
|
+
showTopNavigationSubmenus={navigationModel.requestedMode !== 'automatic' && navigationModel.mode !== 'tree'}
|
|
97
|
+
hasNavigationRailFrame={hasNavigationRailFrame}
|
|
98
|
+
frameColors={frameColors}
|
|
99
|
+
readerPreferences={pagePresentation.readerPreferences}
|
|
100
|
+
/>
|
|
101
|
+
{siteBanners.length > 0 && <SiteBanners banners={siteBanners} />}
|
|
102
|
+
<main class="site-content" id="main-content" style={`--page-text-width: ${pageTextWidth}`}>
|
|
103
|
+
<SiteSection
|
|
104
|
+
section={notFoundSection}
|
|
105
|
+
pagePresentation={pagePresentation}
|
|
106
|
+
visualTheme={visualTheme}
|
|
107
|
+
sectionIndex={0}
|
|
108
|
+
/>
|
|
109
|
+
</main>
|
|
110
|
+
</BaseLayout>
|
|
@@ -1,31 +1,68 @@
|
|
|
1
1
|
---
|
|
2
2
|
import { getCollection } from 'astro:content';
|
|
3
|
+
import { assertPageAliasModel, createPageAliasModel } from '../../scripts/lib/page-aliases.mjs';
|
|
4
|
+
import { getGeneratedSiteRoutes } from '../../scripts/lib/page-aliases.mjs';
|
|
5
|
+
import { getSitePublicFiles } from '../../scripts/lib/site-link-graph.mjs';
|
|
6
|
+
import projectConfig from '../../scripts/lib/project-config.mjs';
|
|
7
|
+
import PageAliasRedirect from '../components/PageAliasRedirect.astro';
|
|
8
|
+
import SearchPage from '../components/SearchPage.astro';
|
|
3
9
|
import SitePage from '../components/SitePage.astro';
|
|
4
|
-
import {
|
|
10
|
+
import { getSiteModel, getSitePage, isHomePageEntry } from '../lib/sitePages';
|
|
5
11
|
|
|
6
12
|
export async function getStaticPaths() {
|
|
7
13
|
const entries = await getCollection('site');
|
|
8
|
-
const
|
|
14
|
+
const siteModel = await getSiteModel(entries);
|
|
15
|
+
const aliasModel = assertPageAliasModel(createPageAliasModel({
|
|
16
|
+
categories: siteModel.categories,
|
|
17
|
+
pages: siteModel.pages,
|
|
18
|
+
publicFiles: await getSitePublicFiles(),
|
|
19
|
+
generatedRoutes: getGeneratedSiteRoutes({ searchEnabled: projectConfig.search.enabled }),
|
|
20
|
+
}));
|
|
21
|
+
const pagesByPathname = new Map(siteModel.pages.map((page) => [page.pathname, page]));
|
|
9
22
|
|
|
10
|
-
|
|
23
|
+
const pagePaths = siteModel.pages
|
|
11
24
|
.filter((page) => !page.isHome)
|
|
12
25
|
.map((page) => ({
|
|
13
26
|
params: { slug: page.pathSegment },
|
|
14
27
|
props: { entry: page.entry },
|
|
15
28
|
}));
|
|
29
|
+
const aliasPaths = aliasModel.aliases.map((alias) => {
|
|
30
|
+
const page = pagesByPathname.get(alias.targetPathname);
|
|
31
|
+
if (!page) throw new Error(`Page alias "${alias.pathname}" has no target page ${alias.targetPathname}.`);
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
params: { slug: alias.pathname.slice(1, -1) },
|
|
35
|
+
props: {
|
|
36
|
+
alias: {
|
|
37
|
+
targetPathname: page.pathname,
|
|
38
|
+
targetTitle: page.title,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const searchPath = projectConfig.search.enabled
|
|
45
|
+
? [{ params: { slug: 'search' }, props: { searchPage: true } }]
|
|
46
|
+
: [];
|
|
47
|
+
|
|
48
|
+
return [...pagePaths, ...aliasPaths, ...searchPath];
|
|
16
49
|
}
|
|
17
50
|
|
|
18
|
-
const { entry } = Astro.props;
|
|
51
|
+
const { alias, entry, searchPage } = Astro.props;
|
|
19
52
|
|
|
20
|
-
if (!entry || isHomePageEntry(entry)) {
|
|
53
|
+
if (!searchPage && !alias && (!entry || isHomePageEntry(entry))) {
|
|
21
54
|
throw new Error('Page entry is missing.');
|
|
22
55
|
}
|
|
23
56
|
|
|
24
|
-
const currentPage = await getSitePage(entry);
|
|
57
|
+
const currentPage = entry ? await getSitePage(entry) : null;
|
|
25
58
|
|
|
26
|
-
if (!currentPage
|
|
59
|
+
if (!searchPage && !alias && !currentPage?.pathSegment) {
|
|
27
60
|
throw new Error(`Page "${entry.id}" needs a page id.`);
|
|
28
61
|
}
|
|
29
62
|
---
|
|
30
63
|
|
|
31
|
-
|
|
64
|
+
{searchPage
|
|
65
|
+
? <SearchPage />
|
|
66
|
+
: alias
|
|
67
|
+
? <PageAliasRedirect targetPathname={alias.targetPathname} targetTitle={alias.targetTitle} />
|
|
68
|
+
: <SitePage entry={entry} />}
|