@janga/norna 0.7.22 → 0.7.23

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.
Files changed (60) hide show
  1. package/astro.config.mjs +3 -0
  2. package/bin/norna-cli.mjs +2 -2
  3. package/package.json +17 -3
  4. package/schemas/category.schema.json +2 -2
  5. package/schemas/config.schema.json +22 -24
  6. package/schemas/content-frontmatter.schema.json +25 -5
  7. package/schemas/page-theme.schema.json +53 -25
  8. package/schemas/sitewide-content.schema.json +19 -18
  9. package/schemas/theme.schema.json +383 -250
  10. package/scripts/check-config.mjs +24 -4
  11. package/scripts/deploy-site.mjs +0 -1
  12. package/scripts/dev-local.mjs +175 -112
  13. package/scripts/lib/content-sync-apply.mjs +34 -0
  14. package/scripts/lib/content-sync-plan.mjs +179 -0
  15. package/scripts/lib/editor-language-service.mjs +128 -0
  16. package/scripts/lib/heading-ids.mjs +1 -1
  17. package/scripts/lib/image-presentation.mjs +2 -0
  18. package/scripts/lib/markdown-links.mjs +182 -0
  19. package/scripts/lib/navigation-model.mjs +25 -14
  20. package/scripts/lib/norna-markdown-blocks.mjs +1 -0
  21. package/scripts/lib/page-aliases.mjs +164 -0
  22. package/scripts/lib/page-markdown.mjs +56 -6
  23. package/scripts/lib/presentation.mjs +19 -37
  24. package/scripts/lib/project-config.mjs +50 -29
  25. package/scripts/lib/schema-definitions.mjs +24 -13
  26. package/scripts/lib/schema-editor-metadata.mjs +43 -30
  27. package/scripts/lib/schema-value-definitions.mjs +6 -2
  28. package/scripts/lib/site-content.mjs +26 -4
  29. package/scripts/lib/site-link-graph.mjs +326 -0
  30. package/scripts/lib/site-page-urls.mjs +10 -0
  31. package/scripts/lib/sitemap.mjs +34 -0
  32. package/scripts/lib/theme-presets.mjs +51 -23
  33. package/scripts/lib/theme-profiles.mjs +7 -5
  34. package/scripts/lib/yaml-config.mjs +6 -2
  35. package/scripts/sync-content-sections.mjs +116 -213
  36. package/scripts/sync-site-public.mjs +23 -2
  37. package/src/components/CodeBlockCopyScript.astro +92 -0
  38. package/src/components/DisplaySettings.astro +4 -4
  39. package/src/components/ImageCarousel.astro +7 -3
  40. package/src/components/NavigationPageTree.astro +44 -60
  41. package/src/components/PageAliasRedirect.astro +49 -0
  42. package/src/components/PageContentsNavigation.astro +21 -0
  43. package/src/components/SectionNavigationScript.astro +62 -6
  44. package/src/components/SiteNavigation.astro +15 -3
  45. package/src/components/SitePage.astro +42 -2
  46. package/src/components/SiteSection.astro +5 -0
  47. package/src/components/SiteTreeNavigation.astro +0 -1
  48. package/src/content.config.ts +5 -2
  49. package/src/layouts/BaseLayout.astro +14 -8
  50. package/src/lib/readerPreferencesScript.mjs +2 -2
  51. package/src/lib/sitePages.ts +3 -2
  52. package/src/pages/[...slug].astro +35 -8
  53. package/src/styles/content.css +494 -0
  54. package/src/styles/foundations.css +284 -0
  55. package/src/styles/global.css +6 -2510
  56. package/src/styles/media.css +523 -0
  57. package/src/styles/navigation.css +495 -0
  58. package/src/styles/page-layout.css +437 -0
  59. package/src/styles/responsive.css +525 -0
  60. package/starters/basic/README.md +1 -1
@@ -1,31 +1,58 @@
1
1
  ---
2
2
  import { getCollection } from 'astro:content';
3
+ import { assertPageAliasModel, createPageAliasModel } from '../../scripts/lib/page-aliases.mjs';
4
+ import { getSitePublicFiles } from '../../scripts/lib/site-link-graph.mjs';
5
+ import PageAliasRedirect from '../components/PageAliasRedirect.astro';
3
6
  import SitePage from '../components/SitePage.astro';
