@janga/norna 0.7.28 → 0.7.30
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 +8 -6
- package/astro.config.mjs +14 -2
- package/package.json +13 -9
- package/schemas/config.schema.json +80 -65
- package/schemas/content-frontmatter.schema.json +15 -8
- package/schemas/manifest.json +1 -1
- package/schemas/page-theme.schema.json +237 -237
- package/schemas/root-theme.schema.json +237 -237
- package/schemas/sitewide-content.schema.json +18 -18
- package/schemas/theme.schema.json +237 -237
- package/scripts/add-site-node.mjs +29 -100
- package/scripts/dev-local.mjs +34 -7
- package/scripts/generate-images.mjs +97 -127
- package/scripts/generate-search-index.mjs +3 -2
- package/scripts/lib/attachment-render.mjs +23 -0
- package/scripts/lib/dev-server-identity.mjs +30 -0
- package/scripts/lib/documentation-routes.json +63 -62
- package/scripts/lib/docusaurus-migration-audit.mjs +9 -9
- package/scripts/lib/editor-attachments.mjs +69 -0
- package/scripts/lib/editor-file-policy.mjs +2 -0
- package/scripts/lib/editor-language-service.mjs +21 -18
- package/scripts/lib/editor-page-addresses.mjs +5 -3
- package/scripts/lib/editor-page-placement.mjs +28 -98
- package/scripts/lib/editor-resource-actions.mjs +216 -0
- package/scripts/lib/editor-site-editing-tree.mjs +44 -9
- package/scripts/lib/editor-site-files.mjs +3 -2
- package/scripts/lib/editor-site-links.mjs +6 -4
- package/scripts/lib/editor-site-preview.mjs +149 -0
- package/scripts/lib/editor-site-tree.mjs +10 -3
- package/scripts/lib/editor-source-files.mjs +7 -1
- package/scripts/lib/image-dimensions.mjs +3 -72
- package/scripts/lib/logo-assets.mjs +2 -1
- package/scripts/lib/markdown-images.mjs +59 -0
- package/scripts/lib/markdown-links.mjs +29 -9
- package/scripts/lib/norna-markdown-blocks.mjs +0 -57
- package/scripts/lib/norna-markdown-render-plugin.mjs +29 -0
- package/scripts/lib/page-aliases.mjs +5 -0
- package/scripts/lib/page-attachments.mjs +59 -0
- package/scripts/lib/page-markdown.mjs +11 -7
- package/scripts/lib/page-model.mjs +8 -22
- package/scripts/lib/page-move-apply.mjs +6 -4
- package/scripts/lib/page-move-plan.mjs +37 -19
- package/scripts/lib/page-order-source.mjs +22 -0
- package/scripts/lib/page-order.mjs +45 -0
- package/scripts/lib/page-placement-cli.mjs +50 -0
- package/scripts/lib/project-config.mjs +3 -0
- package/scripts/lib/publication-paths.mjs +72 -0
- package/scripts/lib/schema-definitions.mjs +4 -0
- package/scripts/lib/schema-editor-metadata.mjs +4 -1
- package/scripts/lib/site-content.mjs +1 -1
- package/scripts/lib/site-link-graph.mjs +88 -18
- package/scripts/lib/site-node-create.mjs +47 -34
- package/scripts/lib/site-paths.mjs +2 -2
- package/scripts/lib/site-source-format.mjs +12 -0
- package/scripts/lib/site-structure.mjs +14 -13
- package/scripts/lib/site-upgrade.mjs +2 -0
- package/scripts/lib/social-image-assets.mjs +2 -1
- package/scripts/lib/source-files.mjs +1 -1
- package/scripts/lib/structured-blocks.mjs +3 -1
- package/scripts/move-site-page.mjs +46 -133
- package/scripts/sync-content-sections.mjs +8 -10
- package/scripts/sync-site-public.mjs +44 -66
- package/src/components/ContentBlocks.astro +2 -1
- package/src/components/ImageStack.astro +7 -5
- package/src/components/SearchPage.astro +2 -1
- package/src/components/SitePage.astro +1 -0
- package/src/components/SiteSection.astro +1 -0
- package/src/content.config.ts +1 -4
- package/src/lib/imagePresentation.ts +3 -0
- package/src/lib/markdownImages.ts +101 -0
- package/src/lib/sectionContent.ts +64 -19
- package/src/lib/sitePages.ts +4 -30
- package/src/lib/sitePublicAssets.ts +2 -1
- package/src/pages/[...slug].astro +2 -1
- package/src/styles/area-navigation.css +14 -5
- package/src/styles/content.css +6 -0
- package/src/styles/media.css +24 -0
- package/starters/basic/.github/workflows/deploy.yml +3 -6
- package/starters/basic/README.md +2 -0
- package/starters/basic/site/site-config/settings.yaml +3 -0
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { getPageImageSourceKey } from '../../scripts/lib/site-conventions.mjs';
|
|
2
|
+
import { readPageAttachments, resolveAttachment, attachmentHref } from '../../scripts/lib/page-attachments.mjs';
|
|
2
3
|
import { markdownToHtml } from 'satteri';
|
|
4
|
+
import { parseFragment } from 'parse5';
|
|
3
5
|
import { groupRenderedTabs } from '../../scripts/lib/content-tabs.mjs';
|
|
4
6
|
import projectConfig from '../../scripts/lib/project-config.mjs';
|
|
5
7
|
import {
|
|
@@ -8,12 +10,20 @@ import {
|
|
|
8
10
|
import { splitNornaRenderedBlocks } from '../../scripts/lib/norna-markdown-blocks.mjs';
|
|
9
11
|
import { applyBasePathToHtml } from './basePath';
|
|
10
12
|
import type { SitePage } from './sitePages';
|
|
13
|
+
import { splitMarkdownImages, renderNestedMarkdownImages } from './markdownImages';
|
|
14
|
+
import { nornaMarkdownImagePlugin } from '../../scripts/lib/norna-markdown-render-plugin.mjs';
|
|
15
|
+
import { rewriteAttachmentLinks } from '../../scripts/lib/attachment-render.mjs';
|
|
16
|
+
import { siteDir } from '../../scripts/lib/site-paths.mjs';
|
|
17
|
+
import { pathToFileURL } from 'node:url';
|
|
11
18
|
|
|
12
19
|
type ManagedImage = {
|
|
13
20
|
image: string;
|
|
14
21
|
src: string;
|
|
15
22
|
alt?: string;
|
|
16
23
|
caption?: string;
|
|
24
|
+
title?: string;
|
|
25
|
+
href?: string;
|
|
26
|
+
linkTitle?: string;
|
|
17
27
|
};
|
|
18
28
|
type CardListItem = {
|
|
19
29
|
title: string;
|
|
@@ -94,40 +104,73 @@ const applyH3HeadingIds = (
|
|
|
94
104
|
html: string,
|
|
95
105
|
headings: Array<{ id: string | null }>,
|
|
96
106
|
) => {
|
|
107
|
+
const renderHeading = (attributes: string, headingHtml: string, heading: { id: string | null }) => {
|
|
108
|
+
if (!heading.id) throw new Error('Rendered Markdown H3 could not be matched to its heading id.');
|
|
109
|
+
const cleanAttributes = attributes
|
|
110
|
+
.replace(/\s+data-norna-page-heading="true"/g, '')
|
|
111
|
+
.replace(/\s+id=(["']).*?\1/gi, '');
|
|
112
|
+
const cleanHeadingHtml = headingHtml.replace(explicitHeadingIdRegex, '').trim();
|
|
113
|
+
return `<h3${cleanAttributes} id="${heading.id}">${cleanHeadingHtml}</h3>`;
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// Content rendered before the heading marker was introduced may still be in
|
|
117
|
+
// Astro's content cache. Its top-level H3s can be identified from the HTML tree.
|
|
118
|
+
if (!html.includes('data-norna-page-heading=') && headings.length > 0) {
|
|
119
|
+
const fragment = parseFragment(html, { sourceCodeLocationInfo: true });
|
|
120
|
+
const renderedHeadings = fragment.childNodes.filter((node) => (
|
|
121
|
+
'tagName' in node && node.tagName === 'h3' && node.sourceCodeLocation?.startTag && node.sourceCodeLocation?.endTag
|
|
122
|
+
));
|
|
123
|
+
if (renderedHeadings.length !== headings.length) {
|
|
124
|
+
throw new Error('Markdown H3 headings could not be matched to the rendered page content.');
|
|
125
|
+
}
|
|
126
|
+
let result = html;
|
|
127
|
+
for (let index = renderedHeadings.length - 1; index >= 0; index -= 1) {
|
|
128
|
+
const location = renderedHeadings[index]?.sourceCodeLocation;
|
|
129
|
+
const heading = headings[index];
|
|
130
|
+
if (!location?.startTag || !location.endTag || !heading) {
|
|
131
|
+
throw new Error('Rendered Markdown H3 could not be matched to its heading id.');
|
|
132
|
+
}
|
|
133
|
+
const source = html.slice(location.startOffset, location.endOffset);
|
|
134
|
+
const replacement = source.replace(/<h3\b([^>]*)>([\s\S]*?)<\/h3>/i, (_match, attributes, headingHtml) =>
|
|
135
|
+
renderHeading(String(attributes), String(headingHtml), heading));
|
|
136
|
+
result = result.slice(0, location.startOffset) + replacement + result.slice(location.endOffset);
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
|
|
97
141
|
let headingIndex = 0;
|
|
98
|
-
const result = html.replace(/<h3\b([^>]*)>([\s\S]*?)<\/h3>/gi, (
|
|
142
|
+
const result = html.replace(/<h3\b([^>]*)>([\s\S]*?)<\/h3>/gi, (source, attributes, headingHtml) => {
|
|
143
|
+
if (!/\sdata-norna-page-heading="true"/.test(attributes)) return source;
|
|
99
144
|
const heading = headings[headingIndex];
|
|
100
145
|
headingIndex += 1;
|
|
101
|
-
if (!heading
|
|
102
|
-
|
|
103
|
-
const cleanAttributes = String(attributes).replace(/\s+id=(["']).*?\1/gi, '');
|
|
104
|
-
const cleanHeadingHtml = String(headingHtml).replace(explicitHeadingIdRegex, '').trim();
|
|
105
|
-
return `<h3${cleanAttributes} id="${heading.id}">${cleanHeadingHtml}</h3>`;
|
|
146
|
+
if (!heading) throw new Error('Rendered Markdown H3 could not be matched to its heading id.');
|
|
147
|
+
return renderHeading(String(attributes), String(headingHtml), heading);
|
|
106
148
|
});
|
|
107
|
-
|
|
108
149
|
if (headingIndex !== headings.length) {
|
|
109
150
|
throw new Error('Markdown H3 headings could not be matched to the rendered page content.');
|
|
110
151
|
}
|
|
111
|
-
|
|
112
152
|
return result;
|
|
113
153
|
};
|
|
114
154
|
|
|
115
155
|
const getImageSourceKey = (page: SitePage, image: string) => getPageImageSourceKey(page.pageDirectory, image);
|
|
116
156
|
|
|
117
|
-
const renderHeadingTitleHtml = async (headingSource: string) => {
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const result = await markdownToHtml(
|
|
157
|
+
const renderHeadingTitleHtml = async (headingSource: string, page: SitePage) => {
|
|
158
|
+
const references = [...page.markdownDocument.markdownImages, ...page.markdownDocument.links];
|
|
159
|
+
const definitions = [...new Set(references.map((reference) => reference.definitionSource).filter(Boolean))].join('\n');
|
|
160
|
+
// Keep the heading syntax: text such as "1. Plan" becomes a list if parsed alone.
|
|
161
|
+
const source = await rewriteAttachmentLinks(`${headingSource}\n\n${definitions}`, pathToFileURL(page.contentPath), siteDir);
|
|
162
|
+
const result = await markdownToHtml(source, {
|
|
163
|
+
mdastPlugins: [nornaMarkdownImagePlugin],
|
|
123
164
|
features: {
|
|
124
165
|
gfm: true,
|
|
125
166
|
smartPunctuation: true,
|
|
126
167
|
},
|
|
127
168
|
});
|
|
128
|
-
const
|
|
129
|
-
if (!
|
|
130
|
-
|
|
169
|
+
const heading = result.html.trim().match(/^<h([12])\b[^>]*>([\s\S]*)<\/h\1>$/i);
|
|
170
|
+
if (!heading) throw new Error('Markdown heading could not be rendered as inline content.');
|
|
171
|
+
const html = prepareContentHtml((heading[2] ?? '').replace(explicitHeadingIdRegex, '').trim());
|
|
172
|
+
const marked = splitMarkdownImages(html, page.pageDirectory).map((block) => block.type === 'html' ? block.html : '').join('');
|
|
173
|
+
return renderNestedMarkdownImages(marked, page.title, { desktop: 100, mobile: 100 });
|
|
131
174
|
};
|
|
132
175
|
|
|
133
176
|
const splitRenderedRegions = (html: string, regionCount: number) => {
|
|
@@ -159,11 +202,12 @@ const resolveContentBlocks = async (
|
|
|
159
202
|
|
|
160
203
|
for (const block of splitBlocks) {
|
|
161
204
|
if (block.type === 'html') {
|
|
162
|
-
resolvedBlocks.push(
|
|
205
|
+
resolvedBlocks.push(...splitMarkdownImages(prepareContentHtml(block.html), page.pageDirectory));
|
|
163
206
|
continue;
|
|
164
207
|
}
|
|
165
208
|
|
|
166
209
|
if (block.type === 'card-list') {
|
|
210
|
+
const { files } = await readPageAttachments(page);
|
|
167
211
|
resolvedBlocks.push({
|
|
168
212
|
type: 'card-list',
|
|
169
213
|
layout: block.layout,
|
|
@@ -172,6 +216,7 @@ const resolveContentBlocks = async (
|
|
|
172
216
|
width: block.width,
|
|
173
217
|
cards: block.cards.map((card: CardListItem) => ({
|
|
174
218
|
...card,
|
|
219
|
+
...(card.link && resolveAttachment(card.link, page.pathname, files) ? { link: attachmentHref(resolveAttachment(card.link, page.pathname, files), card.link) } : {}),
|
|
175
220
|
...(card.image ? { src: getImageSourceKey(page, card.image) } : {}),
|
|
176
221
|
})),
|
|
177
222
|
});
|
|
@@ -258,7 +303,7 @@ export const getSectionsContent = async (
|
|
|
258
303
|
sections.push({
|
|
259
304
|
id,
|
|
260
305
|
title,
|
|
261
|
-
titleHtml: await renderHeadingTitleHtml(bodySection.heading.source),
|
|
306
|
+
titleHtml: await renderHeadingTitleHtml(bodySection.heading.source, page),
|
|
262
307
|
headingLevel,
|
|
263
308
|
contentBlocks: groupRenderedTabs(
|
|
264
309
|
await resolveContentBlocks(content, bodySection.blocks, page, childPages),
|
package/src/lib/sitePages.ts
CHANGED
|
@@ -16,7 +16,7 @@ type SiteNodeBase = {
|
|
|
16
16
|
isHome: boolean;
|
|
17
17
|
navigation: {
|
|
18
18
|
listed: boolean;
|
|
19
|
-
order: number;
|
|
19
|
+
order: number | null;
|
|
20
20
|
};
|
|
21
21
|
depth: number;
|
|
22
22
|
pageDirectories: string[];
|
|
@@ -25,7 +25,6 @@ type SiteNodeBase = {
|
|
|
25
25
|
pageDirectory: string;
|
|
26
26
|
pageId: string;
|
|
27
27
|
pageIds: string[];
|
|
28
|
-
pageOrders: number[];
|
|
29
28
|
pagePath: string;
|
|
30
29
|
parentPagePath: string | null;
|
|
31
30
|
title: string;
|
|
@@ -64,16 +63,6 @@ const getPageMetadata = (entry: SiteEntry) => {
|
|
|
64
63
|
|
|
65
64
|
const getPagePathname = (pathSegment: string) => pathSegment ? `/${pathSegment}/` : '/';
|
|
66
65
|
|
|
67
|
-
const compareNumberPaths = (left: number[], right: number[]) => {
|
|
68
|
-
const sharedLength = Math.min(left.length, right.length);
|
|
69
|
-
for (let index = 0; index < sharedLength; index += 1) {
|
|
70
|
-
const difference = left[index] - right[index];
|
|
71
|
-
if (difference !== 0) return difference;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return left.length - right.length;
|
|
75
|
-
};
|
|
76
|
-
|
|
77
66
|
const readPageMarkdownDocument = async (entry: SiteEntry) => {
|
|
78
67
|
const pageDirectory = getPageDirectory(entry);
|
|
79
68
|
const contentRelativePath = [getPageSourceDirectory(pageDirectory), 'content.md'].filter(Boolean).join('/');
|
|
@@ -112,7 +101,7 @@ const createSitePage = async (entry: SiteEntry): Promise<SitePage> => {
|
|
|
112
101
|
depth: pageMetadata.depth,
|
|
113
102
|
navigation: {
|
|
114
103
|
listed: navigation.listed ?? true,
|
|
115
|
-
order: isHome ? 0 :
|
|
104
|
+
order: isHome ? 0 : navigation.order ?? null,
|
|
116
105
|
},
|
|
117
106
|
pathSegment,
|
|
118
107
|
pathname: getPagePathname(pathSegment),
|
|
@@ -120,7 +109,6 @@ const createSitePage = async (entry: SiteEntry): Promise<SitePage> => {
|
|
|
120
109
|
pageDirectories: pageMetadata.pageDirectories,
|
|
121
110
|
pageId,
|
|
122
111
|
pageIds: pageMetadata.pageIds,
|
|
123
|
-
pageOrders: pageMetadata.pageOrders,
|
|
124
112
|
pagePath,
|
|
125
113
|
parentPagePath: isHome ? null : pageMetadata.parentPagePath,
|
|
126
114
|
contentLabel,
|
|
@@ -148,7 +136,6 @@ const createSiteCategory = (category: Awaited<ReturnType<typeof getSiteStructure
|
|
|
148
136
|
pageDirectories: category.pageDirectories,
|
|
149
137
|
pageId: category.pageId,
|
|
150
138
|
pageIds: category.pageIds,
|
|
151
|
-
pageOrders: category.pageOrders,
|
|
152
139
|
pagePath: category.pagePath,
|
|
153
140
|
parentPagePath: category.parentPagePath,
|
|
154
141
|
title: category.label,
|
|
@@ -160,7 +147,6 @@ const createSitePages = async (entries: SiteEntry[]) => {
|
|
|
160
147
|
const pages = await Promise.all(entries.map(createSitePage));
|
|
161
148
|
const pathnames = new Map<string, SitePage>();
|
|
162
149
|
const siblingPageIds = new Map<string, SitePage>();
|
|
163
|
-
const siblingPageOrders = new Map<string, SitePage>();
|
|
164
150
|
|
|
165
151
|
for (const page of pages) {
|
|
166
152
|
if (page.isHome && page.navigation.listed === false) {
|
|
@@ -184,24 +170,12 @@ const createSitePages = async (entries: SiteEntry[]) => {
|
|
|
184
170
|
|
|
185
171
|
siblingPageIds.set(pageIdKey, page);
|
|
186
172
|
|
|
187
|
-
const pageOrderKey = `${siblingScope}\0${page.navigation.order}`;
|
|
188
|
-
const existingPageOrder = siblingPageOrders.get(pageOrderKey);
|
|
189
|
-
if (existingPageOrder) {
|
|
190
|
-
throw new Error(`Duplicate sibling page order "${String(page.navigation.order).padStart(3, '0')}" below "${page.parentPagePath ?? '/'}" from "${existingPageOrder.entry.id}" and "${page.entry.id}".`);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
siblingPageOrders.set(pageOrderKey, page);
|
|
194
173
|
}
|
|
195
174
|
if (!pages.some(({ isHome }) => isHome)) {
|
|
196
175
|
throw new Error(`Homepage content ${siteDirLabel}/root/content.md is missing.`);
|
|
197
176
|
}
|
|
198
177
|
|
|
199
|
-
return pages
|
|
200
|
-
left.isHome ? -1 : right.isHome ? 1 :
|
|
201
|
-
compareNumberPaths(left.pageOrders, right.pageOrders) ||
|
|
202
|
-
left.title.localeCompare(right.title, 'sv') ||
|
|
203
|
-
left.pathname.localeCompare(right.pathname, 'sv')
|
|
204
|
-
));
|
|
178
|
+
return pages;
|
|
205
179
|
};
|
|
206
180
|
|
|
207
181
|
export const getSiteModel = async (entries: SiteEntry[]) => {
|
|
@@ -228,7 +202,7 @@ export const getSiteModel = async (entries: SiteEntry[]) => {
|
|
|
228
202
|
categoryDestinations: assertCategoryDestinationModel(createCategoryDestinationModel(nodes)),
|
|
229
203
|
categories: nodes.filter((node): node is SiteCategory => node.kind === 'category'),
|
|
230
204
|
nodes,
|
|
231
|
-
pages,
|
|
205
|
+
pages: nodes.filter((node): node is SitePage => node.kind === 'page'),
|
|
232
206
|
warnings: structure.warnings,
|
|
233
207
|
};
|
|
234
208
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { publicFilePublication } from '../../scripts/lib/publication-paths.mjs';
|
|
1
2
|
import { existsSync } from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { sitePublicDir } from '../../scripts/lib/site-paths.mjs';
|
|
@@ -28,5 +29,5 @@ export const getIconLinks = () => faviconCandidates
|
|
|
28
29
|
.filter((candidate) => publicAssetExists(candidate.href))
|
|
29
30
|
.map((candidate) => ({
|
|
30
31
|
...candidate,
|
|
31
|
-
href: withBasePath(projectConfig.site.basePath, candidate.href),
|
|
32
|
+
href: withBasePath(projectConfig.site.basePath, publicFilePublication(candidate.href.slice(1)).pathname),
|
|
32
33
|
}));
|
|
@@ -3,6 +3,7 @@ import { getCollection } from 'astro:content';
|
|
|
3
3
|
import { assertPageAliasModel, createPageAliasModel } from '../../scripts/lib/page-aliases.mjs';
|
|
4
4
|
import { getGeneratedSiteRoutes } from '../../scripts/lib/page-aliases.mjs';
|
|
5
5
|
import { getSitePublicFiles } from '../../scripts/lib/site-link-graph.mjs';
|
|
6
|
+
import { sitePublicDir } from '../../scripts/lib/site-paths.mjs';
|
|
6
7
|
import projectConfig from '../../scripts/lib/project-config.mjs';
|
|
7
8
|
import PageAliasRedirect from '../components/PageAliasRedirect.astro';
|
|
8
9
|
import SearchPage from '../components/SearchPage.astro';
|
|
@@ -15,7 +16,7 @@ export async function getStaticPaths() {
|
|
|
15
16
|
const aliasModel = assertPageAliasModel(createPageAliasModel({
|
|
16
17
|
categories: siteModel.categories,
|
|
17
18
|
pages: siteModel.pages,
|
|
18
|
-
publicFiles: await getSitePublicFiles(),
|
|
19
|
+
publicFiles: await getSitePublicFiles(sitePublicDir, projectConfig),
|
|
19
20
|
generatedRoutes: getGeneratedSiteRoutes({ searchEnabled: projectConfig.search.enabled }),
|
|
20
21
|
}));
|
|
21
22
|
const pagesByPathname = new Map(siteModel.pages.map((page) => [page.pathname, page]));
|
|
@@ -217,10 +217,19 @@
|
|
|
217
217
|
--area-reading-rail-width: 16rem;
|
|
218
218
|
--area-reading-header-height: 4rem;
|
|
219
219
|
--site-top-anchor-offset: calc(var(--area-reading-header-height) + 1px + var(--nav-top-safety-inset));
|
|
220
|
-
--area-reading-outer-margin:
|
|
220
|
+
--area-reading-outer-margin: var(--content-gutter);
|
|
221
|
+
--area-reading-frame-width: min(calc(100% - (var(--content-gutter) * 2)), max(var(--page-width), 86rem));
|
|
221
222
|
--area-reading-content-start: calc(var(--area-reading-outer-margin) + var(--area-reading-rail-width) + var(--local-navigation-gap, clamp(1.5rem, 3vw, 3rem)));
|
|
222
223
|
}
|
|
223
224
|
|
|
225
|
+
/* Keep the reading frame near navigation on wide screens. Focus reading
|
|
226
|
+
hides navigation without moving the article to a different reading axis. */
|
|
227
|
+
:root[data-area-reading-frame] .site-page-layout-tree {
|
|
228
|
+
width: var(--area-reading-frame-width);
|
|
229
|
+
margin-inline: var(--area-reading-outer-margin) auto;
|
|
230
|
+
--tree-layout-outer-margin: max(var(--content-gutter), calc(100vw - var(--content-gutter) - max(var(--page-width), 86rem)));
|
|
231
|
+
}
|
|
232
|
+
|
|
224
233
|
:root[data-area-reading-frame] .site-page-layout-tree > .site-content {
|
|
225
234
|
grid-column: 2;
|
|
226
235
|
}
|
|
@@ -242,7 +251,8 @@
|
|
|
242
251
|
display: grid;
|
|
243
252
|
grid-template-columns: var(--area-reading-rail-width) minmax(0, 1fr);
|
|
244
253
|
column-gap: var(--local-navigation-gap, clamp(1.5rem, 3vw, 3rem));
|
|
245
|
-
width:
|
|
254
|
+
width: var(--area-reading-frame-width);
|
|
255
|
+
margin-inline: var(--area-reading-outer-margin) auto;
|
|
246
256
|
}
|
|
247
257
|
|
|
248
258
|
:root[data-area-reading-frame]:not([data-focus-reading='on']) .site-nav-leading {
|
|
@@ -263,7 +273,6 @@
|
|
|
263
273
|
|
|
264
274
|
:root[data-area-reading-frame] .site-brand-logo {
|
|
265
275
|
max-width: 100%;
|
|
266
|
-
max-height: 2.25rem;
|
|
267
276
|
object-fit: contain;
|
|
268
277
|
}
|
|
269
278
|
|
|
@@ -333,8 +342,8 @@
|
|
|
333
342
|
}
|
|
334
343
|
|
|
335
344
|
:root[data-area-reading-frame] .site-banners {
|
|
336
|
-
width: calc(
|
|
337
|
-
margin-inline: var(--area-reading-content-start)
|
|
345
|
+
width: calc(var(--area-reading-frame-width) - var(--area-reading-rail-width) - var(--local-navigation-gap, clamp(1.5rem, 3vw, 3rem)));
|
|
346
|
+
margin-inline: var(--area-reading-content-start) auto;
|
|
338
347
|
}
|
|
339
348
|
|
|
340
349
|
:root[data-area-reading-frame]:not([data-focus-reading='on']) .area-navigation-switcher {
|
package/src/styles/content.css
CHANGED
package/src/styles/media.css
CHANGED
|
@@ -337,6 +337,30 @@
|
|
|
337
337
|
object-fit: contain;
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
+
.managed-markdown-inline {
|
|
341
|
+
display: inline;
|
|
342
|
+
width: auto;
|
|
343
|
+
height: auto;
|
|
344
|
+
max-width: 100%;
|
|
345
|
+
vertical-align: middle;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
.section-markdown .managed-markdown-standalone {
|
|
349
|
+
margin-block: var(--space-image-item);
|
|
350
|
+
max-width: 100%;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.section-markdown :is(ul, ol):has(.managed-markdown-standalone) {
|
|
354
|
+
display: block;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.section-markdown li > .managed-markdown-standalone {
|
|
358
|
+
display: inline-grid;
|
|
359
|
+
width: 100%;
|
|
360
|
+
margin-block: 0;
|
|
361
|
+
vertical-align: top;
|
|
362
|
+
}
|
|
363
|
+
|
|
340
364
|
:root[data-image-inspector-open='true'] {
|
|
341
365
|
overflow: hidden;
|
|
342
366
|
}
|
|
@@ -27,16 +27,13 @@ jobs:
|
|
|
27
27
|
node-version: 24.18.0
|
|
28
28
|
cache: npm
|
|
29
29
|
|
|
30
|
-
- name: Install image tools
|
|
31
|
-
run: sudo apt-get update && sudo apt-get install -y imagemagick
|
|
32
|
-
|
|
33
30
|
- name: Restore generated images
|
|
34
31
|
uses: actions/cache@v4
|
|
35
32
|
with:
|
|
36
|
-
path: site/.norna/public/images
|
|
37
|
-
key: norna-images-${{ hashFiles('site/.norna/generated-images.json') }}
|
|
33
|
+
path: site/.norna/public/_norna/images
|
|
34
|
+
key: norna-images-v4-${{ hashFiles('site/.norna/generated-images.json') }}
|
|
38
35
|
restore-keys: |
|
|
39
|
-
norna-images-
|
|
36
|
+
norna-images-v4-
|
|
40
37
|
|
|
41
38
|
- name: Install dependencies
|
|
42
39
|
run: npm ci
|
package/starters/basic/README.md
CHANGED