Skip to content
Open
Changes from 1 commit
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
126 changes: 93 additions & 33 deletions src/resources/projects/website/listing/quarto-listing.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
const kProgressiveAttr = "data-src";
let categoriesLoaded = false;
let selectedCategories = new Set();
const kDefaultCategory = ""; // Default category "" means all posts selected

window.quartoListingCategory = (category) => {
// category is URI encoded in EJS template for UTF-8 support
category = decodeURIComponent(atob(category));
if (categoriesLoaded) {
activateCategory(category);
setCategoryHash(category);
setCategoryHash();
}
};

Expand All @@ -15,11 +17,19 @@ window["quarto-listing-loaded"] = () => {
const hash = getHash();

if (hash) {
// If there is a category, switch to that
if (hash.category) {
// category hash are URI encoded so we need to decode it before processing
// so that we can match it with the category element processed in JS
activateCategory(decodeURIComponent(hash.category));
// If there are categories, switch to those
if (hash.categories) {
const cats = hash.categories.split(",");
for (const cat of cats) {
if (cat) selectedCategories.add(decodeURIComponent(cat));
}
updateCategoryUI();
filterListingCategories();
} else {
// No categories in hash, use default
selectedCategories.add(kDefaultCategory);
updateCategoryUI();
filterListingCategories();
}
// Paginate a specific listing
const listingIds = Object.keys(window["quarto-listings"]);
Expand All @@ -29,6 +39,11 @@ window["quarto-listing-loaded"] = () => {
showPage(listingId, page);
}
}
} else {
// No hash at all, use default category
selectedCategories.add(kDefaultCategory);
updateCategoryUI();
filterListingCategories();
}

const listingIds = Object.keys(window["quarto-listings"]);
Expand Down Expand Up @@ -66,9 +81,14 @@ window.document.addEventListener("DOMContentLoaded", function (_event) {
const category = decodeURIComponent(
atob(categoryEl.getAttribute("data-category"))
);
categoryEl.onclick = () => {
categoryEl.onclick = (e) => {
// Allow holding Ctrl/Cmd key for multiple selection
// Clear other selections if not using Ctrl/Cmd
if (!e.ctrlKey && !e.metaKey) {
selectedCategories.clear();
}
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if that is a good idea as it makes the multiple selection feature "hidden".
I thought this was less disruptive than checkboxes everywhere.
This was the only idea I came up with to keep previous visual appearance.

activateCategory(category);
setCategoryHash(category);
setCategoryHash();
};
}

Expand All @@ -79,11 +99,29 @@ window.document.addEventListener("DOMContentLoaded", function (_event) {
);
for (const categoryTitleEl of categoryTitleEls) {
categoryTitleEl.onclick = () => {
activateCategory("");
setCategoryHash("");
selectedCategories.clear();
updateCategoryUI();
setCategoryHash();
filterListingCategories();
};
}

// Process any existing hash for multiple categories
const hash = getHash();
if (hash && hash.categories) {
const cats = hash.categories.split(",");
for (const cat of cats) {
if (cat) selectedCategories.add(decodeURIComponent(cat));
}
updateCategoryUI();
filterListingCategories();
} else {
// No hash at all, use default category
selectedCategories.add(kDefaultCategory);
updateCategoryUI();
filterListingCategories();
}

categoriesLoaded = true;
});

Expand All @@ -101,8 +139,15 @@ function toggleNoMatchingMessage(list) {
}
}

function setCategoryHash(category) {
setHash({ category });
function setCategoryHash() {
if (selectedCategories.size === 0) {
setHash({});
} else {
const categoriesStr = Array.from(selectedCategories)
.map((cat) => encodeURIComponent(cat))
.join(",");
setHash({ category: categoriesStr });
}
}

function setPageHash(listingId, page) {
Expand Down Expand Up @@ -205,45 +250,60 @@ function showPage(listingId, page) {
}

function activateCategory(category) {
// Deactivate existing categories
const activeEls = window.document.querySelectorAll(
".quarto-listing-category .category.active"
);
for (const activeEl of activeEls) {
activeEl.classList.remove("active");
if (selectedCategories.has(category)) {
selectedCategories.delete(category);
} else {
selectedCategories.add(category);
}
updateCategoryUI();
filterListingCategories();
}

// Activate this category
const categoryEl = window.document.querySelector(
`.quarto-listing-category .category[data-category='${btoa(
encodeURIComponent(category)
)}']`
function updateCategoryUI() {
// Deactivate all categories first
const activeEls = window.document.querySelectorAll(
".quarto-listing-category .category"
);
if (categoryEl) {
categoryEl.classList.add("active");
for (const activeEls of activeEls) {
activeEls.classList.remove("active");
}

// Filter the listings to this category
filterListingCategory(category);
// Activate selected categories
for (const category of selectedCategories) {
const categoryEl = window.document.querySelector(
`.quarto-listing-category .category[data-category='${btoa(
encodeURIComponent(category)
)}']`
);
if (categoryEl) {
categoryEl.classList.add("active");
}
}
}

function filterListingCategory(category) {
function filterListingCategories() {
const listingIds = Object.keys(window["quarto-listings"]);
for (const listingId of listingIds) {
const list = window["quarto-listings"][listingId];
if (list) {
if (category === "") {
// resets the filter
if (selectedCategories.size === 0 ||
(selectedCategories.size === 1 && selectedCategories.has(kDefaultCategory))) {
// Reset the filter when no categories selected or only default category
list.filter();
} else {
// filter to this category
// Filter to selected categories, but ignore kDefaultCategory if other categories selected
const effectiveCategories = new Set(selectedCategories);
if (effectiveCategories.size > 1) {
effectiveCategories.delete(kDefaultCategory);
}

list.filter(function (item) {
const itemValues = item.values();
if (itemValues.categories !== null) {
const categories = decodeURIComponent(
const itemCategories = decodeURIComponent(
atob(itemValues.categories)
).split(",");
return categories.includes(category);
return itemCategories.some(category => effectiveCategories.has(category));
} else {
return false;
}
Expand Down
Loading