Files
vue-seo-layer/app/utils/paginationWindow.ts
Jakub Zych f2d13d6264 feat: pagination component and composable
- <SeoPagination> (UI-SPEC L2): previous and next (left out at the
  ends), page 1, the last page and five pages around the current one with
  gap items; nothing when last <= 1; props current, last, labels,
  classPrefix, locale
- usePagination(): hrefs in the CMS form (resolve answer, then site
  answer, then seo.pagination), other query parameters kept, page 1
  unnumbered, <link rel=prev/next> in the head
- the CMS head fills %pages% in the title and description once the
  listing's page count is known (seoFillPages)
- seoPaginationWindow, seoPageHref, seoPageNumber; L2 default styles
2026-11-04 11:59:51 +01:00

36 lines
1.4 KiB
TypeScript

// The page links of <SeoPagination> (UI-SPEC L2, D-42): five consecutive
// pages around the current one, plus page 1 and the last page when they
// fall outside, with a gap item where pages are skipped, so every page is
// within two hops of page 1 for a crawler. Pure, so `node --test` drives
// it. Names start with `seo` because app/utils is auto-imported into the
// consuming app (vue-journal-layer has its own paginationWindow).
export const SEO_PAGINATION_GAP = 'gap' as const
export type SeoPaginationItem = number | typeof SEO_PAGINATION_GAP
/**
* The items for page `current` of `last`: page numbers in order and 'gap'
* between page 1 or the last page and the window when pages are skipped.
* Empty when there is nothing to paginate (last < 1).
*/
export function seoPaginationWindow(current: number, last: number, size = 5): SeoPaginationItem[] {
if (!Number.isInteger(last) || last < 1) return []
const cur = Math.min(Math.max(Number.isInteger(current) ? current : 1, 1), last)
const width = Math.min(Math.max(1, size), last)
let start = Math.max(1, cur - Math.floor(width / 2))
if (start + width - 1 > last) start = last - width + 1
const end = start + width - 1
const out: SeoPaginationItem[] = []
if (start > 1) {
out.push(1)
if (start > 2) out.push(SEO_PAGINATION_GAP)
}
for (let p = start; p <= end; p++) out.push(p)
if (end < last) {
if (end < last - 1) out.push(SEO_PAGINATION_GAP)
out.push(last)
}
return out
}