import type { Page as CmsPage } from "../types/cms/Page"; import type { HTMLSkeleton } from "../types/cms/Html"; import type { MarkdownSkeleton } from "../types/cms/Markdown"; import type { ComponentIframeSkeleton } from "../types/cms/Iframe"; import type { ImageGallerySkeleton } from "../types/cms/ImageGallery"; import type { ComponentImageSkeleton } from "../types/cms/Image"; import type { QuoteSkeleton } from "../types/cms/Quote"; import type { ComponentYoutubeVideoSkeleton } from "../types/cms/YoutubeVideo"; import type { ComponentHeadlineSkeleton } from "../types/cms/Headline"; import type { Page, ContentItem, ContentRow } from "../types/page"; import { ContentType } from "../types/cms/ContentType.enum"; type ContentEntry = | HTMLSkeleton | MarkdownSkeleton | ComponentIframeSkeleton | ImageGallerySkeleton | ComponentImageSkeleton | QuoteSkeleton | ComponentYoutubeVideoSkeleton | ComponentHeadlineSkeleton; /** * Mapper für Page-Transformationen * Konvertiert CMS-Typen zu unseren Domain-Typen */ export class PageMapper { /** * Strategy Pattern: Map mit Mapper-Funktionen für jeden Content-Type */ private static contentMappers = new Map< ContentType, (entry: ContentEntry) => ContentItem | null >([ [ ContentType.html, (entry) => { const htmlEntry = entry as HTMLSkeleton; return { type: "html", name: htmlEntry.fields.id || "", html: htmlEntry.fields.html, layout: htmlEntry.fields.layout, }; }, ], [ ContentType.markdown, (entry) => { const markdownEntry = entry as MarkdownSkeleton; return { type: "markdown", name: markdownEntry.fields.name, content: markdownEntry.fields.content, layout: markdownEntry.fields.layout, alignment: markdownEntry.fields.alignment, }; }, ], [ ContentType.iframe, (entry) => { const iframeEntry = entry as ComponentIframeSkeleton; return { type: "iframe", name: iframeEntry.fields.name, content: iframeEntry.fields.content, iframe: iframeEntry.fields.iframe, overlayImageUrl: iframeEntry.fields.overlayImage?.fields.file.url, layout: iframeEntry.fields.layout, }; }, ], [ ContentType.imgGallery, (entry) => { const galleryEntry = entry as ImageGallerySkeleton; return { type: "imageGallery", name: galleryEntry.fields.name, images: galleryEntry.fields.images.map((img) => ({ url: img.fields.file.url, title: img.fields.title, description: img.fields.description, })), description: galleryEntry.fields.description, layout: galleryEntry.fields.layout, }; }, ], [ ContentType.image, (entry) => { const imageEntry = entry as ComponentImageSkeleton; return { type: "image", name: imageEntry.fields.name, imageUrl: imageEntry.fields.image.fields.file.url, caption: imageEntry.fields.caption, maxWidth: imageEntry.fields.maxWidth, aspectRatio: imageEntry.fields.aspectRatio, layout: imageEntry.fields.layout, }; }, ], [ ContentType.quote, (entry) => { const quoteEntry = entry as QuoteSkeleton; return { type: "quote", quote: quoteEntry.fields.quote, author: quoteEntry.fields.author, variant: quoteEntry.fields.variant, layout: quoteEntry.fields.layout, }; }, ], [ ContentType.youtubeVideo, (entry) => { const videoEntry = entry as ComponentYoutubeVideoSkeleton; return { type: "youtubeVideo", id: videoEntry.fields.id, youtubeId: videoEntry.fields.youtubeId, params: videoEntry.fields.params, title: videoEntry.fields.title, description: videoEntry.fields.description, layout: videoEntry.fields.layout, }; }, ], [ ContentType.headline, (entry) => { const headlineEntry = entry as ComponentHeadlineSkeleton; return { type: "headline", internal: headlineEntry.fields.internal, text: headlineEntry.fields.text, tag: headlineEntry.fields.tag, align: headlineEntry.fields.align, layout: headlineEntry.fields.layout, }; }, ], ]); /** * Mappt ein Contentful Content-Item zu unserem ContentItem * Verwendet Strategy Pattern für wartbaren Code */ private static mapContentItem(entry: ContentEntry): ContentItem | null { if (!entry.contentTypeId || !entry.fields) { return null; } const mapper = this.contentMappers.get(entry.contentTypeId); if (!mapper) { return null; } return mapper(entry); } /** * Mappt eine CMS Content-Row zu unserer ContentRow */ private static mapContentRow( content: ContentEntry[], justifyContent: string, alignItems: string ): ContentRow | undefined { if (!content || content.length === 0) { return undefined; } const mappedContent = content .map((entry) => this.mapContentItem(entry)) .filter((item): item is ContentItem => item !== null); if (mappedContent.length === 0) { return undefined; } return { justifyContent: justifyContent as ContentRow["justifyContent"], alignItems: alignItems as ContentRow["alignItems"], content: mappedContent, }; } static fromCms(cmsPage: CmsPage): Page { return { slug: cmsPage.slug, name: cmsPage.name, linkName: cmsPage.linkName, headline: cmsPage.headline, subheadline: cmsPage.subheadline, seoTitle: cmsPage.seoTitle, seoMetaRobots: cmsPage.seoMetaRobots, seoDescription: cmsPage.seoDescription, topFullwidthBanner: cmsPage.topFullwidthBanner ? { name: cmsPage.topFullwidthBanner.fields.name, variant: cmsPage.topFullwidthBanner.fields.variant, headline: cmsPage.topFullwidthBanner.fields.headline, subheadline: cmsPage.topFullwidthBanner.fields.subheadline, text: cmsPage.topFullwidthBanner.fields.text, imageUrl: cmsPage.topFullwidthBanner.fields.img.fields.file.url, } : undefined, row1: this.mapContentRow( cmsPage.row1Content, cmsPage.row1JustifyContent, cmsPage.row1AlignItems ), row2: this.mapContentRow( cmsPage.row2Content, cmsPage.row2JustifyContent, cmsPage.row2AlignItems ), row3: this.mapContentRow( cmsPage.row3Content, cmsPage.row3JustifyContent, cmsPage.row3AlignItems ), }; } static fromCmsArray(cmsPages: CmsPage[]): Page[] { return cmsPages.map((page) => this.fromCms(page)); } }