diff --git a/components/admin/AdminPagination.vue b/components/admin/AdminPagination.vue index 7cde4d5..d11f6f9 100644 --- a/components/admin/AdminPagination.vue +++ b/components/admin/AdminPagination.vue @@ -1,4 +1,6 @@ diff --git a/docs/map.md b/docs/map.md index 0addff4..a6e9eb8 100644 --- a/docs/map.md +++ b/docs/map.md @@ -46,6 +46,7 @@ | lib/analytics-shared.js | 통계 추적 경로 필터·체류/스크롤 상수(클라이언트·서버 공용) | | lib/analytics.js | 서버 전용 visitor/session hash(`node:crypto`) | | lib/analytics-traffic.js | referrer·User-Agent 기반 유입원·디바이스·검색 키워드 축약 분류 | +| lib/admin-pagination.js | 관리자 번호형 페이지네이션 페이지 보정·ellipsis 항목 계산 | | lib/csp-report.js | CSP 보고 Content-Type·32KB/20건 제한 상수, payload 검증, 문서 경로·차단 origin·브라우저 계열 최소화 | | lib/media-catalog.js | 업로드 URL·저장 키 정규화, 미디어 종류·원본/파생 역할 판별, 콘텐츠 미디어 참조 추출, PostgreSQL LIKE 리터럴 이스케이프 | | lib/social-links.js | 사이트 설정 SNS 링크 아이콘 프리셋·사용자 SVG 정리·URL 자동 보정·레거시 JSON 문자열 복구·공개 노출 목록 생성 | @@ -96,6 +97,7 @@ | tests/multipart-file-upload.test.js | multipart 스트리밍·제한 단위 회귀 테스트 | | tests/csp-report.test.js | CSP URL 최소화·payload 형식 단위 회귀 테스트 | | tests/media-catalog.test.js | 미디어 URL·역할·참조·LIKE 이스케이프 단위 회귀 테스트 | +| tests/admin-pagination.test.js | 관리자 페이지네이션 페이지 보정·ellipsis 항목 단위 테스트 | | tests/playwright/api/core-api.spec.js | 인증·부분 설정 저장·공개 목록·CSP·미디어 reconcile/fallback·세션 무효화 API 통합 10건 | | tests/playwright/e2e/admin-guard-and-settings.spec.js | 설정 카드 독립 저장과 미저장 이탈 취소 브라우저 E2E 2건 | | tests/playwright/e2e/admin-publish.spec.js | 관리자 로그인·게시물 발행·공개 화면 확인 브라우저 E2E 1건 | diff --git a/docs/update.md b/docs/update.md index 917d88d..4434798 100644 --- a/docs/update.md +++ b/docs/update.md @@ -1,5 +1,10 @@ # 업데이트 이력 +## v1.11.10 + +- 관리자 페이지네이션 계산 로직을 `lib/admin-pagination.js` 순수 함수로 분리. +- 관리자 페이지네이션 페이지 보정·ellipsis 계산 단위 테스트 추가. + ## v1.11.9 - 관리자 공용 select 입력이 숫자 옵션 값을 보존하도록 보완. diff --git a/lib/admin-pagination.js b/lib/admin-pagination.js new file mode 100644 index 0000000..8222604 --- /dev/null +++ b/lib/admin-pagination.js @@ -0,0 +1,53 @@ +/** + * 페이지 값을 유효한 범위로 보정한다. + * @param {number|string} page - 보정할 페이지 + * @param {number|string} pageCount - 전체 페이지 수 + * @returns {number} 보정된 페이지 + */ +export const clampAdminPage = (page, pageCount) => { + const totalPageCount = Math.max(1, Number(pageCount) || 1) + return Math.min(Math.max(Number(page) || 1, 1), totalPageCount) +} + +/** + * 관리자 번호형 페이지네이션 항목을 생성한다. + * @param {number|string} page - 현재 페이지 + * @param {number|string} pageCount - 전체 페이지 수 + * @returns {Array} 페이지 번호와 ellipsis 항목 + */ +export const buildAdminPaginationItems = (page, pageCount) => { + const total = Math.max(1, Number(pageCount) || 1) + const current = clampAdminPage(page, total) + + if (total <= 7) { + return Array.from({ length: total }, (_, index) => index + 1) + } + + const pages = new Set([1, total, current - 1, current, current + 1]) + + if (current <= 4) { + pages.add(2) + pages.add(3) + pages.add(4) + pages.add(5) + } + + if (current >= total - 3) { + pages.add(total - 4) + pages.add(total - 3) + pages.add(total - 2) + pages.add(total - 1) + } + + const orderedPages = [...pages].filter((item) => item >= 1 && item <= total).sort((left, right) => left - right) + return orderedPages.reduce((items, item, index) => { + const previousPage = orderedPages[index - 1] + + if (previousPage && item - previousPage > 1) { + items.push(`ellipsis-${previousPage}-${item}`) + } + + items.push(item) + return items + }, []) +} diff --git a/package-lock.json b/package-lock.json index 454d917..d2b025c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sori.studio", - "version": "1.11.9", + "version": "1.11.10", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sori.studio", - "version": "1.11.9", + "version": "1.11.10", "hasInstallScript": true, "dependencies": { "@nuxtjs/tailwindcss": "^6.14.0", diff --git a/package.json b/package.json index 2d407ef..1c219f7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sori.studio", - "version": "1.11.9", + "version": "1.11.10", "private": true, "type": "module", "imports": { diff --git a/tests/admin-pagination.test.js b/tests/admin-pagination.test.js new file mode 100644 index 0000000..64780db --- /dev/null +++ b/tests/admin-pagination.test.js @@ -0,0 +1,26 @@ +import assert from 'node:assert/strict' +import test from 'node:test' +import { buildAdminPaginationItems, clampAdminPage } from '../lib/admin-pagination.js' + +test('관리자 페이지 값을 유효 범위로 보정한다', () => { + assert.equal(clampAdminPage(0, 10), 1) + assert.equal(clampAdminPage(12, 10), 10) + assert.equal(clampAdminPage('4', 10), 4) + assert.equal(clampAdminPage('abc', 10), 1) +}) + +test('전체 페이지가 적으면 모든 페이지 번호를 표시한다', () => { + assert.deepEqual(buildAdminPaginationItems(3, 7), [1, 2, 3, 4, 5, 6, 7]) +}) + +test('첫 구간에서는 앞쪽 페이지를 펼치고 끝 페이지를 유지한다', () => { + assert.deepEqual(buildAdminPaginationItems(2, 12), [1, 2, 3, 4, 5, 'ellipsis-5-12', 12]) +}) + +test('중간 구간에서는 현재 페이지 주변과 양 끝을 유지한다', () => { + assert.deepEqual(buildAdminPaginationItems(6, 12), [1, 'ellipsis-1-5', 5, 6, 7, 'ellipsis-7-12', 12]) +}) + +test('끝 구간에서는 첫 페이지와 뒤쪽 페이지를 펼친다', () => { + assert.deepEqual(buildAdminPaginationItems(11, 12), [1, 'ellipsis-1-8', 8, 9, 10, 11, 12]) +})