@janga/norna 0.7.8 → 0.7.9
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/docs/README.md +4 -0
- package/docs/design/norna-diagram-design.md +156 -0
- package/examples/typography-presets/.astro/collections/site.schema.json +776 -0
- package/examples/typography-presets/.astro/collections/theme.schema.json +668 -0
- package/examples/typography-presets/.astro/content-assets.mjs +4 -0
- package/examples/typography-presets/.astro/content-modules.mjs +1 -0
- package/examples/typography-presets/.astro/content.d.ts +175 -0
- package/examples/typography-presets/.astro/data-store.json +1 -0
- package/examples/typography-presets/.astro/dev-local.json +8 -0
- package/examples/typography-presets/.astro/dev.json +15 -0
- package/examples/typography-presets/.astro/dev.log +24 -0
- package/examples/typography-presets/.astro/settings.json +5 -0
- package/examples/typography-presets/.astro/types.d.ts +2 -0
- package/examples/typography-presets/node_modules/.vite/deps/_metadata.json +80 -0
- package/examples/typography-presets/node_modules/.vite/deps/astro-D_GIpJIE.js +434 -0
- package/examples/typography-presets/node_modules/.vite/deps/astro-D_GIpJIE.js.map +1 -0
- package/examples/typography-presets/node_modules/.vite/deps/astro_n_aria-query.js +6390 -0
- package/examples/typography-presets/node_modules/.vite/deps/astro_n_aria-query.js.map +1 -0
- package/examples/typography-presets/node_modules/.vite/deps/astro_n_axobject-query.js +2702 -0
- package/examples/typography-presets/node_modules/.vite/deps/astro_n_axobject-query.js.map +1 -0
- package/examples/typography-presets/node_modules/.vite/deps/astro_n_html-escaper.js +65 -0
- package/examples/typography-presets/node_modules/.vite/deps/astro_n_html-escaper.js.map +1 -0
- package/examples/typography-presets/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js +455 -0
- package/examples/typography-presets/node_modules/.vite/deps/astro_runtime_client_dev-toolbar_entrypoint__js.js.map +1 -0
- package/examples/typography-presets/node_modules/.vite/deps/audit-DM9Hkl5B.js +1534 -0
- package/examples/typography-presets/node_modules/.vite/deps/audit-DM9Hkl5B.js.map +1 -0
- package/examples/typography-presets/node_modules/.vite/deps/embla-carousel.js +1389 -0
- package/examples/typography-presets/node_modules/.vite/deps/embla-carousel.js.map +1 -0
- package/examples/typography-presets/node_modules/.vite/deps/highlight-DnZ1ULOL.js +48 -0
- package/examples/typography-presets/node_modules/.vite/deps/highlight-DnZ1ULOL.js.map +1 -0
- package/examples/typography-presets/node_modules/.vite/deps/icons-RyebKi45.js +42 -0
- package/examples/typography-presets/node_modules/.vite/deps/icons-RyebKi45.js.map +1 -0
- package/examples/typography-presets/node_modules/.vite/deps/package.json +3 -0
- package/examples/typography-presets/node_modules/.vite/deps/rolldown-runtime-BvCyGRYZ.js +4 -0
- package/examples/typography-presets/node_modules/.vite/deps/settings-DaU-gNnK.js +190 -0
- package/examples/typography-presets/node_modules/.vite/deps/settings-DaU-gNnK.js.map +1 -0
- package/examples/typography-presets/node_modules/.vite/deps/toolbar-63PA619H.js +458 -0
- package/examples/typography-presets/node_modules/.vite/deps/toolbar-63PA619H.js.map +1 -0
- package/examples/typography-presets/node_modules/.vite/deps/ui-library--bkcNSYz.js +1016 -0
- package/examples/typography-presets/node_modules/.vite/deps/ui-library--bkcNSYz.js.map +1 -0
- package/examples/typography-presets/node_modules/.vite/deps/window-jPkpKVcy.js +32 -0
- package/examples/typography-presets/node_modules/.vite/deps/window-jPkpKVcy.js.map +1 -0
- package/examples/typography-presets/node_modules/.vite/deps/xray-BXCHpEJP.js +111 -0
- package/examples/typography-presets/node_modules/.vite/deps/xray-BXCHpEJP.js.map +1 -0
- package/examples/typography-presets/site/.norna/generated-images.json +290 -0
- package/examples/typography-presets/site/config.mjs +42 -0
- package/examples/typography-presets/site/content.md +134 -0
- package/examples/typography-presets/site/images/compact-gallery/black-schnauzer.jpg +0 -0
- package/examples/typography-presets/site/images/compact-gallery/retriever-portrait.jpg +0 -0
- package/examples/typography-presets/site/images/compact-gallery/schnauzer-portrait.jpg +0 -0
- package/examples/typography-presets/site/images/quiet-gallery/puppy-portrait.jpg +0 -0
- package/examples/typography-presets/site/images/quiet-gallery/shepherd-portrait.jpg +0 -0
- package/examples/typography-presets/site/images/quiet-gallery/white-puppy.jpg +0 -0
- package/examples/typography-presets/site/images/statement/black-pedigree.jpg +0 -0
- package/examples/typography-presets/site/images/statement/sheepdog-grass.jpg +0 -0
- package/examples/typography-presets/site/images/statement/sheepdog-wide.jpg +0 -0
- package/examples/typography-presets/site/images/text-forward/boxer-portrait.jpg +0 -0
- package/examples/typography-presets/site/images/text-forward/grass-puppy.jpg +0 -0
- package/examples/typography-presets/site/images/text-forward/grey-street-dog.jpg +0 -0
- package/examples/typography-presets/site/public/robots.txt +2 -0
- package/examples/typography-presets/site/theme.md +43 -0
- package/package.json +1 -1
- package/scripts/lib/site-content.mjs +76 -0
- package/scripts/lib/theme-config.mjs +2 -0
- package/scripts/sync-content-sections.mjs +3 -0
- package/scripts/test-content-check.mjs +30 -0
- package/scripts/test-package-check.mjs +17 -0
- package/src/components/SiteSection.astro +1 -1
- package/src/lib/sectionContent.ts +10 -2
- package/src/styles/global.css +1 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Typography Presets
|
|
3
|
+
description: A Norna example page showing every built-in typography preset.
|
|
4
|
+
navigation:
|
|
5
|
+
label: Presets
|
|
6
|
+
sections:
|
|
7
|
+
quiet-gallery:
|
|
8
|
+
presentation:
|
|
9
|
+
typography:
|
|
10
|
+
preset: quiet-gallery
|
|
11
|
+
compact-gallery:
|
|
12
|
+
presentation:
|
|
13
|
+
typography:
|
|
14
|
+
preset: compact-gallery
|
|
15
|
+
text-forward:
|
|
16
|
+
presentation:
|
|
17
|
+
typography:
|
|
18
|
+
preset: text-forward
|
|
19
|
+
statement:
|
|
20
|
+
presentation:
|
|
21
|
+
typography:
|
|
22
|
+
preset: statement
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Quiet Gallery {#quiet-gallery}
|
|
26
|
+
|
|
27
|
+
This section uses the `quiet-gallery` preset. Its purpose is to keep text
|
|
28
|
+
restrained so images can carry much of the page without the typography
|
|
29
|
+
competing for attention.
|
|
30
|
+
|
|
31
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer posuere erat a
|
|
32
|
+
ante venenatis dapibus posuere velit aliquet. Maecenas faucibus mollis
|
|
33
|
+
interdum. Donec ullamcorper nulla non metus auctor fringilla.
|
|
34
|
+
|
|
35
|
+
### Reading rhythm
|
|
36
|
+
|
|
37
|
+
Curabitur blandit tempus porttitor. Praesent commodo cursus magna, vel
|
|
38
|
+
scelerisque nisl consectetur et. Vestibulum id ligula porta felis euismod
|
|
39
|
+
semper.
|
|
40
|
+
|
|
41
|
+
```norna-image-stack
|
|
42
|
+
- image: puppy-portrait.jpg
|
|
43
|
+
alt: A puppy sitting outdoors and looking toward the camera.
|
|
44
|
+
caption: Puppy portrait. Photo by Bicanski, CC0, via Pixnio.
|
|
45
|
+
- image: shepherd-portrait.jpg
|
|
46
|
+
alt: A shepherd dog standing outdoors and looking toward the camera.
|
|
47
|
+
caption: Shepherd dog portrait. Photo by Bicanski, CC0, via Pixnio.
|
|
48
|
+
- image: white-puppy.jpg
|
|
49
|
+
alt: A white puppy outdoors in winter.
|
|
50
|
+
caption: White puppy outdoors. Photo by Bicanski, CC0, via Pixnio.
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Compact Gallery {#compact-gallery}
|
|
54
|
+
|
|
55
|
+
This section uses the `compact-gallery` preset. Its purpose is to make denser
|
|
56
|
+
image-led pages easier to scan when a site contains many sections, many images
|
|
57
|
+
or short information blocks.
|
|
58
|
+
|
|
59
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean lacinia
|
|
60
|
+
bibendum nulla sed consectetur. Etiam porta sem malesuada magna mollis euismod.
|
|
61
|
+
|
|
62
|
+
### Dense sections
|
|
63
|
+
|
|
64
|
+
Nullam id dolor id nibh ultricies vehicula ut id elit. Fusce dapibus, tellus ac
|
|
65
|
+
cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit
|
|
66
|
+
amet risus.
|
|
67
|
+
|
|
68
|
+
```norna-image-stack
|
|
69
|
+
- image: schnauzer-portrait.jpg
|
|
70
|
+
alt: A schnauzer-type dog sitting outdoors.
|
|
71
|
+
caption: Schnauzer portrait. Photo by Bicanski, CC0, via Pixnio.
|
|
72
|
+
- image: black-schnauzer.jpg
|
|
73
|
+
alt: A black schnauzer-type dog standing on grass.
|
|
74
|
+
caption: Black hunting dog portrait. Photo by Bicanski, CC0, via Pixnio.
|
|
75
|
+
- image: retriever-portrait.jpg
|
|
76
|
+
alt: A retriever-type dog sitting outdoors.
|
|
77
|
+
caption: Retriever portrait. Photo by Bicanski, CC0, via Pixnio.
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Text Forward {#text-forward}
|
|
81
|
+
|
|
82
|
+
This section uses the `text-forward` preset. Its purpose is to give longer
|
|
83
|
+
explanatory text more presence while still supporting image captions and
|
|
84
|
+
image-led sections.
|
|
85
|
+
|
|
86
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed posuere
|
|
87
|
+
consectetur est at lobortis. Cras mattis consectetur purus sit amet fermentum.
|
|
88
|
+
Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.
|
|
89
|
+
|
|
90
|
+
### Longer explanations
|
|
91
|
+
|
|
92
|
+
Morbi leo risus, porta ac consectetur ac, vestibulum at eros. Donec sed odio
|
|
93
|
+
dui. Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget
|
|
94
|
+
lacinia odio sem nec elit.
|
|
95
|
+
|
|
96
|
+
```norna-image-stack
|
|
97
|
+
- image: grass-puppy.jpg
|
|
98
|
+
alt: A puppy standing in green grass.
|
|
99
|
+
caption: Puppy in grass. Photo by Bicanski, CC0, via Pixnio.
|
|
100
|
+
- image: grey-street-dog.jpg
|
|
101
|
+
alt: A grey dog standing on a paved street.
|
|
102
|
+
caption: Grey dog on pavement. Photo by Bicanski, CC0, via Pixnio.
|
|
103
|
+
- image: boxer-portrait.jpg
|
|
104
|
+
alt: A brown boxer-type dog looking at the camera.
|
|
105
|
+
caption: Brown dog portrait. Photo by Bicanski, CC0, via Pixnio.
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Statement {#statement}
|
|
109
|
+
|
|
110
|
+
This section uses the `statement` preset. Its purpose is to make short,
|
|
111
|
+
declarative sections feel more direct and programmatic. It is intentionally
|
|
112
|
+
stronger than the other presets and should usually be used sparingly.
|
|
113
|
+
|
|
114
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec id elit non mi
|
|
115
|
+
porta gravida at eget metus. Integer posuere erat a ante venenatis dapibus
|
|
116
|
+
posuere velit aliquet.
|
|
117
|
+
|
|
118
|
+
### Short emphasis
|
|
119
|
+
|
|
120
|
+
Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Aenean
|
|
121
|
+
lacinia bibendum nulla sed consectetur. Maecenas sed diam eget risus varius
|
|
122
|
+
blandit sit amet non magna.
|
|
123
|
+
|
|
124
|
+
```norna-image-stack
|
|
125
|
+
- image: sheepdog-grass.jpg
|
|
126
|
+
alt: A sheepdog puppy sitting in green grass.
|
|
127
|
+
caption: Sheepdog puppy in grass. Photo by Bicanski, CC0, via Pixnio.
|
|
128
|
+
- image: sheepdog-wide.jpg
|
|
129
|
+
alt: A sheepdog puppy lying outdoors.
|
|
130
|
+
caption: Sheepdog puppy outdoors. Photo by Bicanski, CC0, via Pixnio.
|
|
131
|
+
- image: black-pedigree.jpg
|
|
132
|
+
alt: A black dog standing in grass.
|
|
133
|
+
caption: Black pedigree dog portrait. Photo by Bicanski, CC0, via Pixnio.
|
|
134
|
+
```
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
navigation:
|
|
3
|
+
brand: Typography Presets
|
|
4
|
+
layout:
|
|
5
|
+
density: normal
|
|
6
|
+
pageWidth: 1320px
|
|
7
|
+
gutter:
|
|
8
|
+
desktop: clamp(1.25rem, 4vw, 3rem)
|
|
9
|
+
mobile: 1rem
|
|
10
|
+
spacing:
|
|
11
|
+
firstSectionTop:
|
|
12
|
+
desktop: clamp(1.5rem, 3vw, 2.5rem)
|
|
13
|
+
mobile: 1rem
|
|
14
|
+
sectionGap:
|
|
15
|
+
desktop: clamp(2rem, 4vw, 3.5rem)
|
|
16
|
+
mobile: 2rem
|
|
17
|
+
finalSectionBottom:
|
|
18
|
+
desktop: clamp(2rem, 4vw, 3.5rem)
|
|
19
|
+
mobile: 2rem
|
|
20
|
+
bodyToImages:
|
|
21
|
+
desktop: 1.25rem
|
|
22
|
+
mobile: 1rem
|
|
23
|
+
imageGap:
|
|
24
|
+
desktop: clamp(1.25rem, 3vw, 2.25rem)
|
|
25
|
+
mobile: 1.5rem
|
|
26
|
+
gallery:
|
|
27
|
+
width: 70vw
|
|
28
|
+
maxAvailableWidthPercent:
|
|
29
|
+
desktop: 100
|
|
30
|
+
mobile: 100
|
|
31
|
+
maxAvailableHeightPercent:
|
|
32
|
+
desktop: 72
|
|
33
|
+
mobile: 68
|
|
34
|
+
typography:
|
|
35
|
+
fontFamily: "Arial, 'Helvetica Neue', Helvetica, sans-serif"
|
|
36
|
+
preset: quiet-gallery
|
|
37
|
+
rhythm: normal
|
|
38
|
+
presentation:
|
|
39
|
+
backgroundColor: "#f8f5ee"
|
|
40
|
+
textColor: "#17201d"
|
|
41
|
+
frame:
|
|
42
|
+
colors: presentation
|
|
43
|
+
---
|
package/package.json
CHANGED
|
@@ -22,6 +22,9 @@ const inlineStyleReferenceRegex = /\[[^\]\n]+\]\{\.([a-z][a-z0-9-]*)\}/g;
|
|
|
22
22
|
const frontmatterDelimiterRegex = /^---\s*$/;
|
|
23
23
|
const knownContentTopLevelFrontmatterKeys = new Set(['title', 'description', 'navigation', 'presentation', 'frame', 'sections']);
|
|
24
24
|
const knownThemeTopLevelFrontmatterKeys = new Set(['navigation', 'layout', 'gallery', 'typography', 'presentation', 'frame']);
|
|
25
|
+
const frontmatterColorKeys = new Set(['backgroundColor', 'textColor', 'color']);
|
|
26
|
+
const quotedHexColorRegex = /^["']#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})["']$/;
|
|
27
|
+
const unquotedHexColorRegex = /^#(?:[0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/;
|
|
25
28
|
const knownNestedFrontmatterKeys = new Set([
|
|
26
29
|
'align',
|
|
27
30
|
'alt',
|
|
@@ -261,6 +264,79 @@ export const validateFrontmatterStructure = (frontmatter, addIssue, {
|
|
|
261
264
|
}
|
|
262
265
|
};
|
|
263
266
|
|
|
267
|
+
const stripInlineYamlComment = (value) => {
|
|
268
|
+
let quote = null;
|
|
269
|
+
|
|
270
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
271
|
+
const character = value[index];
|
|
272
|
+
const previous = value[index - 1];
|
|
273
|
+
|
|
274
|
+
if ((character === '"' || character === "'") && previous !== '\\') {
|
|
275
|
+
quote = quote === character ? null : quote ?? character;
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (character === '#' && quote === null && /\s/.test(previous ?? '')) {
|
|
280
|
+
return value.slice(0, index).trimEnd();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return value.trim();
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
export const validateFrontmatterColorValues = (frontmatter, addIssue, label = 'Frontmatter') => {
|
|
288
|
+
const lines = frontmatter.split(/\r?\n/);
|
|
289
|
+
const pathStack = [];
|
|
290
|
+
|
|
291
|
+
for (const [index, line] of lines.entries()) {
|
|
292
|
+
const lineNumber = index + 1;
|
|
293
|
+
const trimmed = line.trim();
|
|
294
|
+
|
|
295
|
+
if (!trimmed || trimmed.startsWith('#') || frontmatterDelimiterRegex.test(line)) {
|
|
296
|
+
continue;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const match = line.match(/^(\s*)([A-Za-z][A-Za-z0-9-]*):\s*(.*?)\s*$/);
|
|
300
|
+
if (!match) continue;
|
|
301
|
+
|
|
302
|
+
const indent = match[1].length;
|
|
303
|
+
const key = match[2];
|
|
304
|
+
const rawValue = match[3].trim();
|
|
305
|
+
|
|
306
|
+
while (pathStack.length > 0 && pathStack[pathStack.length - 1].indent >= indent) {
|
|
307
|
+
pathStack.pop();
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
const pathLabel = [...pathStack.map((entry) => entry.key), key].join('.');
|
|
311
|
+
|
|
312
|
+
if (!frontmatterColorKeys.has(key)) {
|
|
313
|
+
if (!rawValue) {
|
|
314
|
+
pathStack.push({ indent, key });
|
|
315
|
+
}
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const value = stripInlineYamlComment(rawValue);
|
|
320
|
+
|
|
321
|
+
if (quotedHexColorRegex.test(value)) continue;
|
|
322
|
+
|
|
323
|
+
if (unquotedHexColorRegex.test(rawValue)) {
|
|
324
|
+
addIssue({
|
|
325
|
+
severity: 'error',
|
|
326
|
+
message: `${label} line ${lineNumber} defines ${pathLabel} as an unquoted hex color. YAML treats "#" as the start of a comment unless the value is quoted.`,
|
|
327
|
+
fix: `Write it as ${key}: "#ffd84d" or another quoted hex color.`,
|
|
328
|
+
});
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
addIssue({
|
|
333
|
+
severity: 'error',
|
|
334
|
+
message: `${label} line ${lineNumber} defines ${pathLabel} with invalid color value ${rawValue || '(empty)'}.`,
|
|
335
|
+
fix: `Use a quoted hex color in #rgb, #rrggbb, or #rrggbbaa form, for example ${key}: "#ffd84d".`,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
};
|
|
339
|
+
|
|
264
340
|
export const validateContentFrontmatterStructure = (frontmatter, addIssue) =>
|
|
265
341
|
validateFrontmatterStructure(frontmatter, addIssue, {
|
|
266
342
|
knownTopLevelFrontmatterKeys: knownContentTopLevelFrontmatterKeys,
|
|
@@ -2,6 +2,7 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
import { parseYamlMapping } from './frontmatter-yaml.mjs';
|
|
3
3
|
import {
|
|
4
4
|
splitSiteFile,
|
|
5
|
+
validateFrontmatterColorValues,
|
|
5
6
|
validateFrontmatterIndentation,
|
|
6
7
|
validateThemeFrontmatterStructure,
|
|
7
8
|
} from './site-content.mjs';
|
|
@@ -22,6 +23,7 @@ export const readThemeConfig = async () => {
|
|
|
22
23
|
|
|
23
24
|
validateFrontmatterIndentation(frontmatter, (issue) => issues.push(issue));
|
|
24
25
|
validateThemeFrontmatterStructure(frontmatter, (issue) => issues.push(issue));
|
|
26
|
+
validateFrontmatterColorValues(frontmatter, (issue) => issues.push(issue), siteThemeLabel);
|
|
25
27
|
|
|
26
28
|
if (issues.length > 0) {
|
|
27
29
|
throw new Error([
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
readThemeFile,
|
|
21
21
|
toPosixPath,
|
|
22
22
|
validateContentFrontmatterStructure,
|
|
23
|
+
validateFrontmatterColorValues,
|
|
23
24
|
validateFrontmatterIndentation,
|
|
24
25
|
validateThemeFrontmatterStructure,
|
|
25
26
|
} from './lib/site-content.mjs';
|
|
@@ -394,6 +395,7 @@ try {
|
|
|
394
395
|
themeFrontmatter = (await readThemeFile(siteThemePath)).frontmatter;
|
|
395
396
|
validateFrontmatterIndentation(themeFrontmatter, addIssue);
|
|
396
397
|
validateThemeFrontmatterStructure(themeFrontmatter, addIssue);
|
|
398
|
+
validateFrontmatterColorValues(themeFrontmatter, addIssue, siteThemeLabel);
|
|
397
399
|
} catch (error) {
|
|
398
400
|
if (error?.code !== 'ENOENT') {
|
|
399
401
|
throw error;
|
|
@@ -415,6 +417,7 @@ for (const contentFile of contentFiles) {
|
|
|
415
417
|
const bodyLineOffset = frontmatter.split(/\r?\n/).length - 1;
|
|
416
418
|
validateFrontmatterIndentation(frontmatter, addIssue);
|
|
417
419
|
validateContentFrontmatterStructure(frontmatter, addIssue);
|
|
420
|
+
validateFrontmatterColorValues(frontmatter, addIssue, contentFile.contentLabel);
|
|
418
421
|
|
|
419
422
|
const frontmatterSections = getFrontmatterSections(frontmatter);
|
|
420
423
|
const frontmatterIds = new Set(frontmatterSections.map((section) => section.id));
|
|
@@ -204,6 +204,36 @@ test('content:check fails when Markdown uses undefined inline styles', async ()
|
|
|
204
204
|
});
|
|
205
205
|
});
|
|
206
206
|
|
|
207
|
+
const invalidInlineStyleColorTheme = `---
|
|
208
|
+
presentation:
|
|
209
|
+
inlineStyles:
|
|
210
|
+
yellow:
|
|
211
|
+
color: "#ffd844d"
|
|
212
|
+
---
|
|
213
|
+
`;
|
|
214
|
+
|
|
215
|
+
test('content:check explains invalid inline style colors', async () => {
|
|
216
|
+
await withTempProject({
|
|
217
|
+
site: `---
|
|
218
|
+
title: Invalid Inline Color
|
|
219
|
+
---
|
|
220
|
+
## Intro {#intro}
|
|
221
|
+
This has [yellow text]{.yellow}.
|
|
222
|
+
`,
|
|
223
|
+
theme: invalidInlineStyleColorTheme,
|
|
224
|
+
files: [],
|
|
225
|
+
}, async (root) => {
|
|
226
|
+
const result = runContentScript(root, ['--check']);
|
|
227
|
+
const output = getOutput(result);
|
|
228
|
+
|
|
229
|
+
assert.equal(result.status, 1, output);
|
|
230
|
+
assert.match(output, /^Content check failed\./m);
|
|
231
|
+
assert.match(output, /site\/theme\.md line 5 defines presentation\.inlineStyles\.yellow\.color with invalid color value "#ffd844d"\./);
|
|
232
|
+
assert.match(output, /Use a quoted hex color in #rgb, #rrggbb, or #rrggbbaa form, for example color: "#ffd84d"\./);
|
|
233
|
+
assert.doesNotMatch(output, /Inline style "\.yellow" is used in site\/content\.md but is not defined/);
|
|
234
|
+
});
|
|
235
|
+
});
|
|
236
|
+
|
|
207
237
|
const badWhitespaceSite = `---
|
|
208
238
|
presentation:
|
|
209
239
|
typography:
|
|
@@ -195,6 +195,7 @@ title: Package Check Site
|
|
|
195
195
|
description: Site used by package checks.
|
|
196
196
|
sections:
|
|
197
197
|
intro: {}
|
|
198
|
+
warning: {}
|
|
198
199
|
work: {}
|
|
199
200
|
---
|
|
200
201
|
|
|
@@ -207,6 +208,10 @@ image: package-check.svg
|
|
|
207
208
|
source: package check fixture
|
|
208
209
|
-->
|
|
209
210
|
|
|
211
|
+
## [**Warning**]{.highlight} {#warning}
|
|
212
|
+
|
|
213
|
+
This section verifies styled section headings.
|
|
214
|
+
|
|
210
215
|
## Work {#work}
|
|
211
216
|
|
|
212
217
|
This section keeps the package check independent from user-facing starter copy.
|
|
@@ -317,6 +322,18 @@ This route verifies that packaged norna sites can build route pages.
|
|
|
317
322
|
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
318
323
|
'class="inline-style inline-style-highlight" style="--inline-style-color: #ffd84d"',
|
|
319
324
|
);
|
|
325
|
+
await assertFileIncludes(
|
|
326
|
+
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
327
|
+
'<h2><span class="inline-style inline-style-highlight" style="--inline-style-color: #ffd84d"><strong>Warning</strong></span></h2>',
|
|
328
|
+
);
|
|
329
|
+
await assertFileIncludes(
|
|
330
|
+
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
331
|
+
'data-section-title="Warning"',
|
|
332
|
+
);
|
|
333
|
+
await assertFileIncludes(
|
|
334
|
+
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
335
|
+
'href="#warning">Warning</a>',
|
|
336
|
+
);
|
|
320
337
|
await assertFileIncludes(
|
|
321
338
|
path.join(siteProjectRoot, 'dist', 'index.html'),
|
|
322
339
|
'--section-background-color: #000000',
|
|
@@ -256,7 +256,7 @@ const sectionStyle = [
|
|
|
256
256
|
>
|
|
257
257
|
<div class="section-card">
|
|
258
258
|
<header class="section-header">
|
|
259
|
-
<HeadingTag
|
|
259
|
+
<HeadingTag set:html={section.titleHtml} />
|
|
260
260
|
</header>
|
|
261
261
|
|
|
262
262
|
<div class:list={['section-body', { 'section-body-has-gallery': hasMedia, 'section-body-text-only': !hasMedia }]}>
|
|
@@ -38,12 +38,13 @@ type SectionContentBlock =
|
|
|
38
38
|
export type ResolvedSection = SiteSectionMetadata & {
|
|
39
39
|
id: string;
|
|
40
40
|
title: string;
|
|
41
|
+
titleHtml: string;
|
|
41
42
|
contentBlocks: SectionContentBlock[];
|
|
42
43
|
};
|
|
43
44
|
|
|
44
45
|
const headingRegex = /<h2\b([^>]*)>([\s\S]*?)<\/h2>/gi;
|
|
45
46
|
const explicitHeadingIdRegex = /\s*\{#([a-z0-9-]+)\}\s*$/;
|
|
46
|
-
const inlineStyleReferenceRegex = /\[([
|
|
47
|
+
const inlineStyleReferenceRegex = /\[([\s\S]*?)\]\{\.([a-z][a-z0-9-]*)\}/g;
|
|
47
48
|
const markdownH2Regex = /^##\s+.*$/gm;
|
|
48
49
|
const imageProvenanceCommentRegex = /<!--\s*norna-image-provenance:[\s\S]*?-->/gi;
|
|
49
50
|
|
|
@@ -81,8 +82,14 @@ const getHeadingId = (attributes: string, headingHtml: string) => {
|
|
|
81
82
|
const getExplicitHeadingId = (headingHtml: string) =>
|
|
82
83
|
decodeHtmlEntities(stripTags(headingHtml)).trim().match(explicitHeadingIdRegex)?.[1];
|
|
83
84
|
|
|
85
|
+
const stripInlineStyleMarkers = (value: string) =>
|
|
86
|
+
value.replace(inlineStyleReferenceRegex, '$1');
|
|
87
|
+
|
|
84
88
|
const getHeadingTitle = (headingHtml: string) =>
|
|
85
|
-
decodeHtmlEntities(stripTags(headingHtml)).replace(explicitHeadingIdRegex, '').trim();
|
|
89
|
+
stripInlineStyleMarkers(decodeHtmlEntities(stripTags(headingHtml)).replace(explicitHeadingIdRegex, '').trim());
|
|
90
|
+
|
|
91
|
+
const getHeadingTitleHtml = (headingHtml: string, inlineStyles: InlineStyles | undefined) =>
|
|
92
|
+
prepareContentHtml(headingHtml.replace(explicitHeadingIdRegex, '').trim(), inlineStyles);
|
|
86
93
|
|
|
87
94
|
const applyInlineStyles = (html: string, inlineStyles: InlineStyles | undefined) =>
|
|
88
95
|
html.replace(inlineStyleReferenceRegex, (_match, text: string, styleName: string) => {
|
|
@@ -209,6 +216,7 @@ export const getSectionsContent = (
|
|
|
209
216
|
...(sectionMetadata[id] ?? {}),
|
|
210
217
|
id,
|
|
211
218
|
title,
|
|
219
|
+
titleHtml: getHeadingTitleHtml(headingHtml, inlineStyles),
|
|
212
220
|
contentBlocks: resolveContentBlocks(content, rawSections.get(id) ?? '', page, id, inlineStyles),
|
|
213
221
|
});
|
|
214
222
|
}
|