1// main.js â Blog page logic: search, filter, sort, pagination 2 3(function () { 4 const POSTS_PER_PAGE = 6; 5 6 let state = { 7 category: 'all', 8 search: '', 9 tag: '', 10 sort: 'newest', 11 page: 1 12 }; 13 14 // --- DOM refs --- 15 const grid = document.getElementById('blogGrid'); 16 const pagination = document.getElementById('pagination'); 17 const paginationWrap = document.getElementById('paginationWrap'); 18 const noResults = document.getElementById('noResults'); 19 const resultsCount = document.getElementById('resultsCount'); 20 const searchInput = document.getElementById('searchInput'); 21 const clearSearch = document.getElementById('clearSearch'); 22 const sortSelect = document.getElementById('sortSelect'); 23 const activeFilters= document.getElementById('activeFilters'); 24 25 // --- Category counts --- 26 function updateCounts() { 27 const counts = { all: BLOGS.length, ai: 0, microsoft: 0, hr: 0, other: 0, guide: 0 }; 28 BLOGS.forEach(b => { if (counts[b.category] !== undefined) counts[b.category]++; }); 29 document.getElementById('countAll').textContent = counts.all; 30 document.getElementById('countAI').textContent = counts.ai; 31 document.getElementById('countMS').textContent = counts.microsoft; 32 document.getElementById('countHR').textContent = counts.hr; 33 document.getElementById('countOther').textContent = counts.other; 34 document.getElementById('countGuide').textContent = counts.guide; 35 } 36 37 // --- Filter & sort posts --- 38 function getFiltered() { 39 let list = [...BLOGS]; 40 41 if (state.category !== 'all') { 42 list = list.filter(b => b.category === state.category); 43 } 44 if (state.search) { 45 const q = state.search.toLowerCase(); 46 list = list.filter(b => 47 b.title.toLowerCase().includes(q) || 48 b.excerpt.toLowerCase().includes(q) || 49 b.tags.some(t => t.toLowerCase().includes(q)) || 50 b.badgeText.toLowerCase().includes(q) 51 ); 52 } 53 if (state.tag) { 54 list = list.filter(b => b.tags.includes(state.tag)); 55 } 56 57 if (state.sort === 'newest') { 58 list.sort((a, b) => new Date(b.date) - new Date(a.date)); 59 } else if (state.sort === 'oldest') { 60 list.sort((a, b) => new Date(a.date) - new Date(b.date)); 61 } else if (state.sort === 'readtime') { 62 list.sort((a, b) => a.readtime - b.readtime); 63 } 64 65 return list; 66 } 67 68 // --- Format date --- 69 function fmtDate(d) { 70 return new Date(d).toLocaleDateString('en-IN', { year: 'numeric', month: 'short', day: 'numeric' }); 71 } 72 73 // --- Render card --- 74 function renderCard(b) { 75 return ` 76 <div class="col-md-6 col-lg-6 blog-card-col" data-id="${b.id}"> 77 <div class="blog-card h-100" onclick="window.open('${b.url}','_blank')" role="button" tabindex="0" aria-label="Read: ${b.title}"> 78 <div class="card-thumb"> 79 <div class="card-thumb"> 80 <img src="${b.thumbImage}" alt="${b.title}" class="card-img-top" /> 81 <span class="card-cat-badge ${b.badgeClass}"> 82 <i class="bi ${b.badgeIcon}"></i>${b.badgeText} 83 </span> 84</div> 85 <span class="card-cat-badge ${b.badgeClass}"><i class="bi ${b.badgeIcon}"></i>${b.badgeText}</span> 86 </div> 87 <div class="card-body-inner"> 88 <div class="card-meta"> 89 <span><i class="bi bi-calendar3"></i>${fmtDate(b.date)}</span> 90 <span><i class="bi bi-clock"></i>${b.readtime} min read</span> 91 </div> 92 <h3 class="card-title">${b.title}</h3> 93 <p class="card-excerpt">${b.excerpt}</p> 94 <div class="card-footer-row"> 95 <a href="${b.url}" target="_blank" class="read-more-link" onclick="event.stopPropagation()"> 96 Read More <i class="bi bi-arrow-right"></i> 97 </a> 98 <div class="card-tags"> 99 ${b.tags.slice(0, 2).map(t => `<span class="card-tag">${t}</span>`).join('')} 100 </div> 101 </div> 102 </div> 103 </div> 104 </div>`; 105 } 106 107 // --- Render pagination --- 108 function renderPagination(total, current) { 109 const pages = Math.ceil(total / POSTS_PER_PAGE); 110 pagination.innerHTML = ''; 111 if (pages <= 1) { paginationWrap.style.display = 'none'; return; } 112 paginationWrap.style.display = ''; 113 114 const prev = document.createElement('li'); 115 prev.className = `page-item ${current === 1 ? 'disabled' : ''}`; 116 prev.innerHTML = `<button class="page-link" aria-label="Previous"><i class="bi bi-chevron-left"></i></button>`; 117 prev.addEventListener('click', () => { if (current > 1) goPage(current - 1); }); 118 pagination.appendChild(prev); 119 120 for (let i = 1; i <= pages; i++) { 121 const li = document.createElement('li'); 122 li.className = `page-item ${i === current ? 'active' : ''}`; 123 li.innerHTML = `<button class="page-link">${i}</button>`; 124 li.addEventListener('click', () => goPage(i)); 125 pagination.appendChild(li); 126 } 127 128 const next = document.createElement('li'); 129 next.className = `page-item ${current === pages ? 'disabled' : ''}`; 130 next.innerHTML = `<button class="page-link" aria-label="Next"><i class="bi bi-chevron-right"></i></button>`; 131 next.addEventListener('click', () => { if (current < pages) goPage(current + 1); }); 132 pagination.appendChild(next); 133 } 134 135 function goPage(n) { 136 state.page = n; 137 render(); 138 window.scrollTo({ top: document.querySelector('.blog-main').offsetTop - 80, behavior: 'smooth' }); 139 } 140 141 // --- Active filter chips --- 142 function renderActiveFilters() { 143 activeFilters.innerHTML = ''; 144 if (state.category !== 'all') { 145 const labels = { ai: 'AI Certifications', microsoft: 'Microsoft Certs', hr: 'HR & L&D', other: 'Other Certifications', guide: 'Guides' }; 146 activeFilters.innerHTML += `<span class="filter-chip" onclick="resetCategory()"><i class="bi bi-x"></i>${labels[state.category]}</span>`;
147 } 148 if (state.tag) { 149 activeFilters.innerHTML += `<span class="filter-chip" onclick="resetTag()"><i class="bi bi-x"></i>${state.tag}</span>`; 150 } 151 if (state.search) { 152 activeFilters.innerHTML += `<span class="filter-chip" onclick="resetSearch()"><i class="bi bi-x"></i>"${state.search}"</span>`; 153 } 154 } 155 156 // --- Main render --- 157 function render() { 158 const filtered = getFiltered(); 159 const start = (state.page - 1) * POSTS_PER_PAGE; 160 const paginated = filtered.slice(start, start + POSTS_PER_PAGE); 161 162 renderActiveFilters(); 163 164 if (filtered.length === 0) { 165 grid.innerHTML = ''; 166 noResults.classList.remove('d-none'); 167 resultsCount.innerHTML = 'Showing <strong>0</strong> articles'; 168 renderPagination(0, 1); 169 return; 170 } 171 172 noResults.classList.add('d-none'); 173 resultsCount.innerHTML = `Showing <strong>${filtered.length}</strong> article${filtered.length !== 1 ? 's' : ''}`; 174 grid.innerHTML = paginated.map(renderCard).join(''); 175 renderPagination(filtered.length, state.page); 176 } 177 178 // --- Event listeners --- 179 // Category buttons 180 document.getElementById('categoryList').addEventListener('click', e => { 181 const btn = e.target.closest('.cat-btn'); 182 if (!btn) return; 183 document.querySelectorAll('.cat-btn').forEach(b => b.classList.remove('active')); 184 btn.classList.add('active'); 185 state.category = btn.dataset.cat; 186 state.page = 1; 187 render(); 188 }); 189 190 // Search 191 searchInput.addEventListener('input', () => { 192 state.search = searchInput.value.trim(); 193 state.page = 1; 194 clearSearch.classList.toggle('d-none', !state.search); 195 render(); 196 }); 197 clearSearch.addEventListener('click', () => { 198 searchInput.value = ''; 199 state.search = ''; 200 clearSearch.classList.add('d-none'); 201 state.page = 1; 202 render(); 203 }); 204 205 // Sort 206 sortSelect.addEventListener('change', () => { 207 state.sort = sortSelect.value; 208 state.page = 1; 209 render(); 210 }); 211 212 // Tags 213 document.getElementById('tagCloud').addEventListener('click', e => { 214 const pill = e.target.closest('.tag-pill'); 215 if (!pill) return; 216 if (state.tag === pill.dataset.tag) { 217 state.tag = ''; 218 pill.classList.remove('active'); 219 } else { 220 document.querySelectorAll('.tag-pill').forEach(p => p.classList.remove('active')); 221 state.tag = pill.dataset.tag; 222 pill.classList.add('active'); 223 } 224 state.page = 1; 225 render(); 226 }); 227 228 // --- Global reset helpers --- 229 window.resetFilters = function () { 230 state = { category: 'all', search: '', tag: '', sort: 'newest', page: 1 }; 231 searchInput.value = ''; 232 clearSearch.classList.add('d-none'); 233 sortSelect.value = 'newest'; 234 document.querySelectorAll('.cat-btn').forEach(b => b.classList.remove('active')); 235 document.querySelector('.cat-btn[data-cat="all"]').classList.add('active'); 236 document.querySelectorAll('.tag-pill').forEach(p => p.classList.remove('active')); 237 render(); 238 }; 239 window.resetCategory = function () { 240 state.category = 'all'; 241 state.page = 1; 242 document.querySelectorAll('.cat-btn').forEach(b => b.classList.remove('active')); 243 document.querySelector('.cat-btn[data-cat="all"]').classList.add('active'); 244 render(); 245 }; 246 window.resetTag = function () { 247 state.tag = ''; 248 state.page = 1; 249 document.querySelectorAll('.tag-pill').forEach(p => p.classList.remove('active')); 250 render(); 251 }; 252 window.resetSearch = function () { 253 state.search = ''; 254 state.page = 1; 255 searchInput.value = ''; 256 clearSearch.classList.add('d-none'); 257 render(); 258 }; 259 window.filterCat = function (cat) { 260 window.scrollTo({ top: 0, behavior: 'smooth' }); 261 state.category = cat; 262 state.page = 1; 263 document.querySelectorAll('.cat-btn').forEach(b => b.classList.remove('active')); 264 const btn = document.querySelector(`.cat-btn[data-cat="${cat}"]`); 265 if (btn) btn.classList.add('active'); 266 render(); 267 }; 268 269 // Newsletter 270 window.subscribeNewsletter = function () { 271 const emailEl = document.getElementById('newsletterEmail'); 272 const msgEl = document.getElementById('newsletterMsg'); 273 if (!emailEl || !emailEl.value.includes('@')) { 274 msgEl.textContent = 'Please enter a valid email address.'; 275 msgEl.style.color = '#e53e3e'; 276 msgEl.classList.remove('d-none'); 277 return; 278 } 279 msgEl.textContent = 'â You\'re subscribed! Welcome to Pioneer insights.'; 280 msgEl.style.color = '#6ee7b7'; 281 msgEl.classList.remove('d-none'); 282 emailEl.value = ''; 283 }; 284 285 // Init 286 updateCounts(); 287 render(); 288})();
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.