// Page-number rules for Journal listings. Pages use ?page=N (never a path // segment). Anything but a positive integer, and any page past the last one, // is a real 404. /** * Parses the ?page query value: 1 when absent, the integer for a string of * digits greater than 0, and null (a 404) for anything else: non-numeric, * empty, 0, negative, decimal, repeated (array) or beyond the safe range. */ export function parsePageParam(raw: unknown): number | null { if (raw === undefined || raw === null) return 1 if (typeof raw !== 'string' || !/^\d+$/.test(raw)) return null const n = Number(raw) if (!Number.isSafeInteger(n) || n < 1) return null return n } /** * True when `page` lies past the last page. Page 1 is always in range, so an * empty listing renders its empty state instead of a 404. */ export function pageOutOfRange(page: number, lastPage: number): boolean { return page > 1 && page > lastPage } /** * The page numbers to render around `current`: at most `size` consecutive * pages, clipped to 1..last and kept full-width near either end. */ export function paginationWindow(current: number, last: number, size: number = 5): number[] { if (last < 1 || size < 1) return [] const width = Math.min(size, last) let start = Math.max(1, current - Math.floor(width / 2)) if (start + width - 1 > last) start = last - width + 1 return Array.from({ length: width }, (_, i) => start + i) }