// 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 }
}