11/* blog.js - list of posts with tag filter, pagination, and search */
22
3- // Constants
4- const PER_PAGE = 10 ;
3+ // Pagination size (mutable via dropdown)
4+ let perPage = 10 ;
55
66// DOM Elements
77const list = document . getElementById ( "posts-list" ) ;
88const btnBox = document . getElementById ( "tag-buttons" ) ;
99const prevBtn = document . getElementById ( "prev-button" ) ;
1010const nextBtn = document . getElementById ( "next-button" ) ;
11+ const perPageSelect = document . getElementById ( "per-page-select" ) ;
12+ const pageInput = document . getElementById ( "page-input" ) ;
13+ const totalPagesSpan = document . getElementById ( "total-pages" ) ;
1114const searchBox = document . getElementById ( "search-box" ) ;
1215const searchButton = document . getElementById ( "search-button" ) ;
1316
@@ -64,11 +67,24 @@ function renderPosts(posts, filterTag = null) {
6467}
6568
6669// Render the current page of posts
70+ function totalPages ( ) {
71+ return Math . max ( 1 , Math . ceil ( filtered . length / perPage ) ) ;
72+ }
73+
74+ function clampPage ( ) {
75+ const tp = totalPages ( ) ;
76+ if ( currentPage > tp ) currentPage = tp ;
77+ if ( currentPage < 1 ) currentPage = 1 ;
78+ }
79+
6780function renderPage ( ) {
68- const start = ( currentPage - 1 ) * PER_PAGE ;
69- renderPosts ( filtered . slice ( start , start + PER_PAGE ) ) ;
81+ clampPage ( ) ;
82+ const start = ( currentPage - 1 ) * perPage ;
83+ renderPosts ( filtered . slice ( start , start + perPage ) ) ;
7084 prevBtn . disabled = currentPage === 1 ;
71- nextBtn . disabled = start + PER_PAGE >= filtered . length ;
85+ nextBtn . disabled = currentPage >= totalPages ( ) ;
86+ if ( pageInput ) pageInput . value = currentPage ;
87+ if ( totalPagesSpan ) totalPagesSpan . textContent = ` / ${ totalPages ( ) } ` ;
7288}
7389
7490/* -------------------- Event Handlers -------------------- */
@@ -83,12 +99,43 @@ prevBtn.addEventListener("click", () => {
8399
84100// Handle pagination (next button)
85101nextBtn . addEventListener ( "click" , ( ) => {
86- if ( currentPage * PER_PAGE < filtered . length ) {
102+ if ( currentPage < totalPages ( ) ) {
87103 currentPage ++ ;
88104 renderPage ( ) ;
89105 }
90106} ) ;
91107
108+ // Change per-page size
109+ if ( perPageSelect ) {
110+ perPageSelect . addEventListener ( "change" , ( ) => {
111+ const val = parseInt ( perPageSelect . value , 10 ) ;
112+ if ( [ 10 , 20 , 50 ] . includes ( val ) ) {
113+ perPage = val ;
114+ currentPage = 1 ;
115+ renderPage ( ) ;
116+ }
117+ } ) ;
118+ }
119+
120+ // Page jump input
121+ if ( pageInput ) {
122+ pageInput . addEventListener ( "change" , ( ) => {
123+ let val = parseInt ( pageInput . value , 10 ) ;
124+ if ( Number . isNaN ( val ) ) val = 1 ;
125+ currentPage = val ;
126+ renderPage ( ) ;
127+ } ) ;
128+ pageInput . addEventListener ( "keydown" , e => {
129+ if ( e . key === "Enter" ) {
130+ e . preventDefault ( ) ;
131+ let val = parseInt ( pageInput . value , 10 ) ;
132+ if ( Number . isNaN ( val ) ) val = currentPage ;
133+ currentPage = val ;
134+ renderPage ( ) ;
135+ }
136+ } ) ;
137+ }
138+
92139// Execute a search over titles + loaded body excerpts
93140function executeSearch ( ) {
94141 const query = searchBox . value . trim ( ) . toLowerCase ( ) ;
@@ -176,7 +223,7 @@ fetch("./posts/metadata/entries.json")
176223 . then ( data => {
177224 posts = data ;
178225 filtered = posts ;
179- // Reset search and filter
226+ // Reset search and filter (also updates pagination UI)
180227 resetSearchAndFilter ( ) ;
181228
182229 // Build tag buttons
0 commit comments