Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 20 additions & 16 deletions src/resources/projects/website/listing/quarto-listing.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,16 @@ window["quarto-listing-loaded"] = () => {
// The actual list
const list = window["quarto-listings"][listingId];

// Update the handlers for pagination events
refreshPaginationHandlers(listingId);
// Record the page hash on pagination clicks
attachPaginationHandler(listingId);

// Render any visible items that need it
renderVisibleProgressiveImages(list);

// Whenever the list is updated, we also need to
// attach handlers to the new pagination elements
// and refresh any newly visible items.
// Whenever the list is updated, refresh any newly visible items.
list.on("updated", function () {
renderVisibleProgressiveImages(list);
window.quartoDecorateExternalLinks?.(list.list);
setTimeout(() => refreshPaginationHandlers(listingId));

// Show or hide the no matching message
toggleNoMatchingMessage(list);
Expand Down Expand Up @@ -130,19 +127,26 @@ function getListingPageKey(listingId) {
return `${listingId}-page`;
}

function refreshPaginationHandlers(listingId) {
function attachPaginationHandler(listingId) {
// list.js rebuilds the page links on every update but keeps their
// container, so a single listener there covers every link.
const listingEl = window.document.getElementById(listingId);
const paginationEls = listingEl.querySelectorAll(
".pagination li.page-item:not(.disabled) .page.page-link"
);
for (const paginationEl of paginationEls) {
const paginationEl = listingEl.querySelector(".pagination");
if (!paginationEl) {
return;
}
paginationEl.addEventListener("click", (event) => {
const pageLink = event.target.closest(
"li.page-item:not(.disabled) .page.page-link"
);
if (!pageLink) {
return;
}
// list.js's pagination plugin shows the page from its own click
// listener on the pagination list; this handler only records the hash.
paginationEl.onclick = (sender) => {
setPageHash(listingId, sender.target.getAttribute("data-i"));
return false;
};
}
event.preventDefault();
setPageHash(listingId, pageLink.getAttribute("data-i"));
});
}

function renderVisibleProgressiveImages(list) {
Expand Down
17 changes: 17 additions & 0 deletions tests/integration/playwright/tests/listing-interactions.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,23 @@ test.describe("Listing pagination", () => {
await expect(page).toHaveURL(/#listing-second-listing-page=3$/);
});

test("page link clicked right after an update writes the page hash", async ({ page }) => {
await page.goto(fixture);

// Click page 2 then page 3 in one task, so no timer runs between the
// update that rebuilds the links and the click on a rebuilt link.
await page.evaluate(() => {
const link = (n: string) =>
[...document.querySelectorAll<HTMLElement>("#second-listing-pagination .page.page-link")]
.find((el) => el.textContent?.trim() === n)!;
link("2").click();
link("3").click();
});

await expect(titles(page, "second-listing")).toHaveText(["Post 3"]);
await expect(page).toHaveURL(/#listing-second-listing-page=3$/);
});

test("back restores the previous page", async ({ page }) => {
await page.goto(fixture);
await pageLink(page, "second-listing", "2").click();
Expand Down
Loading