import { ChangeDetectionStrategy, Component, computed, input, } from '@angular/core'; import { copyFor, type Copy } from '../core/copy'; import { listingAlternates, listingPath } from '../core/routes'; import type { Locale } from '../core/api'; import { RadarPage } from './radar-page'; import { ServiceDetail } from './service-detail'; import { CallDetail } from './call-detail'; import type { RoutedPageData, RoutedRecord } from '../core/routed-page'; import { ContentPage } from './content-page'; import { ContributePage } from './contribute-page'; import { MemberDetail } from './member-detail'; import { PageShell, type PageSeo } from './page-shell'; import { ProjectDetail } from './project-detail'; /** * A page reached by its address, whatever it holds: the shell around it, and * the body for its type inside. */ @Component({ selector: 'app-routed-page', changeDetection: ChangeDetectionStrategy.OnPush, imports: [ CallDetail, ContentPage, ContributePage, MemberDetail, PageShell, ProjectDetail, RadarPage, ServiceDetail, ], template: ` @if (data().found; as found) { @switch (found.type) { @case ('page') { } @case ('project') { } @case ('call') { } @case ('service') { } @case ('contribute') { } @case ('radar') { } @case ('member') { } } } `, }) export class RoutedPage { readonly data = input.required(); protected readonly copy = computed(() => copyFor(this.data().locale)); protected readonly seo = computed(() => seoFor(this.data().found, this.copy(), this.data().locale), ); } /** What each type puts in the document head. Null when nothing was found. */ export function seoFor( found: RoutedRecord | null, copy: Copy, locale: Locale, ): PageSeo | null { if (!found) { return null; } switch (found.type) { case 'page': { const page = found.record; return { title: page.meta_title || `Pireon — ${page.title}`, description: page.meta_description || page.excerpt || '', path: page.path, alternates: page.alternates ?? {}, type: 'article', }; } case 'project': { const project = found.record; return { title: project.meta_title || `Pireon — ${project.title}`, description: project.meta_description || project.tagline || '', path: project.path, alternates: project.alternates ?? {}, type: 'article', }; } case 'member': { const member = found.record; return { title: `Pireon — ${member.name}`, description: member.headline || member.bio?.slice(0, 160) || '', path: member.path, alternates: member.alternates ?? {}, type: 'profile', }; } case 'service': { const service = found.record; return { title: service.meta_title || `Pireon — ${service.title}`, description: service.meta_description || service.tagline || '', path: service.path, alternates: service.alternates ?? {}, type: 'article', }; } case 'contribute': return { title: `Pireon — ${copy.contribute.title}`, description: copy.contribute.lead, path: listingPath('contribute', locale), alternates: listingAlternates('contribute'), type: 'website', }; case 'radar': return { title: `Pireon — ${copy.sections.radar.title}`, description: copy.radar.lead, path: listingPath('radar', locale), alternates: listingAlternates('radar'), type: 'website', }; case 'call': { const call = found.record; const project = call.project?.title; return { title: `Pireon — ${call.title}${project ? ` · ${project}` : ''}`, description: call.description?.slice(0, 160) || '', path: call.path, alternates: call.alternates ?? {}, type: 'article', }; } } }