4
- import { getSitePage, getSitePages, isHomePageEntry } from '../lib/sitePages';
7
+ import { getSiteModel, getSitePage, isHomePageEntry } from '../lib/sitePages';
5
8
 
6
9
  export async function getStaticPaths() {
7
10
  const entries = await getCollection('site');
8
- const pages = await getSitePages(entries);
11
+ const siteModel = await getSiteModel(entries);
12
+ const aliasModel = assertPageAliasModel(createPageAliasModel({
13
+ categories: siteModel.categories,
14
+ pages: siteModel.pages,
15
+ publicFiles: await getSitePublicFiles(),
16
+ }));
17
+ const pagesByPathname = new Map(siteModel.pages.map((page) => [page.pathname, page]));
9
18
 
10
- return pages
19
+ const pagePaths = siteModel.pages
11
20
  .filter((page) => !page.isHome)
12
21
  .map((page) => ({
13
22
  params: { slug: page.pathSegment },
14
23
  props: { entry: page.entry },
15
24
  }));
25
+ const aliasPaths = aliasModel.aliases.map((alias) => {
26
+ const page = pagesByPathname.get(alias.targetPathname);
27
+ if (!page) throw new Error(`Page alias "${alias.pathname}" has no target page ${alias.targetPathname}.`);
28
+
29
+ return {
30
+ params: { slug: alias.pathname.slice(1, -1) },
31
+ props: {
32
+ alias: {
33
+ targetPathname: page.pathname,
34
+ targetTitle: page.title,
35
+ },
36
+ },
37
+ };
38
+ });
39
+
40
+ return [...pagePaths, ...aliasPaths];
16
41
  }
17
42
 
18
- const { entry } = Astro.props;
43
+ const { alias, entry } = Astro.props;
19
44
 
