feat(static-pages): extend data model with route/enabled/status/media/SEO
Milestone 1 of the Static Pages Module sprint. - StaticPageConfig / ContentPage / ContentPageBootstrapInput gain: explicit editable route (defaults from slug, independently overridable), enabled (master on/off), status: 'draft'|'published' (per-page publish lifecycle, independent of the whole-bootstrap draft/publish cycle), customTemplate, heroImage/thumbnail/gallery, and seo.robots. - ContentPageService: normalizePage/normalizePages default missing enabled/status to enabled+published so existing bootstrap data never gets silently un-published; only the editor's createPage() opts a brand-new page into 'draft'. Legacy array-format pages get the same treatment. - resolvePage now returns null (storefront 404) for a disabled or draft page, regardless of whether the surrounding bootstrap itself is published - affects the storefront static-page route AND the auto-generated footer nav group (both go through this same resolver), which is the correct behavior. - validatePages extended: duplicateRoutes (route can now diverge from slug), invalidHtml, invalidSeo (canonical/ogImage URL shape, known robots tokens). - New schema/validators/primitives.validateHtml: stack-based tag-balance check (void/self-closing elements skipped, comments stripped). Caught and fixed a real bug during its own spec run: the initial implementation popped the stack back to the nearest matching ancestor on a mismatched closing tag, which silently swallowed a genuinely unclosed inner tag instead of flagging it - now a closing tag must match the top of the stack exactly. - toBootstrapRecord serializes the new fields; visible mirrors enabled so any reader of the older field name stays truthful. - Specs: content-page.service.spec.ts (new), primitives.spec.ts (validateHtml). Gate: tsc --noEmit, npm test (57/57), arch:check, build all green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -8,6 +8,7 @@ export interface ContentPageSeoConfig {
|
||||
ogTitle?: string;
|
||||
ogDescription?: string;
|
||||
ogImage?: string;
|
||||
robots?: string;
|
||||
}
|
||||
|
||||
export interface ContentPageTranslation {
|
||||
@@ -16,9 +17,13 @@ export interface ContentPageTranslation {
|
||||
seo?: ContentPageSeoConfig;
|
||||
}
|
||||
|
||||
export type ContentPageStatus = 'draft' | 'published';
|
||||
|
||||
export interface ContentPage {
|
||||
id: string;
|
||||
slug: string;
|
||||
/** Editable independently of `slug`; defaults from it but may diverge (e.g. legacy redirects). */
|
||||
route: string;
|
||||
title: string;
|
||||
icon?: string;
|
||||
order: number;
|
||||
@@ -34,11 +39,20 @@ export interface ContentPage {
|
||||
footerGroup?: string;
|
||||
translations: Record<string, ContentPageTranslation>;
|
||||
seo?: ContentPageSeoConfig;
|
||||
/** Master on/off switch; a disabled page never resolves on the storefront. */
|
||||
enabled: boolean;
|
||||
/** Per-page publish lifecycle, independent of the whole-bootstrap draft/publish cycle. */
|
||||
status: ContentPageStatus;
|
||||
customTemplate?: string;
|
||||
heroImage?: string;
|
||||
thumbnail?: string;
|
||||
gallery?: string[];
|
||||
}
|
||||
|
||||
export interface ContentPageBootstrapInput {
|
||||
id: string;
|
||||
slug: string;
|
||||
route?: string;
|
||||
title?: string | LocalizedTextContent;
|
||||
showInFooter?: boolean;
|
||||
showInHeader?: boolean;
|
||||
@@ -55,4 +69,10 @@ export interface ContentPageBootstrapInput {
|
||||
translations?: Record<string, ContentPageTranslation>;
|
||||
html?: string | LocalizedHtmlContent;
|
||||
seo?: ContentPageSeoConfig | { title?: string | LocalizedTextContent; description?: string | LocalizedTextContent; keywords?: string; canonical?: string; ogTitle?: string | LocalizedTextContent; ogDescription?: string | LocalizedTextContent; ogImage?: string; robots?: string; metaTags?: Array<{ name?: string; property?: string; content: string }>; };
|
||||
enabled?: boolean;
|
||||
status?: ContentPageStatus;
|
||||
customTemplate?: string;
|
||||
heroImage?: string;
|
||||
thumbnail?: string;
|
||||
gallery?: string[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user