Legacy detail URLs now 308 to locale-neutral fragments, and crawl plus JSON-LD follow the 18-path set. Co-authored-by: Cursor <cursoragent@cursor.com>
76 lines
2.6 KiB
TypeScript
76 lines
2.6 KiB
TypeScript
import { computed, inject, Injectable, signal } from '@angular/core';
|
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
|
|
import { NavigationEnd, Router } from '@angular/router';
|
|
import { filter } from 'rxjs';
|
|
import { SITE_CONFIG } from '../content/site-config';
|
|
import { otherLocale, type AppLocale } from '../i18n/locale';
|
|
import { LocaleService } from '../i18n/locale.service';
|
|
import { isAppRouteData } from '../routing/app-route-data';
|
|
import { type RouteId } from '../routing/route-ids';
|
|
import { routeCommands } from '../routing/route-paths';
|
|
import { FOOTER_NAV, PRIMARY_NAV, type NavItem } from './navigation';
|
|
|
|
export interface ResolvedNavItem {
|
|
readonly routeId: RouteId;
|
|
readonly label: string;
|
|
readonly link: unknown[];
|
|
}
|
|
|
|
@Injectable({ providedIn: 'root' })
|
|
export class NavigationService {
|
|
private readonly localeService = inject(LocaleService);
|
|
private readonly router = inject(Router);
|
|
private readonly activeRouteIdSignal = signal<RouteId>('home');
|
|
|
|
readonly activeRouteId = this.activeRouteIdSignal.asReadonly();
|
|
readonly cvHref = SITE_CONFIG.cvAssetPath;
|
|
readonly contactLink = computed(() => this.link('contact'));
|
|
readonly primaryNav = computed(() => this.resolveItems(PRIMARY_NAV, this.localeService.locale()));
|
|
readonly footerNav = computed(() => this.resolveItems(FOOTER_NAV, this.localeService.locale()));
|
|
|
|
constructor() {
|
|
this.router.events
|
|
.pipe(
|
|
filter((event): event is NavigationEnd => event instanceof NavigationEnd),
|
|
takeUntilDestroyed(),
|
|
)
|
|
.subscribe(() => this.syncFromRouter());
|
|
|
|
this.syncFromRouter();
|
|
}
|
|
|
|
link(routeId: RouteId, locale?: AppLocale): unknown[] {
|
|
return routeCommands(routeId, locale ?? this.localeService.locale());
|
|
}
|
|
|
|
alternateLocaleLink(): unknown[] {
|
|
const targetLocale = otherLocale(this.localeService.locale());
|
|
const current = this.activeRouteId();
|
|
const routeId = current === 'notFound' ? 'home' : current;
|
|
return routeCommands(routeId, targetLocale);
|
|
}
|
|
|
|
private resolveItems(items: readonly NavItem[], locale: AppLocale): ResolvedNavItem[] {
|
|
return items.map((item) => ({
|
|
routeId: item.routeId,
|
|
label: item.label[locale],
|
|
link: routeCommands(item.routeId, locale),
|
|
}));
|
|
}
|
|
|
|
private syncFromRouter(): void {
|
|
let snapshot = this.router.routerState.snapshot.root;
|
|
|
|
while (snapshot.firstChild) {
|
|
snapshot = snapshot.firstChild;
|
|
}
|
|
|
|
if (!isAppRouteData(snapshot.data)) {
|
|
return;
|
|
}
|
|
|
|
this.localeService.setLocale(snapshot.data.locale);
|
|
this.activeRouteIdSignal.set(snapshot.data.routeId);
|
|
}
|
|
}
|