From 0bba787f9dfa2cb64d02420db82df5dd9b8c75de Mon Sep 17 00:00:00 2001 From: Lawrence Tong Date: Sat, 11 Jul 2026 11:41:00 -0500 Subject: [PATCH] feat: paginate starred questions pages Co-Authored-By: Claude Fable 5 --- client/scripts/utilities/pagination.js | 59 ++++++++++++++++++++++++++ client/user/stars/bonuses.html | 1 + client/user/stars/bonuses.js | 54 +++++++++++++++++------ client/user/stars/tossups.html | 1 + client/user/stars/tossups.js | 52 +++++++++++++++++------ 5 files changed, 140 insertions(+), 27 deletions(-) create mode 100644 client/scripts/utilities/pagination.js diff --git a/client/scripts/utilities/pagination.js b/client/scripts/utilities/pagination.js new file mode 100644 index 000000000..cbe679402 --- /dev/null +++ b/client/scripts/utilities/pagination.js @@ -0,0 +1,59 @@ +/** + * Renders a Bootstrap pagination control into `container` and wires up click handlers. + * + * @param {HTMLElement} container - Element to render the pagination nav into. + * @param {number} currentPage - The currently active page (1-indexed). + * @param {number} totalPages - Total number of pages. + * @param {(page: number) => void} onPageChange - Called with the new page number when the user navigates. + */ +export default function renderPagination (container, currentPage, totalPages, onPageChange) { + if (totalPages <= 1) { + container.innerHTML = ''; + return; + } + + const shiftLength = window.screen.width > 992 ? 10 : 5; + const shift = shiftLength * Math.floor((currentPage - 1) / shiftLength); + const rangeStart = shift; + const rangeEnd = Math.min(shift + shiftLength, totalPages); + + const pageLinks = Array(rangeEnd - rangeStart).fill().map((_, idx) => { + const page = rangeStart + idx + 1; + const isActive = page === currentPage; + return ` +
  • + ${page} +
  • + `; + }).join(''); + + container.innerHTML = ` + + `; + + for (const link of container.querySelectorAll('a.page-link')) { + link.addEventListener('click', function (event) { + event.preventDefault(); + const page = Number(this.dataset.page); + if (page !== currentPage) { + onPageChange(page); + } + }); + } +} diff --git a/client/user/stars/bonuses.html b/client/user/stars/bonuses.html index 9e671ebcd..bb40f2175 100644 --- a/client/user/stars/bonuses.html +++ b/client/user/stars/bonuses.html @@ -20,6 +20,7 @@
    +
    - `; + `).join('') + : '

    You have no starred tossups.

    '; + + for (const tossup of pageTossups) { + attachStarListener(tossup); + } + + renderPagination(paginationContainer, page, totalPages, goToPage); } -for (const tossup of tossups) { - document.getElementById(`star-tossup-${tossup._id}`).addEventListener('click', async function () { - if (this.classList.contains('selected')) { - this.innerHTML = star.unstarredSvg; - star.unstarTossup(tossup._id); - this.classList.toggle('selected'); - } else if (await star.starTossup(tossup._id)) { - this.innerHTML = star.starredSvg; - this.classList.toggle('selected'); - } - }); +function goToPage (page) { + renderPage(page); + tossupList.scrollIntoView({ behavior: 'smooth', block: 'start' }); } +renderPage(1); + document.getElementById('confirm').addEventListener('click', async function () { await star.clearStarredTossups(); window.location.reload();