@@ -8,6 +8,94 @@ let allProblems = [];
88let filteredProblems = [ ] ;
99let currentSort = { column : 'number' , direction : 'asc' } ;
1010
11+ // Pagination
12+ const DEFAULT_PAGE_SIZE = 100 ;
13+ let currentPage = 1 ;
14+ let pageSize = DEFAULT_PAGE_SIZE ;
15+
16+ function getPageSizeFromUI ( ) {
17+ const input = document . getElementById ( 'page-size' ) ;
18+ if ( ! input ) return pageSize ;
19+
20+ // Keep the last valid value while the user is typing.
21+ const value = input . value . trim ( ) ;
22+ if ( ! / ^ [ 1 - 9 ] \d * $ / . test ( value ) ) return pageSize ;
23+
24+ // Regex guarantees a positive integer.
25+ return Number ( value ) ;
26+ }
27+
28+ function setPageSizeInUI ( size ) {
29+ const input = document . getElementById ( 'page-size' ) ;
30+ if ( ! input ) return ;
31+ input . value = String ( size ) ;
32+ }
33+
34+ function resetToFirstPageAndUpdate ( ) {
35+ currentPage = 1 ;
36+ updateTable ( ) ;
37+ }
38+
39+ function updatePaginationUI ( totalPages ) {
40+ const info = document . getElementById ( 'pagination-info' ) ;
41+ const prevBtn = document . getElementById ( 'pagination-prev' ) ;
42+ const nextBtn = document . getElementById ( 'pagination-next' ) ;
43+
44+ if ( info ) {
45+ info . textContent = `Page ${ currentPage . toLocaleString ( ) } / ${ totalPages . toLocaleString ( ) } ` ;
46+ }
47+ if ( prevBtn ) {
48+ prevBtn . disabled = currentPage <= 1 ;
49+ }
50+ if ( nextBtn ) {
51+ nextBtn . disabled = currentPage >= totalPages ;
52+ }
53+ }
54+
55+ function initializePaginationListeners ( ) {
56+ const prevBtn = document . getElementById ( 'pagination-prev' ) ;
57+ const nextBtn = document . getElementById ( 'pagination-next' ) ;
58+ const pageSizeInput = document . getElementById ( 'page-size' ) ;
59+
60+ if ( prevBtn ) {
61+ prevBtn . addEventListener ( 'click' , ( ) => {
62+ currentPage = Math . max ( 1 , currentPage - 1 ) ;
63+ updateTable ( ) ;
64+ } ) ;
65+ }
66+
67+ if ( nextBtn ) {
68+ nextBtn . addEventListener ( 'click' , ( ) => {
69+ currentPage += 1 ;
70+ updateTable ( ) ;
71+ } ) ;
72+ }
73+
74+ if ( pageSizeInput ) {
75+ let inputTimeout ;
76+
77+ const applyPageSize = ( ) => {
78+ const newSize = getPageSizeFromUI ( ) ;
79+ pageSize = newSize ;
80+ // Changing page size should reset to page 1
81+ currentPage = 1 ;
82+ updateTable ( ) ; // updateTable will save state to URL
83+ } ;
84+
85+ // Update as the user types, but debounce so we don't re-render on every keystroke.
86+ pageSizeInput . addEventListener ( 'input' , ( ) => {
87+ clearTimeout ( inputTimeout ) ;
88+ inputTimeout = setTimeout ( applyPageSize , 250 ) ;
89+ } ) ;
90+
91+ // Commit immediately on blur/enter (change fires on commit)
92+ pageSizeInput . addEventListener ( 'change' , ( ) => {
93+ clearTimeout ( inputTimeout ) ;
94+ applyPageSize ( ) ;
95+ } ) ;
96+ }
97+ }
98+
1199/**
12100 * Load problems from YAML file
13101 * @returns {Promise<Array<Object>> } Array of problem objects
@@ -101,7 +189,8 @@ function renderTable(problems) {
101189
102190 if ( problems . length === 0 ) {
103191 tableBody . innerHTML = '<tr><td colspan="7" class="loading-cell">No problems match the current filters.</td></tr>' ;
104- updateStats ( 0 , allProblems . length ) ;
192+ // With pagination, show filtered vs total(allProblems.length) counts (range is empty here)
193+ updateStats ( ) ;
105194 return ;
106195 }
107196
@@ -131,18 +220,27 @@ function renderTable(problems) {
131220 tableBody . innerHTML = rows ;
132221
133222 // Update stats
134- updateStats ( problems . length , allProblems . length ) ;
223+ updateStats ( ) ;
135224}
136225
137226/**
138227 * Update statistics display
139- * @param {number } showing - Number of problems currently shown
140- * @param {number } total - Total number of problems
141228 */
142- function updateStats ( showing , total ) {
229+ function updateStats ( ) {
143230 const showingCount = document . getElementById ( 'showing-count' ) ;
144231 if ( showingCount ) {
145- showingCount . textContent = `Showing ${ showing . toLocaleString ( ) } of ${ total . toLocaleString ( ) } problems` ;
232+ const filteredTotal = Array . isArray ( filteredProblems ) ? filteredProblems . length : 0 ;
233+ const start = filteredTotal === 0 ? 0 : ( ( currentPage - 1 ) * pageSize + 1 ) ;
234+ const end = filteredTotal === 0 ? 0 : Math . min ( currentPage * pageSize , filteredTotal ) ;
235+
236+ // N.B: total = allProblems.length i.e total number of problems
237+ // Example: "Showing 201–300 of 1,742 (total 2,100) problems"
238+ // When no filters are active, filteredTotal === total.
239+ if ( filteredTotal > 0 ) {
240+ showingCount . textContent = `Showing ${ start . toLocaleString ( ) } –${ end . toLocaleString ( ) } of ${ filteredTotal . toLocaleString ( ) } (total ${ allProblems . length . toLocaleString ( ) } ) problems` ;
241+ } else {
242+ showingCount . textContent = `Showing 0 of 0 (total ${ allProblems . length . toLocaleString ( ) } ) problems` ;
243+ }
146244 }
147245}
148246
@@ -167,11 +265,11 @@ function handleSortClick(event) {
167265 // Update visual indicators
168266 updateSortIndicators ( currentSort . column , currentSort . direction ) ;
169267
268+ // Sorting should reset pagination
269+ currentPage = 1 ;
270+
170271 // Re-render table
171272 updateTable ( ) ;
172-
173- // Save state to URL
174- saveStateToURL ( getCurrentState ( ) ) ;
175273}
176274
177275/**
@@ -194,6 +292,14 @@ function updateTable() {
194292 // Store filtered results
195293 filteredProblems = results ;
196294
295+ // Pagination: slice the final, sorted results
296+ pageSize = getPageSizeFromUI ( ) ;
297+ const totalPages = Math . max ( 1 , Math . ceil ( results . length / pageSize ) ) ;
298+ if ( ! Number . isFinite ( currentPage ) || currentPage < 1 ) currentPage = 1 ;
299+ if ( currentPage > totalPages ) currentPage = totalPages ;
300+ const startIndex = ( currentPage - 1 ) * pageSize ;
301+ const pagedResults = results . slice ( startIndex , startIndex + pageSize ) ;
302+
197303 // Update tag and dropdown displays with filtered counts
198304 const nonTagFiltersActive = hasNonTagFilters ( ) ;
199305
@@ -216,7 +322,10 @@ function updateTable() {
216322 updateAllDropdownDisplays ( allProblems , hasAnyFilters ) ;
217323
218324 // Render
219- renderTable ( results ) ;
325+ renderTable ( pagedResults ) ;
326+
327+ // Update pagination controls
328+ updatePaginationUI ( totalPages ) ;
220329
221330 // Save state to URL
222331 saveStateToURL ( getCurrentState ( ) ) ;
@@ -263,7 +372,8 @@ async function initialize() {
263372 }
264373
265374 // Set filter change handler FIRST (before creating any event listeners)
266- setFilterChangeHandler ( updateTable ) ;
375+ // Any query change should reset pagination.
376+ setFilterChangeHandler ( resetToFirstPageAndUpdate ) ;
267377
268378 // Extract tag counts and tags
269379 const tagCounts = extractTagCounts ( allProblems ) ;
@@ -290,6 +400,11 @@ async function initialize() {
290400 currentSort . column = urlState . sortColumn ;
291401 currentSort . direction = urlState . sortDirection ;
292402
403+ // Restore pagination from URL
404+ currentPage = urlState . page || 1 ;
405+ pageSize = urlState . pageSize || DEFAULT_PAGE_SIZE ;
406+ setPageSizeInUI ( pageSize ) ;
407+
293408 // Get initial tag sort preference from URL
294409 const initialTagSort = urlState . tagSort || 'count' ;
295410
@@ -303,6 +418,7 @@ async function initialize() {
303418 // Initialize event listeners
304419 initializeSortListeners ( ) ;
305420 initializeFilterListeners ( ) ;
421+ initializePaginationListeners ( ) ;
306422
307423 // Initial render
308424 updateTable ( ) ;
0 commit comments