diff --git a/odoo-community-developers/index.html b/odoo-community-developers/index.html
index 21eeb63..4736b74 100644
--- a/odoo-community-developers/index.html
+++ b/odoo-community-developers/index.html
@@ -324,6 +324,64 @@
outline-offset: 2px;
}
+ /* Pagination — nine cards per page. Script-dependent BY DESIGN: with JavaScript
+ off the element keeps its `hidden` attribute and every build-time card shows.
+ `[hidden]` needs its own rule because this class sets `display`. */
+ .dir-pager {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ max-width: 1000px;
+ margin: 1.75rem auto 0;
+ }
+ .dir-pager[hidden] {
+ display: none;
+ }
+ .dir-pager-pages {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.35rem;
+ }
+ .dir-page {
+ font: inherit;
+ font-size: 0.9rem;
+ min-width: 2.4rem;
+ padding: 0.4rem 0.7rem;
+ color: #fff;
+ background: transparent;
+ border: 1px solid var(--line-brass);
+ border-radius: var(--radius);
+ cursor: pointer;
+ }
+ .dir-page:hover:not([disabled]) {
+ border-color: var(--brass-bright);
+ }
+ .dir-page:focus-visible {
+ outline: 3px solid var(--brass-bright);
+ outline-offset: 2px;
+ }
+ /* The current page is carried three ways — a filled pill, `aria-current`, and the
+ "Page N of M" line — so it is never signalled by colour alone. */
+ .dir-page[aria-current="page"] {
+ background: var(--brass);
+ border-color: var(--brass-deep);
+ color: var(--midnight);
+ font-weight: 600;
+ }
+ .dir-page[disabled] {
+ opacity: 0.45;
+ cursor: default;
+ }
+ .dir-pager-status {
+ flex-basis: 100%;
+ margin: 0;
+ text-align: center;
+ font-size: 0.82rem;
+ color: rgba(255, 255, 255, 0.72);
+ }
+
/* The grid of freelancer cards */
.dir-grid {
display: grid;
@@ -1216,6 +1274,18 @@
+
+
+
+
Nothing matches. Widen your search, or .
@@ -2545,6 +2615,7 @@
records.forEach(function (rec) { grid.appendChild(liveCard(rec, active)); });
// The button is added after the cards exist — only while the route is set.
Array.prototype.forEach.call(grid.querySelectorAll(".dev-card"), enhanceAsk);
+ dirPage = 1;
applyFilters();
})
.catch(function () { /* the build-time list already on the page stands */ });
@@ -2554,11 +2625,13 @@
function initFilters() {
var bar = document.getElementById("dirFilters");
if (!bar) return;
+ // A new filter or query starts at page 1 — staying on page 3 after narrowing to
+ // two matches is the classic pagination bug.
bar.addEventListener("change", function (event) {
- if (event.target && event.target.tagName === "SELECT") applyFilters();
+ if (event.target && event.target.tagName === "SELECT") { dirPage = 1; applyFilters(); }
});
bar.addEventListener("input", function (event) {
- if (event.target && event.target.hasAttribute("data-search-box")) applyFilters();
+ if (event.target && event.target.hasAttribute("data-search-box")) { dirPage = 1; applyFilters(); }
});
var emptyReset = document.getElementById("dirEmptyReset");
if (emptyReset) {
@@ -2566,12 +2639,75 @@
var box = bar.querySelector("[data-search-box]");
if (box) box.value = "";
Array.prototype.forEach.call(bar.querySelectorAll("select"), function (s) { s.value = ""; });
+ dirPage = 1;
applyFilters();
});
}
applyFilters();
}
+ /* ---- The pager: nine cards per page ---------------------------------------
+ It renders ONLY when the filtered list needs more than one page — the same rule
+ the facets follow. With a short list it stays hidden, so no pager appears. */
+ var PER_PAGE = 9;
+ var dirPage = 1;
+
+ function renderPager(total) {
+ var pager = document.getElementById("dirPager");
+ var pages = document.getElementById("dirPages");
+ var prev = document.getElementById("dirPrev");
+ var next = document.getElementById("dirNext");
+ var status = document.getElementById("dirPagerStatus");
+ if (!pager || !pages) return;
+ var pageCount = Math.ceil(total / PER_PAGE);
+ if (pageCount <= 1) {
+ pager.hidden = true;
+ pages.textContent = "";
+ return;
+ }
+ var active = document.activeElement;
+ var keepFocus = pager.contains(active);
+ pager.hidden = false;
+ pages.textContent = "";
+ for (var p = 1; p <= pageCount; p++) {
+ var b = document.createElement("button");
+ b.type = "button";
+ b.className = "dir-page";
+ b.textContent = String(p);
+ b.setAttribute("data-page", String(p));
+ b.setAttribute("aria-label", "Page " + p + " of " + pageCount);
+ if (p === dirPage) b.setAttribute("aria-current", "page");
+ pages.appendChild(b);
+ }
+ if (prev) prev.disabled = dirPage <= 1;
+ if (next) next.disabled = dirPage >= pageCount;
+ if (status) status.textContent = "Page " + dirPage + " of " + pageCount;
+ // Keep the keyboard where it was. Previous/Next survive the rebuild, so focus
+ // stays on them; a number button is replaced, so focus moves to the current page
+ // — never dropped to the top of the document.
+ if (keepFocus) {
+ if (active && document.contains(active) && !active.disabled) active.focus();
+ else {
+ var current = pages.querySelector('[aria-current="page"]');
+ if (current) current.focus();
+ }
+ }
+ }
+
+ function initPager() {
+ var pager = document.getElementById("dirPager");
+ if (!pager) return;
+ pager.addEventListener("click", function (event) {
+ var btn = event.target.closest ? event.target.closest(".dir-page") : null;
+ if (!btn || btn.disabled) return;
+ var target = btn.getAttribute("data-page");
+ if (target === "prev") dirPage -= 1;
+ else if (target === "next") dirPage += 1;
+ else dirPage = parseInt(target, 10);
+ applyFilters();
+ });
+ }
+
function applyFilters() {
var bar = document.getElementById("dirFilters");
var grid = document.getElementById("dirGrid");
@@ -2588,7 +2724,7 @@
bar.hidden = nothing;
if (count) count.hidden = nothing;
- var shown = 0;
+ var matched = [];
cards.forEach(function (card) {
var ok = true;
if (query) ok = (card.getAttribute("data-search") || "").indexOf(query) !== -1;
@@ -2603,11 +2739,19 @@
if (have.split("|").indexOf(want) === -1) ok = false;
});
}
- card.hidden = !ok;
- if (ok) shown++;
+ if (ok) matched.push(card);
});
- if (count && !count.hidden) count.textContent = "Showing " + shown + " of " + cards.length;
- if (empty) empty.hidden = nothing ? true : shown !== 0;
+
+ // Pagination applies to the FILTERED set. A page left out of range by a filter
+ // change falls back to page 1 rather than rendering an empty page.
+ var pageCount = Math.ceil(matched.length / PER_PAGE) || 1;
+ if (dirPage < 1 || dirPage > pageCount) dirPage = 1;
+ var page = matched.slice((dirPage - 1) * PER_PAGE, dirPage * PER_PAGE);
+ cards.forEach(function (card) { card.hidden = page.indexOf(card) === -1; });
+
+ if (count && !count.hidden) count.textContent = "Showing " + matched.length + " of " + cards.length;
+ if (empty) empty.hidden = nothing ? true : matched.length !== 0;
+ renderPager(matched.length);
}
/* ---- Form 1: freelancer application (create — no id) ---- */
@@ -2739,6 +2883,7 @@
initFocusCounter(document.getElementById("applyForm"), "apDescription", "apDescriptionCount", DESC_MAX_CHARS);
initAskModal();
Array.prototype.forEach.call(document.querySelectorAll(".dev-card"), enhanceAsk);
+ initPager();
initFilters();
initLiveList();
initApplyForm();