@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
|
@@ -12,7 +12,7 @@ import { presentationPaletteNames } from './presentation-palette-metadata.mjs';
|
|
|
12
12
|
import { resolveThemeConfig } from './theme-presets.mjs';
|
|
13
13
|
|
|
14
14
|
export { presentationPaletteNames };
|
|
15
|
-
export const
|
|
15
|
+
export const appearanceNames = ['system', 'light', 'dark'];
|
|
16
16
|
const sectionBackgroundPatterns = Object.freeze({
|
|
17
17
|
uniform: ['base'],
|
|
18
18
|
alternating: ['base', 'soft'],
|
|
@@ -44,7 +44,6 @@ const createPaletteMode = ({ appearance, page, surfaces, frame, css }) => {
|
|
|
44
44
|
|
|
45
45
|
const presentationPalettes = Object.freeze({
|
|
46
46
|
'near-monochrome': Object.freeze({
|
|
47
|
-
defaultMode: 'dark',
|
|
48
47
|
modes: Object.freeze({
|
|
49
48
|
light: createPaletteMode({
|
|
50
49
|
appearance: 'light',
|
|
@@ -61,7 +60,6 @@ const presentationPalettes = Object.freeze({
|
|
|
61
60
|
soft: '#d8d8d3',
|
|
62
61
|
accent: '#4b4b46',
|
|
63
62
|
border: 'rgb(23 23 23 / 16%)',
|
|
64
|
-
navBackground: 'rgb(247 247 245 / 92%)',
|
|
65
63
|
navSeparator: 'rgb(23 23 23 / 24%)',
|
|
66
64
|
},
|
|
67
65
|
}),
|
|
@@ -80,14 +78,12 @@ const presentationPalettes = Object.freeze({
|
|
|
80
78
|
soft: '#302f2c',
|
|
81
79
|
accent: '#d8d2c8',
|
|
82
80
|
border: 'rgb(255 255 255 / 14%)',
|
|
83
|
-
navBackground: 'rgb(0 0 0 / 90%)',
|
|
84
81
|
navSeparator: 'rgb(255 255 255 / 28%)',
|
|
85
82
|
},
|
|
86
83
|
}),
|
|
87
84
|
}),
|
|
88
85
|
}),
|
|
89
86
|
'arctic-blue': Object.freeze({
|
|
90
|
-
defaultMode: 'light',
|
|
91
87
|
modes: Object.freeze({
|
|
92
88
|
light: createPaletteMode({
|
|
93
89
|
appearance: 'light',
|
|
@@ -104,7 +100,6 @@ const presentationPalettes = Object.freeze({
|
|
|
104
100
|
soft: '#d5dfe7',
|
|
105
101
|
accent: '#2a5877',
|
|
106
102
|
border: 'rgb(32 41 50 / 16%)',
|
|
107
|
-
navBackground: 'rgb(243 246 248 / 92%)',
|
|
108
103
|
navSeparator: 'rgb(32 41 50 / 24%)',
|
|
109
104
|
},
|
|
110
105
|
}),
|
|
@@ -123,14 +118,12 @@ const presentationPalettes = Object.freeze({
|
|
|
123
118
|
soft: '#354655',
|
|
124
119
|
accent: '#9bc5de',
|
|
125
120
|
border: 'rgb(237 243 247 / 16%)',
|
|
126
|
-
navBackground: 'rgb(23 32 42 / 92%)',
|
|
127
121
|
navSeparator: 'rgb(237 243 247 / 28%)',
|
|
128
122
|
},
|
|
129
123
|
}),
|
|
130
124
|
}),
|
|
131
125
|
}),
|
|
132
126
|
'mineral-teal': Object.freeze({
|
|
133
|
-
defaultMode: 'light',
|
|
134
127
|
modes: Object.freeze({
|
|
135
128
|
light: createPaletteMode({
|
|
136
129
|
appearance: 'light',
|
|
@@ -147,7 +140,6 @@ const presentationPalettes = Object.freeze({
|
|
|
147
140
|
soft: '#cfdfd9',
|
|
148
141
|
accent: '#155f5a',
|
|
149
142
|
border: 'rgb(32 54 49 / 17%)',
|
|
150
|
-
navBackground: 'rgb(238 244 241 / 92%)',
|
|
151
143
|
navSeparator: 'rgb(32 54 49 / 24%)',
|
|
152
144
|
},
|
|
153
145
|
}),
|
|
@@ -166,14 +158,12 @@ const presentationPalettes = Object.freeze({
|
|
|
166
158
|
soft: '#2c554f',
|
|
167
159
|
accent: '#8bd5ca',
|
|
168
160
|
border: 'rgb(237 245 241 / 16%)',
|
|
169
|
-
navBackground: 'rgb(16 45 44 / 92%)',
|
|
170
161
|
navSeparator: 'rgb(237 245 241 / 28%)',
|
|
171
162
|
},
|
|
172
163
|
}),
|
|
173
164
|
}),
|
|
174
165
|
}),
|
|
175
166
|
'soft-lavender': Object.freeze({
|
|
176
|
-
defaultMode: 'light',
|
|
177
167
|
modes: Object.freeze({
|
|
178
168
|
light: createPaletteMode({
|
|
179
169
|
appearance: 'light',
|
|
@@ -190,7 +180,6 @@ const presentationPalettes = Object.freeze({
|
|
|
190
180
|
soft: '#ded5e7',
|
|
191
181
|
accent: '#604678',
|
|
192
182
|
border: 'rgb(46 39 54 / 16%)',
|
|
193
|
-
navBackground: 'rgb(247 243 250 / 92%)',
|
|
194
183
|
navSeparator: 'rgb(46 39 54 / 24%)',
|
|
195
184
|
},
|
|
196
185
|
}),
|
|
@@ -209,14 +198,12 @@ const presentationPalettes = Object.freeze({
|
|
|
209
198
|
soft: '#4b3d57',
|
|
210
199
|
accent: '#d4b7e3',
|
|
211
200
|
border: 'rgb(244 237 248 / 16%)',
|
|
212
|
-
navBackground: 'rgb(37 31 46 / 92%)',
|
|
213
201
|
navSeparator: 'rgb(244 237 248 / 28%)',
|
|
214
202
|
},
|
|
215
203
|
}),
|
|
216
204
|
}),
|
|
217
205
|
}),
|
|
218
206
|
'warm-paper': Object.freeze({
|
|
219
|
-
defaultMode: 'light',
|
|
220
207
|
modes: Object.freeze({
|
|
221
208
|
light: createPaletteMode({
|
|
222
209
|
appearance: 'light',
|
|
@@ -233,7 +220,6 @@ const presentationPalettes = Object.freeze({
|
|
|
233
220
|
soft: '#d8d0c4',
|
|
234
221
|
accent: '#5f513c',
|
|
235
222
|
border: 'rgb(39 37 34 / 18%)',
|
|
236
|
-
navBackground: 'rgb(248 245 238 / 92%)',
|
|
237
223
|
navSeparator: 'rgb(39 37 34 / 24%)',
|
|
238
224
|
},
|
|
239
225
|
}),
|
|
@@ -252,14 +238,12 @@ const presentationPalettes = Object.freeze({
|
|
|
252
238
|
soft: '#3b342b',
|
|
253
239
|
accent: '#d5bea0',
|
|
254
240
|
border: 'rgb(243 237 226 / 16%)',
|
|
255
|
-
navBackground: 'rgb(27 25 22 / 92%)',
|
|
256
241
|
navSeparator: 'rgb(243 237 226 / 26%)',
|
|
257
242
|
},
|
|
258
243
|
}),
|
|
259
244
|
}),
|
|
260
245
|
}),
|
|
261
246
|
'retro-earth': Object.freeze({
|
|
262
|
-
defaultMode: 'light',
|
|
263
247
|
modes: Object.freeze({
|
|
264
248
|
light: createPaletteMode({
|
|
265
249
|
appearance: 'light',
|
|
@@ -276,7 +260,6 @@ const presentationPalettes = Object.freeze({
|
|
|
276
260
|
soft: '#deca9c',
|
|
277
261
|
accent: '#5e4514',
|
|
278
262
|
border: 'rgb(52 41 28 / 18%)',
|
|
279
|
-
navBackground: 'rgb(247 239 217 / 92%)',
|
|
280
263
|
navSeparator: 'rgb(52 41 28 / 25%)',
|
|
281
264
|
},
|
|
282
265
|
}),
|
|
@@ -295,14 +278,12 @@ const presentationPalettes = Object.freeze({
|
|
|
295
278
|
soft: '#54422a',
|
|
296
279
|
accent: '#e5c36c',
|
|
297
280
|
border: 'rgb(245 232 201 / 16%)',
|
|
298
|
-
navBackground: 'rgb(40 33 23 / 92%)',
|
|
299
281
|
navSeparator: 'rgb(245 232 201 / 28%)',
|
|
300
282
|
},
|
|
301
283
|
}),
|
|
302
284
|
}),
|
|
303
285
|
}),
|
|
304
286
|
'clay-rose': Object.freeze({
|
|
305
|
-
defaultMode: 'light',
|
|
306
287
|
modes: Object.freeze({
|
|
307
288
|
light: createPaletteMode({
|
|
308
289
|
appearance: 'light',
|
|
@@ -319,7 +300,6 @@ const presentationPalettes = Object.freeze({
|
|
|
319
300
|
soft: '#e2cfca',
|
|
320
301
|
accent: '#71313d',
|
|
321
302
|
border: 'rgb(60 41 41 / 17%)',
|
|
322
|
-
navBackground: 'rgb(247 239 237 / 92%)',
|
|
323
303
|
navSeparator: 'rgb(60 41 41 / 25%)',
|
|
324
304
|
},
|
|
325
305
|
}),
|
|
@@ -338,14 +318,12 @@ const presentationPalettes = Object.freeze({
|
|
|
338
318
|
soft: '#58404a',
|
|
339
319
|
accent: '#e6a7b0',
|
|
340
320
|
border: 'rgb(247 235 235 / 16%)',
|
|
341
|
-
navBackground: 'rgb(45 30 34 / 92%)',
|
|
342
321
|
navSeparator: 'rgb(247 235 235 / 28%)',
|
|
343
322
|
},
|
|
344
323
|
}),
|
|
345
324
|
}),
|
|
346
325
|
}),
|
|
347
326
|
'forest-moss': Object.freeze({
|
|
348
|
-
defaultMode: 'light',
|
|
349
327
|
modes: Object.freeze({
|
|
350
328
|
light: createPaletteMode({
|
|
351
329
|
appearance: 'light',
|
|
@@ -362,7 +340,6 @@ const presentationPalettes = Object.freeze({
|
|
|
362
340
|
soft: '#d5ddc5',
|
|
363
341
|
accent: '#324e25',
|
|
364
342
|
border: 'rgb(38 50 37 / 17%)',
|
|
365
|
-
navBackground: 'rgb(241 243 232 / 92%)',
|
|
366
343
|
navSeparator: 'rgb(38 50 37 / 24%)',
|
|
367
344
|
},
|
|
368
345
|
}),
|
|
@@ -381,14 +358,12 @@ const presentationPalettes = Object.freeze({
|
|
|
381
358
|
soft: '#394c37',
|
|
382
359
|
accent: '#add493',
|
|
383
360
|
border: 'rgb(238 242 230 / 16%)',
|
|
384
|
-
navBackground: 'rgb(23 34 24 / 92%)',
|
|
385
361
|
navSeparator: 'rgb(238 242 230 / 28%)',
|
|
386
362
|
},
|
|
387
363
|
}),
|
|
388
364
|
}),
|
|
389
365
|
}),
|
|
390
366
|
'vivid-night': Object.freeze({
|
|
391
|
-
defaultMode: 'dark',
|
|
392
367
|
modes: Object.freeze({
|
|
393
368
|
light: createPaletteMode({
|
|
394
369
|
appearance: 'light',
|
|
@@ -405,7 +380,6 @@ const presentationPalettes = Object.freeze({
|
|
|
405
380
|
soft: '#d5daec',
|
|
406
381
|
accent: '#3b568f',
|
|
407
382
|
border: 'rgb(37 36 58 / 16%)',
|
|
408
|
-
navBackground: 'rgb(242 244 251 / 92%)',
|
|
409
383
|
navSeparator: 'rgb(37 36 58 / 24%)',
|
|
410
384
|
},
|
|
411
385
|
}),
|
|
@@ -424,7 +398,6 @@ const presentationPalettes = Object.freeze({
|
|
|
424
398
|
soft: '#403a58',
|
|
425
399
|
accent: '#9fe4e8',
|
|
426
400
|
border: 'rgb(244 241 255 / 16%)',
|
|
427
|
-
navBackground: 'rgb(29 27 45 / 92%)',
|
|
428
401
|
navSeparator: 'rgb(244 241 255 / 28%)',
|
|
429
402
|
},
|
|
430
403
|
}),
|
|
@@ -493,11 +466,11 @@ export const resolveThemePresentation = (theme, sourceLabel = 'theme.yaml') => {
|
|
|
493
466
|
const normalizedTheme = resolveThemeConfig(theme, sourceLabel);
|
|
494
467
|
const paletteName = normalizedTheme.palette ?? 'near-monochrome';
|
|
495
468
|
const palette = getPresentationPalette(paletteName);
|
|
496
|
-
const
|
|
469
|
+
const appearance = normalizedTheme.appearance ?? {};
|
|
497
470
|
const readerControls = normalizedTheme.readerControls ?? {};
|
|
498
|
-
const
|
|
499
|
-
if (!
|
|
500
|
-
throw new Error(`
|
|
471
|
+
const defaultAppearance = appearance.default ?? 'system';
|
|
472
|
+
if (!appearanceNames.includes(defaultAppearance)) {
|
|
473
|
+
throw new Error(`appearance.default must be one of ${appearanceNames.join(', ')} in ${sourceLabel}.`);
|
|
501
474
|
}
|
|
502
475
|
const typography = resolveTypographyConfig(normalizedTheme.typography ?? defaultTypography, sourceLabel);
|
|
503
476
|
const textWidth = normalizedTheme.layout?.textWidth;
|
|
@@ -508,19 +481,19 @@ export const resolveThemePresentation = (theme, sourceLabel = 'theme.yaml') => {
|
|
|
508
481
|
|
|
509
482
|
return {
|
|
510
483
|
paletteName,
|
|
511
|
-
palette: palette.modes[
|
|
484
|
+
palette: palette.modes[defaultAppearance === 'dark' ? 'dark' : 'light'],
|
|
512
485
|
paletteModes: palette.modes,
|
|
513
|
-
|
|
514
|
-
default:
|
|
486
|
+
appearance: {
|
|
487
|
+
default: defaultAppearance,
|
|
515
488
|
},
|
|
516
489
|
readerPreferences: {
|
|
517
490
|
controls: {
|
|
518
|
-
appearance: readerControls.
|
|
491
|
+
appearance: readerControls.appearance === true,
|
|
519
492
|
readingWidth: true,
|
|
520
493
|
focusReading: readerControls.focusReading === true,
|
|
521
494
|
},
|
|
522
495
|
defaults: {
|
|
523
|
-
appearance:
|
|
496
|
+
appearance: defaultAppearance,
|
|
524
497
|
readingWidth: textWidth === 'narrow' ? 'narrow' : textWidth === 'wide' ? 'wide' : 'standard',
|
|
525
498
|
focusReading: 'off',
|
|
526
499
|
},
|
|
@@ -2,12 +2,18 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import {
|
|
3
3
|
validateConfigYamlStructure,
|
|
4
4
|
} from './site-content.mjs';
|
|
5
|
+
import {
|
|
6
|
+
defaultImageMaxAvailableHeightPercent,
|
|
7
|
+
defaultImagePresentation,
|
|
8
|
+
imagePresentationNames,
|
|
9
|
+
} from './image-presentation.mjs';
|
|
5
10
|
import { navigationModeNames } from './navigation-model.mjs';
|
|
6
11
|
import {
|
|
7
12
|
siteConfigLabel,
|
|
8
13
|
siteConfigPath,
|
|
9
14
|
siteThemeLabel,
|
|
10
15
|
} from './site-paths.mjs';
|
|
16
|
+
import { normalizeEditLinkBaseUrl } from './edit-source-link.mjs';
|
|
11
17
|
import { readThemeConfig } from './theme-config.mjs';
|
|
12
18
|
import { resolveThemeConfig } from './theme-presets.mjs';
|
|
13
19
|
import { parseYamlConfig } from './yaml-config.mjs';
|
|
@@ -194,12 +200,24 @@ const readSiteUrl = (config) => {
|
|
|
194
200
|
const localeLabels = Object.freeze({
|
|
195
201
|
en: Object.freeze({
|
|
196
202
|
breadcrumb: 'Breadcrumb',
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
203
|
+
appearance: 'Appearance',
|
|
204
|
+
appearanceDark: 'Dark',
|
|
205
|
+
appearanceLight: 'Light',
|
|
206
|
+
appearanceSystem: 'System',
|
|
207
|
+
codeCopied: 'Copied',
|
|
208
|
+
codeCopyFailed: 'Could not copy code',
|
|
209
|
+
copyCode: 'Copy code',
|
|
210
|
+
calloutCaution: 'Caution',
|
|
211
|
+
calloutDanger: 'Danger',
|
|
212
|
+
calloutImportant: 'Important',
|
|
213
|
+
calloutNote: 'Note',
|
|
214
|
+
calloutTip: 'Tip',
|
|
215
|
+
calloutWarning: 'Warning',
|
|
216
|
+
editSource: 'Edit this page',
|
|
201
217
|
displaySettings: 'Display',
|
|
202
218
|
focusReading: 'Focus reading',
|
|
219
|
+
footnoteBackReference: 'Back to reference {reference}',
|
|
220
|
+
footnotes: 'Footnotes',
|
|
203
221
|
readingWidth: 'Reading width',
|
|
204
222
|
readingWidthNarrow: 'Narrow',
|
|
205
223
|
readingWidthStandard: 'Standard',
|
|
@@ -209,24 +227,60 @@ const localeLabels = Object.freeze({
|
|
|
209
227
|
built: 'Built',
|
|
210
228
|
images: 'Images',
|
|
211
229
|
imageCarousel: 'image carousel',
|
|
230
|
+
navigationCollapsedAll: 'All navigation items collapsed.',
|
|
231
|
+
navigationCollapseAll: 'Collapse all',
|
|
232
|
+
navigationControls: 'Navigation tree controls',
|
|
212
233
|
navigationChildren: 'Child pages',
|
|
234
|
+
navigationExpandedAll: 'All navigation items expanded.',
|
|
235
|
+
navigationExpandAll: 'Expand all',
|
|
236
|
+
navigationFilter: 'Filter pages and groups',
|
|
237
|
+
navigationFilterEmpty: 'No matching pages or groups. The current page remains available.',
|
|
238
|
+
navigationFilterMatches: 'Matching navigation items: {count}',
|
|
239
|
+
navigationLocatedCurrent: 'Current page located.',
|
|
240
|
+
navigationLocateCurrent: 'Locate current page',
|
|
213
241
|
navigationMenu: 'Menu',
|
|
214
242
|
nextImage: 'Next image',
|
|
243
|
+
nextPage: 'Next page',
|
|
215
244
|
note: 'Note',
|
|
245
|
+
notFound: 'Page not found',
|
|
246
|
+
notFoundText: 'The requested page does not exist or may have moved.',
|
|
247
|
+
pageMoved: 'Page moved',
|
|
248
|
+
pageMovedText: 'This address now identifies',
|
|
216
249
|
pageNavigation: 'Page contents',
|
|
250
|
+
pageSequence: 'Page sequence',
|
|
251
|
+
pageSections: 'Sections',
|
|
217
252
|
previousImage: 'Previous image',
|
|
253
|
+
previousPage: 'Previous page',
|
|
254
|
+
returnHome: 'Go to the homepage',
|
|
255
|
+
search: 'Search',
|
|
256
|
+
searchDescription: 'Search the published content on this site.',
|
|
257
|
+
searchLoading: 'Loading search…',
|
|
258
|
+
searchNoScript: 'Search requires JavaScript. Use the page navigation when JavaScript is unavailable.',
|
|
259
|
+
searchUnavailable: 'Search is unavailable. During local work, run norna build:local to create or refresh the search index.',
|
|
218
260
|
siteBanners: 'Site notices',
|
|
219
261
|
siteNavigation: 'Pages',
|
|
220
262
|
skipToContent: 'Skip to content',
|
|
221
263
|
}),
|
|
222
264
|
sv: Object.freeze({
|
|
223
265
|
breadcrumb: 'Brödsmulor',
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
266
|
+
appearance: 'Utseende',
|
|
267
|
+
appearanceDark: 'Mörkt',
|
|
268
|
+
appearanceLight: 'Ljust',
|
|
269
|
+
appearanceSystem: 'System',
|
|
270
|
+
codeCopied: 'Kopierat',
|
|
271
|
+
codeCopyFailed: 'Kunde inte kopiera koden',
|
|
272
|
+
copyCode: 'Kopiera kod',
|
|
273
|
+
calloutCaution: 'Var försiktig',
|
|
274
|
+
calloutDanger: 'Fara',
|
|
275
|
+
calloutImportant: 'Viktigt',
|
|
276
|
+
calloutNote: 'Notera',
|
|
277
|
+
calloutTip: 'Tips',
|
|
278
|
+
calloutWarning: 'Varning',
|
|
279
|
+
editSource: 'Redigera den här sidan',
|
|
228
280
|
displaySettings: 'Visning',
|
|
229
281
|
focusReading: 'Fokuserad läsning',
|
|
282
|
+
footnoteBackReference: 'Tillbaka till referens {reference}',
|
|
283
|
+
footnotes: 'Fotnoter',
|
|
230
284
|
readingWidth: 'Textbredd',
|
|
231
285
|
readingWidthNarrow: 'Smal',
|
|
232
286
|
readingWidthStandard: 'Standard',
|
|
@@ -236,12 +290,36 @@ const localeLabels = Object.freeze({
|
|
|
236
290
|
built: 'Byggd',
|
|
237
291
|
images: 'Bilder',
|
|
238
292
|
imageCarousel: 'bildkarusell',
|
|
293
|
+
navigationCollapsedAll: 'Alla navigationsposter har fällts ihop.',
|
|
294
|
+
navigationCollapseAll: 'Fäll ihop alla',
|
|
295
|
+
navigationControls: 'Kontroller för navigationsträdet',
|
|
239
296
|
navigationChildren: 'Undersidor',
|
|
297
|
+
navigationExpandedAll: 'Alla navigationsposter har fällts ut.',
|
|
298
|
+
navigationExpandAll: 'Fäll ut alla',
|
|
299
|
+
navigationFilter: 'Filtrera sidor och grupper',
|
|
300
|
+
navigationFilterEmpty: 'Inga sidor eller grupper matchar. Den aktuella sidan är fortfarande tillgänglig.',
|
|
301
|
+
navigationFilterMatches: 'Matchande navigationsposter: {count}',
|
|
302
|
+
navigationLocatedCurrent: 'Den aktuella sidan har hittats.',
|
|
303
|
+
navigationLocateCurrent: 'Hitta aktuell sida',
|
|
240
304
|
navigationMenu: 'Meny',
|
|
241
305
|
nextImage: 'Nästa bild',
|
|
306
|
+
nextPage: 'Nästa sida',
|
|
242
307
|
note: 'Not',
|
|
308
|
+
notFound: 'Sidan hittades inte',
|
|
309
|
+
notFoundText: 'Den begärda sidan finns inte eller kan ha flyttats.',
|
|
310
|
+
pageMoved: 'Sidan har flyttats',
|
|
311
|
+
pageMovedText: 'Den här adressen identifierar nu',
|
|
243
312
|
pageNavigation: 'Sidinnehåll',
|
|
313
|
+
pageSequence: 'Sidföljd',
|
|
314
|
+
pageSections: 'Avsnitt',
|
|
244
315
|
previousImage: 'Föregående bild',
|
|
316
|
+
previousPage: 'Föregående sida',
|
|
317
|
+
returnHome: 'Gå till startsidan',
|
|
318
|
+
search: 'Sök',
|
|
319
|
+
searchDescription: 'Sök i det publicerade innehållet på webbplatsen.',
|
|
320
|
+
searchLoading: 'Laddar sökning…',
|
|
321
|
+
searchNoScript: 'Sökning kräver JavaScript. Använd sidnavigeringen när JavaScript inte är tillgängligt.',
|
|
322
|
+
searchUnavailable: 'Sökningen är inte tillgänglig. Kör norna build:local under lokalt arbete för att skapa eller uppdatera sökindexet.',
|
|
245
323
|
siteBanners: 'Meddelanden',
|
|
246
324
|
siteNavigation: 'Sidor',
|
|
247
325
|
skipToContent: 'Hoppa till innehållet',
|
|
@@ -365,6 +443,21 @@ export const resolveThemeVisualConfig = (theme, sourceLabel = siteThemeLabel) =>
|
|
|
365
443
|
const resolvedContentSpacing = readEnum(rawLayoutConfig, 'contentSpacing', 'layout', contentSpacingNames, 'normal', sourceLabel);
|
|
366
444
|
const resolvedLayoutSpacingDefaults = contentSpacingProfiles[resolvedContentSpacing];
|
|
367
445
|
const corners = readEnum(rawThemeConfig, 'corners', 'theme', ['square', 'rounded'], 'rounded', sourceLabel);
|
|
446
|
+
const imagePresentation = readEnum(
|
|
447
|
+
rawImagesConfig,
|
|
448
|
+
'presentation',
|
|
449
|
+
'images',
|
|
450
|
+
imagePresentationNames,
|
|
451
|
+
defaultImagePresentation,
|
|
452
|
+
sourceLabel,
|
|
453
|
+
);
|
|
454
|
+
if (imagePresentation === 'prose-aligned' && rawImagesConfig.maxAvailableHeightPercent !== undefined) {
|
|
455
|
+
throw new Error([
|
|
456
|
+
`images.maxAvailableHeightPercent cannot be used with images.presentation "prose-aligned" in ${sourceLabel}.`,
|
|
457
|
+
'Prose-aligned images follow available horizontal space instead of a viewport-height limit.',
|
|
458
|
+
'Remove images.maxAvailableHeightPercent or set images.presentation to "centered-fit".',
|
|
459
|
+
].join('\n'));
|
|
460
|
+
}
|
|
368
461
|
|
|
369
462
|
return Object.freeze({
|
|
370
463
|
layout: Object.freeze({
|
|
@@ -392,10 +485,18 @@ export const resolveThemeVisualConfig = (theme, sourceLabel = siteThemeLabel) =>
|
|
|
392
485
|
}),
|
|
393
486
|
}),
|
|
394
487
|
images: Object.freeze({
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
488
|
+
presentation: imagePresentation,
|
|
489
|
+
...(imagePresentation === 'centered-fit'
|
|
490
|
+
? {
|
|
491
|
+
maxAvailableHeightPercent: readResponsivePercent(
|
|
492
|
+
rawImagesConfig,
|
|
493
|
+
'maxAvailableHeightPercent',
|
|
494
|
+
'images',
|
|
495
|
+
defaultImageMaxAvailableHeightPercent,
|
|
496
|
+
sourceLabel,
|
|
497
|
+
),
|
|
498
|
+
}
|
|
499
|
+
: {}),
|
|
399
500
|
maxAvailableWidthPercent: readResponsivePercent(rawImagesConfig, 'maxAvailableWidthPercent', 'images', Object.freeze({
|
|
400
501
|
desktop: 100,
|
|
401
502
|
mobile: 100,
|
|
@@ -416,6 +517,13 @@ export const resolveThemeVisualConfig = (theme, sourceLabel = siteThemeLabel) =>
|
|
|
416
517
|
|
|
417
518
|
export const resolveNavigationConfig = (config, sourceLabel = siteConfigLabel) => {
|
|
418
519
|
const rawNavigation = assertObject(config.navigation ?? {}, 'navigation', sourceLabel);
|
|
520
|
+
if (Object.hasOwn(rawNavigation, 'sectionTracking')) {
|
|
521
|
+
throw new Error([
|
|
522
|
+
`navigation.sectionTracking is no longer supported in ${sourceLabel}.`,
|
|
523
|
+
'Section tracking is automatic on pages with a contents rail.',
|
|
524
|
+
'Remove navigation.sectionTracking.',
|
|
525
|
+
].join('\n'));
|
|
526
|
+
}
|
|
419
527
|
|
|
420
528
|
return Object.freeze({
|
|
421
529
|
mode: readEnum(
|
|
@@ -426,22 +534,41 @@ export const resolveNavigationConfig = (config, sourceLabel = siteConfigLabel) =
|
|
|
426
534
|
'automatic',
|
|
427
535
|
sourceLabel,
|
|
428
536
|
),
|
|
429
|
-
sectionTracking: readBoolean(
|
|
430
|
-
rawNavigation,
|
|
431
|
-
'sectionTracking',
|
|
432
|
-
'navigation',
|
|
433
|
-
false,
|
|
434
|
-
sourceLabel,
|
|
435
|
-
),
|
|
436
537
|
});
|
|
437
538
|
};
|
|
438
539
|
|
|
540
|
+
export const resolveEditLinkConfig = (config, sourceLabel = siteConfigLabel) => {
|
|
541
|
+
if (config.editLink === undefined) return null;
|
|
542
|
+
|
|
543
|
+
const rawEditLink = assertObject(config.editLink, 'editLink', sourceLabel);
|
|
544
|
+
if (!Object.hasOwn(rawEditLink, 'baseUrl')) {
|
|
545
|
+
throw new Error(`editLink.baseUrl is required when editLink is configured in ${sourceLabel}.`);
|
|
546
|
+
}
|
|
547
|
+
const unknownKeys = Object.keys(rawEditLink).filter((key) => key !== 'baseUrl');
|
|
548
|
+
if (unknownKeys.length > 0) {
|
|
549
|
+
throw new Error(`editLink.${unknownKeys[0]} is not a valid setting in ${sourceLabel}.`);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
try {
|
|
553
|
+
return Object.freeze({
|
|
554
|
+
baseUrl: normalizeEditLinkBaseUrl(rawEditLink.baseUrl, 'editLink.baseUrl'),
|
|
555
|
+
});
|
|
556
|
+
} catch (error) {
|
|
557
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
558
|
+
throw new Error(`${message.replace(/\.$/, '')} in ${sourceLabel}.`);
|
|
559
|
+
}
|
|
560
|
+
};
|
|
561
|
+
|
|
439
562
|
export const projectConfig = Object.freeze({
|
|
440
563
|
site: Object.freeze({
|
|
441
564
|
basePath: siteUrl.pathname,
|
|
442
565
|
url: siteUrl.href,
|
|
443
566
|
}),
|
|
444
567
|
...resolveThemeVisualConfig(rawTheme, siteThemeLabel),
|
|
568
|
+
editLink: resolveEditLinkConfig(rawConfig, siteConfigLabel),
|
|
569
|
+
search: Object.freeze({
|
|
570
|
+
enabled: readBoolean(rawConfig, 'search', 'config YAML', false),
|
|
571
|
+
}),
|
|
445
572
|
navigation: Object.freeze({
|
|
446
573
|
...resolveNavigationConfig(rawConfig, siteConfigLabel),
|
|
447
574
|
scrollBehavior: readEnum(
|
|
@@ -18,10 +18,22 @@ export const browserIconAssetFilenames = Object.freeze(
|
|
|
18
18
|
browserIconAssetDefinitions.map(({ filename }) => filename),
|
|
19
19
|
);
|
|
20
20
|
|
|
21
|
+
export const socialImageAssetDefinitions = Object.freeze([
|
|
22
|
+
Object.freeze({ filename: 'social-image.png', type: 'image/png' }),
|
|
23
|
+
Object.freeze({ filename: 'social-image.jpg', type: 'image/jpeg' }),
|
|
24
|
+
Object.freeze({ filename: 'social-image.jpeg', type: 'image/jpeg' }),
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
export const socialImageAssetFilenames = Object.freeze(
|
|
28
|
+
socialImageAssetDefinitions.map(({ filename }) => filename),
|
|
29
|
+
);
|
|
30
|
+
|
|
21
31
|
const logoExtensions = new Set(['.svg', '.png', '.jpg', '.jpeg']);
|
|
22
32
|
const faviconExtensions = new Set(['.svg', '.png', '.ico']);
|
|
33
|
+
const socialImageExtensions = new Set(['.png', '.jpg', '.jpeg']);
|
|
23
34
|
const exactLogoNames = new Set(logoAssetFilenames);
|
|
24
35
|
const exactBrowserIconNames = new Set(browserIconAssetFilenames);
|
|
36
|
+
const exactSocialImageNames = new Set(socialImageAssetFilenames);
|
|
25
37
|
|
|
26
38
|
export const isLogoAssetFilename = (filename) => exactLogoNames.has(filename);
|
|
27
39
|
|
|
@@ -60,6 +72,13 @@ const getSuspiciousAsset = (filename) => {
|
|
|
60
72
|
};
|
|
61
73
|
}
|
|
62
74
|
|
|
75
|
+
if (exactSocialImageNames.has(lowerFilename) && filename !== lowerFilename) {
|
|
76
|
+
return {
|
|
77
|
+
code: 'public-asset-case',
|
|
78
|
+
message: `Norna does not recognize "${filename}" as the social sharing image on case-sensitive file systems. Rename it to "${lowerFilename}".`,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
63
82
|
if (parsed.name === 'logo' && !logoExtensions.has(parsed.ext)) {
|
|
64
83
|
return {
|
|
65
84
|
code: 'unsupported-logo-file',
|
|
@@ -74,6 +93,13 @@ const getSuspiciousAsset = (filename) => {
|
|
|
74
93
|
};
|
|
75
94
|
}
|
|
76
95
|
|
|
96
|
+
if (parsed.name === 'social-image' && !socialImageExtensions.has(parsed.ext)) {
|
|
97
|
+
return {
|
|
98
|
+
code: 'unsupported-social-image-file',
|
|
99
|
+
message: `Norna does not recognize "${filename}" as the social sharing image. Use exactly one of ${socialImageAssetFilenames.join(', ')}.`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
77
103
|
if (/^favicon[-_.]/.test(lowerFilename) && !exactBrowserIconNames.has(lowerFilename)) {
|
|
78
104
|
return {
|
|
79
105
|
code: 'unrecognized-favicon-file',
|
|
@@ -95,6 +121,13 @@ const getSuspiciousAsset = (filename) => {
|
|
|
95
121
|
};
|
|
96
122
|
}
|
|
97
123
|
|
|
124
|
+
if (socialImageExtensions.has(parsed.ext) && editDistance(parsed.name, 'social-image') <= 2) {
|
|
125
|
+
return {
|
|
126
|
+
code: 'possible-social-image-typo',
|
|
127
|
+
message: `"${filename}" looks like a misspelled social sharing image filename. Norna recognizes ${socialImageAssetFilenames.join(', ')}.`,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
98
131
|
return null;
|
|
99
132
|
};
|
|
100
133
|
|
|
@@ -102,11 +135,12 @@ export const inspectPublicAssetFilenames = (filenames) => {
|
|
|
102
135
|
const files = [...filenames].sort((left, right) => left.localeCompare(right, 'en'));
|
|
103
136
|
const logos = files.filter(isLogoAssetFilename);
|
|
104
137
|
const browserIcons = files.filter((filename) => exactBrowserIconNames.has(filename));
|
|
138
|
+
const socialImages = files.filter((filename) => exactSocialImageNames.has(filename));
|
|
105
139
|
const suspicious = files.flatMap((filename) => {
|
|
106
|
-
if (isLogoAssetFilename(filename) || exactBrowserIconNames.has(filename)) return [];
|
|
140
|
+
if (isLogoAssetFilename(filename) || exactBrowserIconNames.has(filename) || exactSocialImageNames.has(filename)) return [];
|
|
107
141
|
const issue = getSuspiciousAsset(filename);
|
|
108
142
|
return issue ? [{ filename, ...issue }] : [];
|
|
109
143
|
});
|
|
110
144
|
|
|
111
|
-
return { browserIcons, files, logos, suspicious };
|
|
145
|
+
return { browserIcons, files, logos, socialImages, suspicious };
|
|
112
146
|
};
|