// Page links of a listing in the CMS-chosen URL form (D-40, D-42, UI-SPEC // L2). The form comes from the resolve answer's pagination (a request with // a page number), else the site answer's (when useSeoSite() ran on this // page), else the layer's seo.pagination option; the path segment is the // one of the page's locale. Page 1 is the unnumbered URL; the other query // parameters stay. // // It also adds and to the head and // hands the number of pages to the CMS head (useCmsSeo), which replaces // %pages% in the CMS title and description (D-41). Robots, canonical and // the title and description suffixes come from the resolver. // // const { items, href } = usePagination({ current: page, last: lastPage }) import type { MaybeRefOrGetter } from 'vue' import { paginationOption } from '../../lib/options.ts' import { seoPageHref, type SeoPaginationForm } from '../utils/pageHref.ts' import { seoPaginationWindow } from '../utils/paginationWindow.ts' import type { SeoSiteAnswer } from '../utils/types.ts' import { SEO_SITE_KEY } from './useSeoSite.ts' /** The number of pages of the current listing, for the CMS head's %pages%. */ export function useSeoPages() { return useState('seo-layer:pages', () => null) } const primary = (code: string | undefined | null) => (code ?? '').toLowerCase().split(/[-_]/)[0] ?? '' export interface UsePaginationOptions { current: MaybeRefOrGetter last: MaybeRefOrGetter /** The listing's locale, for the path segment (default: the page's content locale). */ locale?: MaybeRefOrGetter } export function usePagination(opts: UsePaginationOptions) { const route = useRoute() const answer = useSeoAnswer() const config = useRuntimeConfig() const { data: site } = useNuxtData(SEO_SITE_KEY) const option = paginationOption((config.public.seo as { pagination?: unknown } | undefined)?.pagination) const current = computed(() => Math.max(1, Math.trunc(Number(toValue(opts.current)) || 1))) const last = computed(() => Math.max(0, Math.trunc(Number(toValue(opts.last)) || 0))) const form = computed((): { form: SeoPaginationForm; param: string; segment: string } => { const locale = toValue(opts.locale) || answer.value?.locale || primary(answer.value?.html_lang) || site.value?.default_locale || '' const a = answer.value?.pagination if (a && (a.form === 'query' || a.form === 'path')) return { form: a.form, param: a.param || option.param, segment: a.segment || '' } const s = site.value?.pagination if (s && (s.form === 'query' || s.form === 'path')) return { form: s.form, param: s.param || option.param, segment: s.segments?.[locale] || s.segments?.[primary(locale)] || '' } return { form: option.form, param: option.param, segment: option.segments[locale] || option.segments[primary(locale)] || '' } }) const href = (page: number) => seoPageHref(route.fullPath, page, form.value.form, form.value.segment, form.value.param) const items = computed(() => seoPaginationWindow(current.value, last.value)) const pages = useSeoPages() watch(last, (n) => (pages.value = n > 0 ? n : null), { immediate: true }) useHead( computed(() => { const link: Array<{ rel: string; href: string; key: string }> = [] if (last.value > 1 && current.value > 1 && current.value <= last.value) link.push({ rel: 'prev', href: href(current.value - 1), key: 'seo-pagination-prev' }) if (last.value > 1 && current.value < last.value) link.push({ rel: 'next', href: href(current.value + 1), key: 'seo-pagination-next' }) return { link } }), ) return { current, last, items, href, form } }