20
- if (!entry || isHomePageEntry(entry)) {
45
+ if (!alias && (!entry || isHomePageEntry(entry))) {
21
46
  throw new Error('Page entry is missing.');
22
47
  }
23
48
 
24
- const currentPage = await getSitePage(entry);
49
+ const currentPage = entry ? await getSitePage(entry) : null;
25
50
 
26
- if (!currentPage.pathSegment) {
51
+ if (!alias && !currentPage?.pathSegment) {
27
52
  throw new Error(`Page "${entry.id}" needs a page id.`);
28
53
  }
29
54
  ---
30
55
 
31
- <SitePage entry={entry} />
56
+ {alias
57
+ ? <PageAliasRedirect targetPathname={alias.targetPathname} targetTitle={alias.targetTitle} />
58
+ : <SitePage entry={entry} />}
@@ -0,0 +1,494 @@
1
+ /* Banners, footer, sections, prose, headings, and sidenotes. */
2
+
3
+ .site-banners {
4
+ position: relative;
5
+ z-index: 2;
6
+ display: grid;
7
+ gap: 0.75rem;
8
+ width: min(calc(100% - (var(--content-gutter) * 2)), var(--page-width));
9
+ margin: 0 auto;
10
+ padding: 1rem 0 0;
11
+ }
12
+
13
+ .site-banner {
14
+ display: flex;
15
+ align-items: center;
16
+ width: 100%;
17
+ min-width: 0;
18
+ gap: 1rem;
19
+ border: 1px solid var(--color-warning-accent);
20
+ border-inline-start: 0.35rem solid var(--color-warning-accent);
21
+ padding: 0.8rem 0.9rem;
22
+ background: var(--color-warning-surface);
23
+ color: var(--color-warning-text);
24
+ }
25
+
26
+ .site-banner[hidden] {
27
+ display: none;
28
+ }
29
+
30
+ .site-banner-copy {
31
+ display: flex;
32
+ align-items: baseline;
33
+ flex-wrap: wrap;
34
+ gap: 0.65rem;
35
+ min-width: 0;
36
+ flex: 1 1 auto;
37
+ }
38
+
39
+ .site-banner-title,
40
+ .site-banner-text {
41
+ margin: 0;
42
+ }
43
+
44
+ .site-banner-title {
45
+ flex: 0 0 auto;
46
+ white-space: nowrap;
47
+ font-weight: 700;
48
+ }
49
+
50
+ .site-banner-text {
51
+ min-width: 0;
52
+ margin-top: 0.2rem;
53
+ overflow-wrap: anywhere;
54
+ }
55
+
56
+ .site-banner-dismiss {
57
+ flex: 0 0 auto;
58
+ width: 2rem;
59
+ height: 2rem;
60
+ border: 1px solid color-mix(in srgb, currentColor 35%, transparent);
61
+ padding: 0;
62
+ background: transparent;
63
+ color: currentColor;
64
+ cursor: pointer;
65
+ font-size: 1.25rem;
66
+ line-height: 1;
67
+ }
68
+
69
+ .site-banner-dismiss:hover,
70
+ .site-banner-dismiss:focus-visible {
71
+ background: color-mix(in srgb, currentColor 12%, transparent);
72
+ }
73
+
74
+ .site-footer {
75
+ position: relative;
76
+ isolation: isolate;
77
+ z-index: 2;
78
+ width: min(calc(100% - (var(--content-gutter) * 2)), var(--page-width));
79
+ margin: 0 auto;
80
+ padding: 0 0 2.5rem;
81
+ background-color: var(--site-footer-background-color, var(--color-page));
82
+ color: var(--site-footer-text-color, var(--color-muted));
83
+ font-size: 0.75rem;
84
+ line-height: 1.5;
85
+ text-align: center;
86
+ }
87
+
88
+ .site-footer::before {
89
+ position: absolute;
90
+ z-index: -1;
91
+ inset: 0 calc(50% - 50vw);
92
+ background-color: var(--site-footer-background-color, var(--color-page));
93
+ content: "";
94
+ pointer-events: none;
95
+ }
96
+
97
+ .site-footer p {
98
+ margin: 0;
99
+ }
100
+
101
+ .site-footer span {
102
+ display: block;
103
+ }
104
+
105
+ .site-section {
106
+ --layout-heading-width: min(100%, var(--reader-text-width, var(--section-heading-width-desktop, 760px)));
107
+ --layout-reading-width: min(100%, var(--reader-text-width, var(--section-body-width-desktop, var(--text-width))));
108
+ --layout-media-width: min(100%, var(--image-layout-width));
109
+ --layout-note-width: var(--section-note-width, 12rem);
110
+ --layout-note-gap: var(--section-note-gap, 1.25rem);
111
+ position: relative;
112
+ isolation: isolate;
113
+ background-color: var(--section-background-color, transparent);
114
+ padding: var(--space-nav-to-first-section) 0 0;
115
+ }
116
+
117
+ .site-section::before {
118
+ position: absolute;
119
+ z-index: -1;
120
+ inset: 0 calc(50% - 50vw);
121
+ background-color: var(--section-background-color, transparent);
122
+ content: "";
123
+ pointer-events: none;
124
+ }
125
+
126
+ .site-page-layout-tree .site-section::before {
127
+ left: 0;
128
+ right: calc(0px - var(--tree-layout-outer-margin));
129
+ }
130
+
131
+ .site-section + .site-section {
132
+ margin-top: 0;
133
+ padding-top: var(--space-section-to-section);
134
+ }
135
+
136
+ .site-section-page-title-empty .section-header {
137
+ padding-bottom: 0;
138
+ }
139
+
140
+ .site-section-page-title-empty .section-header h1 {
141
+ padding-bottom: 0;
142
+ }
143
+
144
+ .site-section-page-title-empty + .site-section {
145
+ padding-top: var(--space-page-title-to-first-section);
146
+ }
147
+
148
+ .site-section:last-child {
149
+ padding-bottom: var(--space-final-section-bottom);
150
+ }
151
+
152
+ .section-card {
153
+ background: transparent;
154
+ color: var(--section-text-color, var(--color-text));
155
+ }
156
+
157
+ .section-header {
158
+ width: var(--layout-heading-width);
159
+ margin-block: 0;
160
+ margin-inline: var(--layout-inline-start-margin) var(--layout-inline-end-margin);
161
+ padding: 0;
162
+ text-align: var(--section-heading-align-desktop, center);
163
+ }
164
+
165
+ .site-section-first-block-is-visual .section-header h1,
166
+ .site-section-first-block-is-visual .section-header h2 {
167
+ padding-bottom: var(--space-heading-to-block);
168
+ }
169
+
170
+ .section-header h1,
171
+ .section-header h2,
172
+ p,
173
+ ul {
174
+ margin-top: 0;
175
+ }
176
+
177
+ .section-header h1,
178
+ .section-header h2 {
179
+ margin-bottom: 0;
180
+ padding-bottom: var(--section-heading-spacing-after, 0.55em);
181
+ color: inherit;
182
+ font-size: var(--section-heading-font-size-desktop, clamp(1.35rem, 1.1rem + 0.9vw, 2rem));
183
+ font-weight: var(--section-heading-font-weight, 400);
184
+ letter-spacing: 0;
185
+ line-height: var(--section-heading-line-height, 1.04);
186
+ text-transform: uppercase;
187
+ overflow-wrap: anywhere;
188
+ }
189
+
190
+ .section-body {
191
+ display: flow-root;
192
+ container-type: inline-size;
193
+ width: 100%;
194
+ margin: 0 auto;
195
+ color: inherit;
196
+ font-family: var(--font-sans);
197
+ font-size: clamp(0.96rem, 0.94rem + 0.08vw, 1rem);
198
+ }
199
+
200
+ .section-note {
201
+ display: grid;
202
+ grid-template-columns: auto minmax(0, 1fr);
203
+ column-gap: 0.25rem;
204
+ width: 100%;
205
+ margin: var(--section-body-paragraph-spacing, 0.85em) 0 0;
206
+ border-inline-start: 2px solid color-mix(in srgb, currentColor 35%, transparent);
207
+ padding: 0 0 0 0.35rem;
208
+ color: var(--section-secondary-text-color, var(--color-secondary-text));
209
+ font-size: 0.88em;
210
+ line-height: 1.45;
211
+ scroll-margin-top: calc(var(--site-top-anchor-offset) + 1rem);
212
+ }
213
+
214
+ .section-note-number {
215
+ display: block;
216
+ margin: 0;
217
+ color: inherit;
218
+ font-size: 0.82em;
219
+ font-weight: 700;
220
+ line-height: 1;
221
+ text-decoration: none;
222
+ }
223
+
224
+ .section-note-ref {
225
+ display: inline-block;
226
+ margin-left: 0.03em;
227
+ font-size: 0.72em;
228
+ line-height: 0;
229
+ white-space: nowrap;
230
+ vertical-align: super;
231
+ }
232
+
233
+ .section-note-ref a {
234
+ color: inherit;
235
+ text-decoration: underline;
236
+ text-decoration-thickness: 0.08em;
237
+ text-underline-offset: 0.15em;
238
+ }
239
+
240
+ .section-note:target {
241
+ border-inline-start-color: currentColor;
242
+ background: color-mix(in srgb, currentColor 6%, transparent);
243
+ }
244
+
245
+ .section-note-ref:has(+ .section-note:hover) a,
246
+ .section-note-ref a:hover,
247
+ .section-note-ref a:focus-visible {
248
+ text-decoration-thickness: 0.16em;
249
+ }
250
+
251
+ .section-note-content {
252
+ min-width: 0;
253
+ }
254
+
255
+ @media (min-width: 1101px) {
256
+ @container (min-width: 55rem) {
257
+ .section-note {
258
+ float: right;
259
+ clear: right;
260
+ width: var(--layout-note-width);
261
+ margin: 0 calc(0px - var(--layout-note-width) - var(--layout-note-gap)) 0.75rem var(--layout-note-gap);
262
+ }
263
+ }
264
+
265
+ .site-page-layout-contents .section-note {
266
+ float: none;
267
+ clear: none;
268
+ width: 100%;
269
+ margin: var(--section-body-paragraph-spacing, 0.85em) 0 0;
270
+ }
271
+ }
272
+
273
+ .section-markdown {
274
+ width: var(--layout-reading-width);
275
+ max-width: none;
276
+ margin-block: 0;
277
+ margin-inline: var(--layout-inline-start-margin) var(--layout-inline-end-margin);
278
+ font-size: var(--section-body-font-size-desktop, clamp(0.96rem, 0.94rem + 0.08vw, 1rem));
279
+ line-height: var(--section-body-line-height, 1.6);
280
+ text-align: var(--section-body-align-desktop, left);
281
+ }
282
+
283
+ .section-markdown table {
284
+ display: block;
285
+ max-width: 100%;
286
+ margin: var(--section-body-paragraph-spacing, 0.85em) 0;
287
+ border-collapse: collapse;
288
+ font-size: 0.92em;
289
+ line-height: 1.45;
290
+ overflow-x: auto;
291
+ overscroll-behavior-inline: contain;
292
+ scrollbar-gutter: stable;
293
+ }
294
+
295
+ .section-markdown :is(th, td) {
296
+ border-bottom: 1px solid color-mix(in srgb, currentColor 24%, transparent);
297
+ padding: 0.65rem 0.75rem;
298
+ text-align: left;
299
+ vertical-align: top;
300
+ }
301
+
302
+ .section-markdown thead th {
303
+ border-bottom-color: color-mix(in srgb, currentColor 48%, transparent);
304
+ }
305
+
306
+ :root[data-focus-reading='on'] :is(
307
+ .site-nav-leading,
308
+ .site-nav,
309
+ .mobile-nav-menu,
310
+ .page-nav,
311
+ .tree-local-navigation,
312
+ .page-contents-navigation,
313
+ .site-breadcrumbs,
314
+ .site-footer
315
+ ) {
316
+ display: none !important;
317
+ }
318
+
319
+ :root[data-focus-reading='on'] .site-nav-row {
320
+ justify-content: flex-end;
321
+ }
322
+
323
+ @media (min-width: 701px) {
324
+ :root[data-focus-reading='on'] .site-page-layout-tree {
325
+ display: grid;
326
+ grid-template-columns: var(--tree-navigation-width) minmax(0, 1fr);
327
+ gap: var(--tree-layout-gap);
328
+ width: min(calc(100% - (var(--content-gutter) * 2)), var(--tree-layout-width));
329
+ }
330
+
331
+ :root[data-focus-reading='on'] .site-page-layout-tree > .site-content {
332
+ grid-column: 2;
333
+ width: 100%;
334
+ }
335
+
336
+ :root[data-focus-reading='on'] .site-page-layout-tree .site-section::before {
337
+ left: 0;
338
+ right: calc(0px - var(--tree-layout-outer-margin));
339
+ }
340
+ }
341
+
342
+ @media (min-width: 1101px) {
343
+ :root[data-focus-reading='on'] .site-page-layout-tree {
344
+ grid-template-columns: var(--tree-navigation-width) minmax(0, 1fr) var(--contents-navigation-width);
345
+ }
346
+ }
347
+
348
+ :root[data-focus-reading='on'] .site-content-has-breadcrumbs > .site-section:first-of-type {
349
+ padding-top: var(--space-nav-to-first-section);
350
+ }
351
+
352
+ .section-markdown p {
353
+ margin: 0 0 var(--section-body-paragraph-spacing, 1em);
354
+ }
355
+
356
+ .section-markdown blockquote {
357
+ margin: 0 0 var(--section-body-paragraph-spacing, 1em);
358
+ border-left: 1px solid color-mix(in srgb, currentColor 34%, transparent);
359
+ padding-left: 1rem;
360
+ }
361
+
362
+ .section-markdown blockquote > :last-child {
363
+ margin-bottom: 0;
364
+ }
365
+
366
+ .section-markdown pre.astro-code {
367
+ margin: 0 0 var(--section-body-paragraph-spacing, 1em);
368
+ border: 1px solid color-mix(in srgb, currentColor 18%, transparent);
369
+ border-radius: var(--corner-radius-medium, 6px);
370
+ padding: 0.78rem 0.9rem;
371
+ background: var(--color-code-background) !important;
372
+ color: var(--color-code-text);
373
+ font-size: 0.92em;
374
+ line-height: 1.45;
375
+ text-align: left;
376
+ }
377
+
378
+ .section-markdown .code-block {
379
+ position: relative;
380
+ margin: 0 0 var(--section-body-paragraph-spacing, 1em);
381
+ }
382
+
383
+ .section-markdown .code-block > pre.astro-code {
384
+ margin: 0;
385
+ padding-right: 3.85rem;
386
+ }
387
+
388
+ .code-block-copy {
389
+ position: absolute;
390
+ top: 0.5rem;
391
+ right: 0.5rem;
392
+ z-index: 1;
393
+ display: grid;
394
+ width: 2.25rem;
395
+ height: 2.25rem;
396
+ place-items: center;
397
+ padding: 0;
398
+ border: 1px solid color-mix(in srgb, var(--color-code-text) 35%, transparent);
399
+ border-radius: var(--corner-radius-small, 2px);
400
+ background: color-mix(in srgb, var(--color-code-background) 78%, var(--color-code-text));
401
+ color: var(--color-code-text);
402
+ cursor: pointer;
403
+ transition: background-color 0.15s ease, border-color 0.15s ease;
404
+ }
405
+
406
+ .code-block-copy:hover,
407
+ .code-block-copy:active,
408
+ .code-block-copy[data-copy-state] {
409
+ border-color: color-mix(in srgb, var(--color-code-text) 65%, transparent);
410
+ background: color-mix(in srgb, var(--color-code-background) 62%, var(--color-code-text));
411
+ }
412
+
413
+ .code-block-copy svg {
414
+ width: 1.125rem;
415
+ height: 1.125rem;
416
+ fill: none;
417
+ stroke: currentColor;
418
+ }
419
+
420
+ .code-block-copy [hidden] {
421
+ display: none;
422
+ }
423
+
424
+ .section-markdown pre.astro-code code {
425
+ font: inherit;
426
+ }
427
+
428
+ .section-markdown :not(pre) > code {
429
+ overflow-wrap: anywhere;
430
+ word-break: break-word;
431
+ }
432
+
433
+ .section-markdown > :last-child,
434
+ .image-meta :last-child {
435
+ margin-bottom: 0;
436
+ }
437
+
438
+ .section-markdown > pre.astro-code:last-child {
439
+ margin-bottom: 0;
440
+ }
441
+
442
+ .section-markdown > .code-block:last-child {
443
+ margin-bottom: 0;
444
+ }
445
+
446
+ .section-markdown blockquote > pre.astro-code:last-child {
447
+ margin-bottom: 0;
448
+ }
449
+
450
+ .section-markdown blockquote > .code-block:last-child {
451
+ margin-bottom: 0;
452
+ }
453
+
454
+ .section-markdown h3 {
455
+ margin:
456
+ var(--section-markdown-h3-spacing-before, 1.5em)
457
+ 0
458
+ var(--section-markdown-h3-spacing-after, 0.5em);
459
+ border-top: 1px solid var(--color-soft);
460
+ padding-top: 0.55em;
461
+ font-family: var(--font-sans);
462
+ font-size: var(--section-markdown-h3-font-size-desktop, 0.82rem);
463
+ font-weight: var(--section-markdown-h3-font-weight, 700);
464
+ letter-spacing: 0;
465
+ line-height: var(--section-markdown-h3-line-height, 1.35);
466
+ text-align: var(--section-markdown-h3-align-desktop, left);
467
+ text-transform: uppercase;
468
+ }
469
+
470
+ .section-markdown h4 {
471
+ margin:
472
+ var(--section-markdown-h4-spacing-before, 1.1em)
473
+ 0
474
+ var(--section-markdown-h4-spacing-after, 0.4em);
475
+ color: inherit;
476
+ font-size: var(--section-markdown-h4-font-size-desktop, clamp(0.96rem, 0.94rem + 0.08vw, 1rem));
477
+ font-weight: var(--section-markdown-h4-font-weight, 400);
478
+ letter-spacing: 0;
479
+ line-height: var(--section-markdown-h4-line-height, 1.5);
480
+ text-align: var(--section-markdown-h4-align-desktop, left);
481
+ }
482
+
483
+ .section-markdown ul,
484
+ .section-markdown ol {
485
+ display: inline-block;
486
+ margin-right: auto;
487
+ margin-left: auto;
488
+ padding-left: 1.2rem;
489
+ text-align: left;
490
+ }
491
+
492
+ .section-markdown li + li {
493
+ margin-top: 0.35rem;
494
+ }