@taprootio/site-authoring 0.1.1 → 0.2.0
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/package.json +2 -2
- package/src/appearance-contract.js +39 -1
- package/src/constants.js +1 -1
- package/src/presentation-reference.js +26 -2
- package/src/settings-catalog.js +5 -0
- package/src/verbs/validate.js +34 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taprootio/site-authoring",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Taproot site authoring CLI: sign in once in the browser, then pull, validate, push, preview, approve, and deploy a site from the terminal",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "ISC",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"README.md"
|
|
28
28
|
],
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@taprootio/espalier": "4.
|
|
30
|
+
"@taprootio/espalier": "4.7.0"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"test": "node --test",
|
|
@@ -40,6 +40,20 @@ export const APPEARANCE_SCALAR_FIELDS = Object.freeze([
|
|
|
40
40
|
type: "string",
|
|
41
41
|
default: "",
|
|
42
42
|
}),
|
|
43
|
+
Object.freeze({
|
|
44
|
+
settingsType: SETTINGS_TYPE_TAPROOT_STYLES,
|
|
45
|
+
name: "fontMenu",
|
|
46
|
+
path: "taproot-styles.fontMenu",
|
|
47
|
+
type: "string",
|
|
48
|
+
default: "",
|
|
49
|
+
}),
|
|
50
|
+
Object.freeze({
|
|
51
|
+
settingsType: SETTINGS_TYPE_TAPROOT_STYLES,
|
|
52
|
+
name: "fontWeightMenu",
|
|
53
|
+
path: "taproot-styles.fontWeightMenu",
|
|
54
|
+
type: "string",
|
|
55
|
+
default: "",
|
|
56
|
+
}),
|
|
43
57
|
...["lightLogoId", "darkLogoId", "lightCanvasImageId", "darkCanvasImageId"].map((name) =>
|
|
44
58
|
Object.freeze({
|
|
45
59
|
settingsType: SETTINGS_TYPE_TAPROOT_STYLES,
|
|
@@ -72,9 +86,33 @@ export const APPEARANCE_SCALAR_FIELDS = Object.freeze([
|
|
|
72
86
|
name: "headerLayout",
|
|
73
87
|
path: "site-header.headerLayout",
|
|
74
88
|
type: "enum",
|
|
75
|
-
values: Object.freeze(["standard", "centered-brand", "minimal"]),
|
|
89
|
+
values: Object.freeze(["standard", "centered-brand", "minimal", "centered-menu"]),
|
|
76
90
|
default: "standard",
|
|
77
91
|
}),
|
|
92
|
+
Object.freeze({
|
|
93
|
+
settingsType: SETTINGS_TYPE_SITE_HEADER,
|
|
94
|
+
name: "headerWidth",
|
|
95
|
+
path: "site-header.headerWidth",
|
|
96
|
+
type: "enum",
|
|
97
|
+
values: Object.freeze(["contained", "wide"]),
|
|
98
|
+
default: "contained",
|
|
99
|
+
}),
|
|
100
|
+
Object.freeze({
|
|
101
|
+
settingsType: SETTINGS_TYPE_SITE_HEADER,
|
|
102
|
+
name: "navDrawerStyle",
|
|
103
|
+
path: "site-header.navDrawerStyle",
|
|
104
|
+
type: "enum",
|
|
105
|
+
values: Object.freeze(["panel", "full-screen"]),
|
|
106
|
+
default: "full-screen",
|
|
107
|
+
}),
|
|
108
|
+
Object.freeze({
|
|
109
|
+
settingsType: SETTINGS_TYPE_SITE_HEADER,
|
|
110
|
+
name: "navDrawerTransition",
|
|
111
|
+
path: "site-header.navDrawerTransition",
|
|
112
|
+
type: "enum",
|
|
113
|
+
values: Object.freeze(["fade", "slide-down", "slide-up", "slide-left", "slide-right"]),
|
|
114
|
+
default: "fade",
|
|
115
|
+
}),
|
|
78
116
|
Object.freeze({
|
|
79
117
|
settingsType: SETTINGS_TYPE_SITE_HEADER,
|
|
80
118
|
name: "brandText",
|
package/src/constants.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const CLI_NAME = "@taprootio/site-authoring";
|
|
2
|
-
export const CLI_VERSION = "0.
|
|
2
|
+
export const CLI_VERSION = "0.2.0";
|
|
3
3
|
export const CLI_BINARY_NAME = "taproot-site";
|
|
4
4
|
export const RESULT_SCHEMA_VERSION = 1;
|
|
5
5
|
export const CONFIG_FILE_NAME = "taproot-site.json";
|
|
@@ -57,6 +57,12 @@ function getEspalierVersion() {
|
|
|
57
57
|
|
|
58
58
|
const OPTIONAL_THEME_FIELDS = Object.freeze([
|
|
59
59
|
"explicitMappingTokens",
|
|
60
|
+
// Espalier 4.7.0 menu typography: documented so the theme help covers the
|
|
61
|
+
// installed contract, optional so pulled baselines that predate it stay
|
|
62
|
+
// complete (the server seeds and requires the fields listed in
|
|
63
|
+
// DefaultSiteTheme.RequiredProperties only).
|
|
64
|
+
"fontMenu",
|
|
65
|
+
"fontWeightMenu",
|
|
60
66
|
"boxBackgroundImage",
|
|
61
67
|
"boxBackgroundImageOpacity",
|
|
62
68
|
"vellumOpacity",
|
|
@@ -201,8 +207,12 @@ const THEME_GROUPS = Object.freeze([
|
|
|
201
207
|
...["fontBody", "fontHeadings", "fontBrand", "fontMonospace"].map((name) =>
|
|
202
208
|
themeField(name, "CSS font-family; empty inherits the consuming surface's fallback.")
|
|
203
209
|
),
|
|
204
|
-
|
|
205
|
-
|
|
210
|
+
themeField(
|
|
211
|
+
"fontMenu",
|
|
212
|
+
"CSS font-family for navigation menu items and group labels; empty falls back to fontBody, never to headings.",
|
|
213
|
+
),
|
|
214
|
+
...["fontWeightBody", "fontWeightHeadings", "fontWeightBrand", "fontWeightMonospace", "fontWeightMenu"].map(
|
|
215
|
+
(name) => themeField(name, "CSS font-weight: 1–1000, normal/bold/lighter/bolder, or a CSS-wide keyword."),
|
|
206
216
|
),
|
|
207
217
|
themeField("rootFontSize", "Root font size in px.", { minimum: 1, warningMaximum: 100 }),
|
|
208
218
|
themeField("typeRatio", "Modular type-scale ratio.", { exclusiveMinimum: 1, warningMaximum: 1.3 }),
|
|
@@ -330,6 +340,18 @@ export function getAppearanceReference() {
|
|
|
330
340
|
"Use site-owned image ids retained from pull or returned by media upload. URLs are server projections, never authored inputs.",
|
|
331
341
|
logoContract:
|
|
332
342
|
"lightLogoId and darkLogoId are the current scheme-specific logo fields; there is no separate compact-logo setting.",
|
|
343
|
+
headerWidthContract:
|
|
344
|
+
"headerWidth 'contained' keeps the brand and the header buttons at the page content edges; "
|
|
345
|
+
+ "'wide' moves them to the viewport edges. Pages with root-band components (image-banner) span the viewport, "
|
|
346
|
+
+ "so full-bleed designs usually pair headerWidth 'wide' with headerLayout 'centered-menu', which keeps the "
|
|
347
|
+
+ "brand left, the buttons right, and the navigation centered. validate prints a hint, never a failure, when a "
|
|
348
|
+
+ "root-band page ships with a contained header.",
|
|
349
|
+
menuContract:
|
|
350
|
+
"navDrawerStyle 'full-screen' (the published default) opens the mobile menu over the whole viewport with the "
|
|
351
|
+
+ "brand centered above large centered items and navDrawerTransition choosing how it appears (fade, slide-down, "
|
|
352
|
+
+ "slide-up, slide-left, or slide-right); 'panel' keeps the side panel, which exists for application-style "
|
|
353
|
+
+ "menus with many groups. fontMenu and fontWeightMenu set the navigation face the way fontBrand sets the "
|
|
354
|
+
+ "brand; an empty fontMenu falls back to the body font, never to headings.",
|
|
333
355
|
mutationOrder: [
|
|
334
356
|
"fresh footer read plus concurrency-protected ten-color overlay",
|
|
335
357
|
"default scheme, brand fonts and assets, favicon, and header scalars",
|
|
@@ -577,6 +599,8 @@ export function formatPresentationReference(reference) {
|
|
|
577
599
|
+ "\n\nRead-only server projections:\n"
|
|
578
600
|
+ reference.readOnlyProjections.map((field) => " " + field.file + " :: " + field.path).join("\n")
|
|
579
601
|
+ "\n\nImages: " + reference.imageReferences + "\nLogos: " + reference.logoContract
|
|
602
|
+
+ "\nHeader width: " + reference.headerWidthContract
|
|
603
|
+
+ "\nMobile menu and menu font: " + reference.menuContract
|
|
580
604
|
+ "\nFooter: " + reference.footerBoundary
|
|
581
605
|
+ "\nFooter content guard: " + reference.footerContentGuard + "\n\nMutation order (non-atomic):\n"
|
|
582
606
|
+ reference.mutationOrder.map((step, index) => " " + (index + 1) + ". " + step).join("\n")
|
package/src/settings-catalog.js
CHANGED
|
@@ -58,6 +58,8 @@ export const SETTINGS_GROUPS = Object.freeze([
|
|
|
58
58
|
"defaultScheme",
|
|
59
59
|
"fontBrand",
|
|
60
60
|
"fontWeightBrand",
|
|
61
|
+
"fontMenu",
|
|
62
|
+
"fontWeightMenu",
|
|
61
63
|
"lightLogoId",
|
|
62
64
|
"lightLogoUrl",
|
|
63
65
|
"darkLogoId",
|
|
@@ -86,6 +88,9 @@ export const SETTINGS_GROUPS = Object.freeze([
|
|
|
86
88
|
fields: Object.freeze([
|
|
87
89
|
...stringFields(
|
|
88
90
|
"headerLayout",
|
|
91
|
+
"headerWidth",
|
|
92
|
+
"navDrawerStyle",
|
|
93
|
+
"navDrawerTransition",
|
|
89
94
|
"brandText",
|
|
90
95
|
"brandColor",
|
|
91
96
|
"logoAlt",
|
package/src/verbs/validate.js
CHANGED
|
@@ -2,7 +2,7 @@ import { lstat, realpath } from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
|
|
4
4
|
import { NAVIGATION_MAXIMUM_DEPTH, TEMPLATE_TYPE_FREE_FORM } from "../api.js";
|
|
5
|
-
import { sharedThemeContextNames } from "../content/free-form-sections.js";
|
|
5
|
+
import { freeFormRootPresentation, sharedThemeContextNames } from "../content/free-form-sections.js";
|
|
6
6
|
import { VERB_VALIDATE } from "../constants.js";
|
|
7
7
|
import { isCanonicalUuid, SiteAuthoringError } from "../errors.js";
|
|
8
8
|
import { FOOTER_SETTINGS_FILE } from "../footer-workspace.js";
|
|
@@ -340,6 +340,34 @@ function validatePageImageReferences(value, field, knownImageIds, deliveryOrigin
|
|
|
340
340
|
}
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
+
/** Whether a validated page document places a root-band component at its root. */
|
|
344
|
+
function pageUsesRootBand(document) {
|
|
345
|
+
return Array.isArray(document?.content)
|
|
346
|
+
&& document.content.some((node) => freeFormRootPresentation(node)?.rootPlacement === "root-band");
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Advisory only: a root-band component spans the viewport, so a header whose
|
|
351
|
+
* brand and buttons stop at the content edges usually reads as misaligned
|
|
352
|
+
* beside it. The wide header with the centered menu is the recommended pair,
|
|
353
|
+
* never a requirement, so this is a hint in the result and on stderr, not a
|
|
354
|
+
* failure (TR00697).
|
|
355
|
+
*/
|
|
356
|
+
function headerWidthHints(headerWidth, rootBandPages) {
|
|
357
|
+
if (rootBandPages.length === 0 || headerWidth === "wide") return [];
|
|
358
|
+
const pages = boundedList(rootBandPages, MAXIMUM_REPORTED);
|
|
359
|
+
return [{
|
|
360
|
+
code: "header.width_contained_with_root_band",
|
|
361
|
+
message: `${rootBandPages.length} page(s) use a full-bleed root-band component while site-header.headerWidth is `
|
|
362
|
+
+ `'${headerWidth}'. Full-bleed designs usually read better with headerWidth 'wide' (brand and buttons at the `
|
|
363
|
+
+ "viewport edges) paired with headerLayout 'centered-menu'.",
|
|
364
|
+
setting: "site-header.headerWidth",
|
|
365
|
+
suggested: { headerWidth: "wide", headerLayout: "centered-menu" },
|
|
366
|
+
pages: pages.items,
|
|
367
|
+
...(pages.truncated ? { pagesTruncated: true } : {}),
|
|
368
|
+
}];
|
|
369
|
+
}
|
|
370
|
+
|
|
343
371
|
export async function validateFixture(invocation = {}) {
|
|
344
372
|
const onProgress = typeof invocation.onProgress === "function" ? invocation.onProgress : () => {};
|
|
345
373
|
const fixtureRoot = await resolveFixtureRoot(invocation.cwd ?? process.cwd(), invocation.fixturePath);
|
|
@@ -377,6 +405,7 @@ export async function validateFixture(invocation = {}) {
|
|
|
377
405
|
fail("fixture.page_untracked", `Page source '${untracked}' is not bound by ${FIXTURE_MANIFEST_FILE_NAME}.`, untracked);
|
|
378
406
|
}
|
|
379
407
|
const validatedPages = [];
|
|
408
|
+
const rootBandPages = [];
|
|
380
409
|
for (const file of pageFiles) {
|
|
381
410
|
onProgress(`Validating '${file}'.`);
|
|
382
411
|
const entry = manifestByFile.get(file);
|
|
@@ -404,7 +433,10 @@ export async function validateFixture(invocation = {}) {
|
|
|
404
433
|
}
|
|
405
434
|
validatePageImageReferences(page.document, file, imageIds, deliveryOrigins);
|
|
406
435
|
validatedPages.push({ file, path: page.pagePath });
|
|
436
|
+
if (pageUsesRootBand(page.document)) rootBandPages.push(file);
|
|
407
437
|
}
|
|
438
|
+
const hints = headerWidthHints(presentation.header.headerWidth, rootBandPages);
|
|
439
|
+
for (const hint of hints) onProgress(`Hint: ${hint.message}`);
|
|
408
440
|
|
|
409
441
|
onProgress("Validating navigation shape and local PAGE targets.");
|
|
410
442
|
const navigation = validateNavigationWorkspaceDocument(
|
|
@@ -459,6 +491,7 @@ export async function validateFixture(invocation = {}) {
|
|
|
459
491
|
count: presentation.themes.warningCount,
|
|
460
492
|
...(presentation.themes.warningsTruncated ? { truncated: true } : {}),
|
|
461
493
|
},
|
|
494
|
+
hints,
|
|
462
495
|
proves: [
|
|
463
496
|
"fixture structure and bounded files",
|
|
464
497
|
"page content and named theme contexts",
|