News hub: add working pagination that respects the type filter

Client-side paginator (9 per page) reusing the site's pagination styling; page numbers with dots for long lists, Previous/Next with disabled states, resets to page 1 on filter change and hides when a single page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
popovskik 2026-07-31 23:56:42 +02:00
parent ea9acc4968
commit 5b14cc2189
8 changed files with 105 additions and 10 deletions

View File

@ -94,6 +94,15 @@
.newshub-card h3 a:hover { color: #2D738C; } .newshub-card h3 a:hover { color: #2D738C; }
.newshub-excerpt { margin: 0; font-size: 14px; line-height: 22px; color: #667085; } .newshub-excerpt { margin: 0; font-size: 14px; line-height: 22px; color: #667085; }
/* Pagination reuses the shared .pagination look; reset button chrome */
.newshub-pagination { margin-top: 44px; }
.newshub-pagination .pagination-arrow,
.newshub-pagination .page-number {
background: transparent;
border: none;
font-family: inherit;
}
@media (max-width: 768px) { @media (max-width: 768px) {
.newshub-hero { padding: 92px 20px 32px; } .newshub-hero { padding: 92px 20px 32px; }
.newshub-hero h1 { font-size: 30px; } .newshub-hero h1 { font-size: 30px; }

View File

@ -258,7 +258,7 @@
</div> </div>
</section> </section>
<section class="latest-news-section"> <section class="latest-news-section">
<div class="news-header"> <div class="news-header">
<h2>Latest news</h2> <h2>Latest news</h2>
<p>What is new at MSOS, from events and projects to blog stories and announcements.</p> <p>What is new at MSOS, from events and projects to blog stories and announcements.</p>

View File

@ -349,6 +349,11 @@
</div> </div>
</article> </article>
</div> </div>
<nav class="pagination newshub-pagination" data-page-size="9" hidden>
<button type="button" class="pagination-arrow" data-nav="prev"><i class="fas fa-arrow-left" aria-hidden="true"></i> Previous</button>
<div class="pagination-numbers"></div>
<button type="button" class="pagination-arrow" data-nav="next">Next <i class="fas fa-arrow-right" aria-hidden="true"></i></button>
</nav>
</section> </section>
</main> </main>

View File

@ -261,7 +261,7 @@
</div> </div>
</section> </section>
<section class="latest-news-section"> <section class="latest-news-section">
<div class="news-header"> <div class="news-header">
<h2>Најнови вести</h2> <h2>Најнови вести</h2>
<p>Што е ново кај МСОС, од настани и проекти до блог приказни и објави.</p> <p>Што е ново кај МСОС, од настани и проекти до блог приказни и објави.</p>

View File

@ -349,6 +349,11 @@
</div> </div>
</article> </article>
</div> </div>
<nav class="pagination newshub-pagination" data-page-size="9" hidden>
<button type="button" class="pagination-arrow" data-nav="prev"><i class="fas fa-arrow-left" aria-hidden="true"></i> Претходна</button>
<div class="pagination-numbers"></div>
<button type="button" class="pagination-arrow" data-nav="next">Следна <i class="fas fa-arrow-right" aria-hidden="true"></i></button>
</nav>
</section> </section>
</main> </main>

View File

@ -1,19 +1,90 @@
/* News hub type filter: chips toggle which cards are shown. No dependencies. */ /* News hub: type filter + client-side pagination (they work together). */
(function () { (function () {
var list = document.querySelector('.newshub-list'); var list = document.querySelector('.newshub-list');
if (!list) return; if (!list) return;
var chips = list.querySelectorAll('.newshub-chip'); var chips = [].slice.call(list.querySelectorAll('.newshub-chip'));
var cards = list.querySelectorAll('.newshub-card'); var cards = [].slice.call(list.querySelectorAll('.newshub-card'));
function apply(type) { var pager = list.querySelector('.newshub-pagination');
cards.forEach(function (card) { var numbers = pager ? pager.querySelector('.pagination-numbers') : null;
card.hidden = !(type === 'all' || card.getAttribute('data-type') === type); var prevBtn = pager ? pager.querySelector('[data-nav="prev"]') : null;
var nextBtn = pager ? pager.querySelector('[data-nav="next"]') : null;
var pageSize = pager ? (parseInt(pager.getAttribute('data-page-size'), 10) || 9) : 9;
var state = { type: 'all', page: 1 };
function matching() {
return cards.filter(function (c) {
return state.type === 'all' || c.getAttribute('data-type') === state.type;
}); });
} }
// Build the compact list of page tokens: numbers and 'dots'.
function tokens(total, cur) {
if (total <= 7) {
var all = [];
for (var i = 1; i <= total; i++) all.push(i);
return all;
}
var set = { 1: 1, 2: 1 };
set[total] = 1; set[total - 1] = 1;
set[cur] = 1; set[cur - 1] = 1; set[cur + 1] = 1;
var pages = Object.keys(set).map(Number).filter(function (n) { return n >= 1 && n <= total; }).sort(function (a, b) { return a - b; });
var out = [];
for (var j = 0; j < pages.length; j++) {
if (j > 0 && pages[j] - pages[j - 1] > 1) out.push('dots');
out.push(pages[j]);
}
return out;
}
function buildPager(total) {
if (!pager) return;
if (total <= 1) { pager.hidden = true; return; }
pager.hidden = false;
numbers.innerHTML = '';
tokens(total, state.page).forEach(function (t) {
if (t === 'dots') {
var s = document.createElement('span');
s.className = 'page-dots';
s.textContent = '...';
numbers.appendChild(s);
return;
}
var b = document.createElement('button');
b.type = 'button';
b.className = 'page-number' + (t === state.page ? ' active' : '');
b.textContent = t;
b.addEventListener('click', function () { state.page = t; render(true); });
numbers.appendChild(b);
});
prevBtn.classList.toggle('disabled', state.page <= 1);
nextBtn.classList.toggle('disabled', state.page >= total);
}
function render(scroll) {
var m = matching();
var total = Math.max(1, Math.ceil(m.length / pageSize));
if (state.page > total) state.page = total;
if (state.page < 1) state.page = 1;
var start = (state.page - 1) * pageSize;
var end = start + pageSize;
cards.forEach(function (c) { c.hidden = true; });
m.slice(start, end).forEach(function (c) { c.hidden = false; });
buildPager(total);
if (scroll) list.scrollIntoView({ behavior: 'smooth', block: 'start' });
}
chips.forEach(function (chip) { chips.forEach(function (chip) {
chip.addEventListener('click', function () { chip.addEventListener('click', function () {
chips.forEach(function (c) { c.classList.remove('is-active'); }); chips.forEach(function (c) { c.classList.remove('is-active'); });
chip.classList.add('is-active'); chip.classList.add('is-active');
apply(chip.getAttribute('data-type')); state.type = chip.getAttribute('data-type');
state.page = 1;
render(false);
}); });
}); });
if (prevBtn) prevBtn.addEventListener('click', function () { if (state.page > 1) { state.page--; render(true); } });
if (nextBtn) nextBtn.addEventListener('click', function () { state.page++; render(true); });
render(false);
})(); })();

View File

@ -261,7 +261,7 @@
</div> </div>
</section> </section>
<section class="latest-news-section"> <section class="latest-news-section">
<div class="news-header"> <div class="news-header">
<h2>Najnovejše novice</h2> <h2>Najnovejše novice</h2>
<p>Kaj je novega pri MSOS, od dogodkov in projektov do blog zgodb in obvestil.</p> <p>Kaj je novega pri MSOS, od dogodkov in projektov do blog zgodb in obvestil.</p>

View File

@ -349,6 +349,11 @@
</div> </div>
</article> </article>
</div> </div>
<nav class="pagination newshub-pagination" data-page-size="9" hidden>
<button type="button" class="pagination-arrow" data-nav="prev"><i class="fas fa-arrow-left" aria-hidden="true"></i> Prejšnja</button>
<div class="pagination-numbers"></div>
<button type="button" class="pagination-arrow" data-nav="next">Naslednja <i class="fas fa-arrow-right" aria-hidden="true"></i></button>
</nav>
</section> </section>
</main> </main>