import type { LayoutServerLoad } from './$types'; export const trailingSlash = 'always'; import { getNavigationByKey, NavigationKeys, getPages, getPosts, getFooterBySlug, getPageConfigBySlug, } from '$lib/cms'; import type { NavLinkEntry } from '$lib/cms'; import { ensureTransformedImage } from '$lib/rusty-image'; import { DEFAULT_SOCIAL_IMAGE_URL, ROW_RESOLVE, SITE_NAME, SOCIAL_IMAGE_TRANSFORM, } from '$lib/constants'; import { env } from '$env/dynamic/public'; interface NavLink { href: string; label: string; } interface SocialLink { href: string; icon: string; label?: string; } /** Führenden Slash ignorieren (CMS liefert z. B. "slug": "/about"). */ function normalizeSlug(s: string | undefined): string { return (s ?? '').replace(/^\//, '').trim(); } function getSlugFromEntry(entry: NavLinkEntry): string { if (typeof entry === 'string') return entry; const e = entry as { _slug?: string; slug?: string }; return e.slug ?? e._slug ?? ''; } export const load: LayoutServerLoad = async ({ locals }) => { const translations = locals.translations ?? {}; // Header: Navigation laden let headerLinks: NavLink[] = []; try { const nav = await getNavigationByKey(NavigationKeys.header, { locale: 'de', resolve: 'links', }); const rawLinks = nav?.links ?? []; const [pages, posts] = await Promise.all([getPages(), getPosts()]); const slugToLabel = new Map(); const slugToPageHref = new Map(); for (const p of pages) { const label = p.linkName ?? p.name ?? p.headline ?? p.slug ?? p._slug ?? ''; const canonicalSlug = normalizeSlug(p.slug ?? p._slug) || 'home'; const keySlug = normalizeSlug(p.slug); const keyUnderscore = normalizeSlug(p._slug); if (keySlug) { slugToLabel.set(keySlug, label); slugToPageHref.set(keySlug, canonicalSlug); } if (keyUnderscore && keyUnderscore !== keySlug) { slugToLabel.set(keyUnderscore, label); slugToPageHref.set(keyUnderscore, canonicalSlug); } } const slugToPostHref = new Map(); for (const p of posts) { const label = p.linkName ?? p.headline ?? p.slug ?? p._slug ?? ''; const urlSlug = normalizeSlug(p.slug ?? p._slug); const canonical = urlSlug ? `/post/${encodeURIComponent(urlSlug)}/` : ''; const keySlug = normalizeSlug(p.slug); const keyUnderscore = normalizeSlug(p._slug); if (keySlug) { slugToLabel.set(keySlug, label); slugToPostHref.set(keySlug, canonical); } if (keyUnderscore && keyUnderscore !== keySlug) { slugToLabel.set(keyUnderscore, label); slugToPostHref.set(keyUnderscore, canonical); } } for (const entry of rawLinks) { const asObj = typeof entry === 'object' && entry !== null ? (entry as { url?: string; linkName?: string; name?: string }) : null; if (asObj?.url) { const href = asObj.url.startsWith('/') || asObj.url.startsWith('http') ? asObj.url : `/${asObj.url.replace(/^\//, '')}`; const label = asObj.linkName ?? asObj.name ?? asObj.url ?? ''; headerLinks.push({ href, label }); continue; } const raw = getSlugFromEntry(entry); const slug = normalizeSlug(raw) || 'home'; const label = slugToLabel.get(slug) ?? slug; const postHref = slugToPostHref.get(slug); const pageSlug = slugToPageHref.get(slug) ?? slug; const hrefSlug = normalizeSlug(pageSlug) || 'home'; const pageHref = hrefSlug === 'home' ? '/' : '/' + hrefSlug .split('/') .filter(Boolean) .map((seg) => encodeURIComponent(seg)) .join('/') + '/'; const href = postHref ?? pageHref; headerLinks.push({ href, label }); } } catch { // CMS nicht erreichbar } // Social-Media-Links let socialLinks: SocialLink[] = []; try { const socialNav = await getNavigationByKey(NavigationKeys.socialMedia, { locale: 'de', resolve: 'all', }); const rawSocialLinks = socialNav?.links ?? []; for (const entry of rawSocialLinks) { const asObj = typeof entry === 'object' && entry !== null ? (entry as { url?: string; icon?: string; linkName?: string; name?: string }) : null; if (!asObj?.url) continue; const href = asObj.url.startsWith('/') || asObj.url.startsWith('http') ? asObj.url : `/${asObj.url.replace(/^\//, '')}`; const icon = asObj.icon ?? 'mdi:link'; const label = asObj.linkName ?? asObj.name ?? ''; socialLinks.push({ href, icon, label }); } } catch { /* Social-Links optional */ } // Footer laden (mit Row-Resolve) let footerData = null; try { footerData = await getFooterBySlug('footer-main', { locale: 'de', resolve: ROW_RESOLVE, }); } catch { // CMS nicht erreichbar } // Logo und SEO-Templates aus page_config let logoUrl: string | null = null; let logoSvgHtml: string | null = null; let pageConfig = null; try { pageConfig = (await getPageConfigBySlug('page-config-default', { locale: 'de', resolve: 'logo', })) ?? (await getPageConfigBySlug('default', { locale: 'de', resolve: 'logo' })); const rawLogo = pageConfig?.logo; const rawSrc = typeof rawLogo === 'string' ? rawLogo : (rawLogo as { src?: string } | undefined)?.src ?? null; if (rawSrc) { const cmsBase = (env.PUBLIC_CMS_URL || 'http://localhost:3000').replace(/\/$/, ''); // Absolute URL aufbauen const u = rawSrc.startsWith('//') ? `https:${rawSrc}` : rawSrc.startsWith('/') ? `${cmsBase}${rawSrc}` : rawSrc.startsWith('http') ? rawSrc : `${cmsBase}/api/assets/${rawSrc}`; const isSvg = u.split('?')[0].toLowerCase().endsWith('.svg'); if (isSvg) { // SVGs können nicht transformiert werden — direkt verwenden und inline einbetten logoUrl = u; try { const res = await fetch(u); if (res.ok) { const text = (await res.text()).trim(); if (text.includes('