Files
windwiderstand/src/routes/[...slug]/+page.server.ts
T
Peter Meier a139372ce2
Deploy / verify (push) Failing after 14s
Deploy / deploy (push) Has been skipped
feat(image): enhance image handling with focal points and responsive support
- Added support for extracting focal points and alt text from CMS image fields.
- Introduced responsive image handling in TopBanner component, allowing for AVIF/WebP sources and LQIP placeholders.
- Updated image transformation functions to accommodate focal point parameters for better cropping.
- Refactored image URL resolution logic across various components and routes to utilize new image field structure.
2026-04-18 00:42:33 +02:00

95 lines
3.1 KiB
TypeScript

import type { PageServerLoad } from './$types';
import { getPageBySlug } from '$lib/cms';
import {
extractCmsImageField,
resolveContentImages,
resolveImageUrls,
} from '$lib/rusty-image';
import { buildResponsiveImage, type ResponsiveImage } from '$lib/rusty-image.server';
import {
resolvePostOverviewBlocks,
resolveSearchableTextBlocks,
resolveCalendarBlocks,
} from '$lib/blog-utils';
import { PAGE_RESOLVE } from '$lib/constants';
import { error } from '@sveltejs/kit';
const BANNER_WIDTHS = [640, 960, 1280, 1600, 1920, 2400];
function focalToCss(focal: { x: number; y: number } | undefined): string | null {
if (!focal) return null;
return `${(focal.x * 100).toFixed(2)}% ${(focal.y * 100).toFixed(2)}%`;
}
export const load: PageServerLoad = async ({ params, locals }) => {
const { slug } = params;
const translations = locals.translations ?? {};
const page = await getPageBySlug(slug, {
locale: 'de',
resolve: PAGE_RESOLVE,
});
if (!page) {
error(404, 'Seite nicht gefunden');
}
await resolveContentImages(page);
const tagsMap = await resolvePostOverviewBlocks(page);
await resolveSearchableTextBlocks(page, tagsMap);
await resolveCalendarBlocks(page);
// Resolve top banner if present
let topBannerResolvedImages: string[] = [];
let topBannerResponsive: ResponsiveImage | null = null;
let topBannerFocalCss: string | null = null;
const bannerRaw = (page as { topFullwidthBanner?: unknown }).topFullwidthBanner;
if (bannerRaw && typeof bannerRaw === 'object' && 'image' in bannerRaw) {
const bannerImages = Array.isArray((bannerRaw as { image?: unknown }).image)
? ((bannerRaw as { image?: unknown[] }).image ?? [])
: (bannerRaw as { image?: unknown }).image != null
? [(bannerRaw as { image?: unknown }).image]
: [];
if (bannerImages.length > 0) {
const primary = extractCmsImageField(bannerImages[0]);
if (primary?.url) {
topBannerFocalCss = focalToCss(primary.focal);
topBannerResponsive = await buildResponsiveImage(primary.url, {
widths: BANNER_WIDTHS,
fit: 'contain',
formats: ['avif', 'webp', 'jpeg'],
quality: 78,
focal: primary.focal,
});
}
topBannerResolvedImages = await resolveImageUrls(bannerImages as string[], {
width: 2000,
height: 750,
fit: 'cover',
format: 'webp',
});
}
}
return {
page,
translations,
seoTitle: page.seoTitle ?? page.headline ?? page.linkName ?? slug,
seoDescription: page.seoDescription ?? page.subheadline ?? '',
breadcrumbItems: [
{ href: '/', label: 'Start' },
{ label: page.headline ?? page.linkName ?? page.slug ?? page._slug ?? slug },
],
topBanner: bannerRaw
? {
banner: bannerRaw as import('$lib/cms').FullwidthBannerEntry | null,
resolvedImages: topBannerResolvedImages,
responsive: topBannerResponsive,
focalCss: topBannerFocalCss,
headline: page.headline ?? undefined,
subheadline: page.subheadline ?? undefined,
}
: null,
};
};