quarto-listing.js (7111B)
1 const kProgressiveAttr = "data-src"; 2 let categoriesLoaded = false; 3 4 window.quartoListingCategory = (category) => { 5 // category is URI encoded in EJS template for UTF-8 support 6 category = decodeURIComponent(atob(category)); 7 if (categoriesLoaded) { 8 activateCategory(category); 9 setCategoryHash(category); 10 } 11 }; 12 13 window["quarto-listing-loaded"] = () => { 14 // Process any existing hash 15 const hash = getHash(); 16 17 if (hash) { 18 // If there is a category, switch to that 19 if (hash.category) { 20 // category hash are URI encoded so we need to decode it before processing 21 // so that we can match it with the category element processed in JS 22 activateCategory(decodeURIComponent(hash.category)); 23 } 24 // Paginate a specific listing 25 const listingIds = Object.keys(window["quarto-listings"]); 26 for (const listingId of listingIds) { 27 const page = hash[getListingPageKey(listingId)]; 28 if (page) { 29 showPage(listingId, page); 30 } 31 } 32 } 33 34 const listingIds = Object.keys(window["quarto-listings"]); 35 for (const listingId of listingIds) { 36 // The actual list 37 const list = window["quarto-listings"][listingId]; 38 39 // Update the handlers for pagination events 40 refreshPaginationHandlers(listingId); 41 42 // Render any visible items that need it 43 renderVisibleProgressiveImages(list); 44 45 // Whenever the list is updated, we also need to 46 // attach handlers to the new pagination elements 47 // and refresh any newly visible items. 48 list.on("updated", function () { 49 renderVisibleProgressiveImages(list); 50 setTimeout(() => refreshPaginationHandlers(listingId)); 51 52 // Show or hide the no matching message 53 toggleNoMatchingMessage(list); 54 }); 55 } 56 }; 57 58 window.document.addEventListener("DOMContentLoaded", function (_event) { 59 // Attach click handlers to categories 60 const categoryEls = window.document.querySelectorAll( 61 ".quarto-listing-category .category" 62 ); 63 64 for (const categoryEl of categoryEls) { 65 // category needs to support non ASCII characters 66 const category = decodeURIComponent( 67 atob(categoryEl.getAttribute("data-category")) 68 ); 69 categoryEl.onclick = () => { 70 activateCategory(category); 71 setCategoryHash(category); 72 }; 73 } 74 75 // Attach a click handler to the category title 76 // (there should be only one, but since it is a class name, handle N) 77 const categoryTitleEls = window.document.querySelectorAll( 78 ".quarto-listing-category-title" 79 ); 80 for (const categoryTitleEl of categoryTitleEls) { 81 categoryTitleEl.onclick = () => { 82 activateCategory(""); 83 setCategoryHash(""); 84 }; 85 } 86 87 categoriesLoaded = true; 88 }); 89 90 function toggleNoMatchingMessage(list) { 91 const selector = `#${list.listContainer.id} .listing-no-matching`; 92 const noMatchingEl = window.document.querySelector(selector); 93 if (noMatchingEl) { 94 if (list.visibleItems.length === 0) { 95 noMatchingEl.classList.remove("d-none"); 96 } else { 97 if (!noMatchingEl.classList.contains("d-none")) { 98 noMatchingEl.classList.add("d-none"); 99 } 100 } 101 } 102 } 103 104 function setCategoryHash(category) { 105 setHash({ category }); 106 } 107 108 function setPageHash(listingId, page) { 109 const currentHash = getHash() || {}; 110 currentHash[getListingPageKey(listingId)] = page; 111 setHash(currentHash); 112 } 113 114 function getListingPageKey(listingId) { 115 return `${listingId}-page`; 116 } 117 118 function refreshPaginationHandlers(listingId) { 119 const listingEl = window.document.getElementById(listingId); 120 const paginationEls = listingEl.querySelectorAll( 121 ".pagination li.page-item:not(.disabled) .page.page-link" 122 ); 123 for (const paginationEl of paginationEls) { 124 paginationEl.onclick = (sender) => { 125 setPageHash(listingId, sender.target.getAttribute("data-i")); 126 showPage(listingId, sender.target.getAttribute("data-i")); 127 return false; 128 }; 129 } 130 } 131 132 function renderVisibleProgressiveImages(list) { 133 // Run through the visible items and render any progressive images 134 for (const item of list.visibleItems) { 135 const itemEl = item.elm; 136 if (itemEl) { 137 const progressiveImgs = itemEl.querySelectorAll( 138 `img[${kProgressiveAttr}]` 139 ); 140 for (const progressiveImg of progressiveImgs) { 141 const srcValue = progressiveImg.getAttribute(kProgressiveAttr); 142 if (srcValue) { 143 progressiveImg.setAttribute("src", srcValue); 144 } 145 progressiveImg.removeAttribute(kProgressiveAttr); 146 } 147 } 148 } 149 } 150 151 function getHash() { 152 // Hashes are of the form 153 // #name:value|name1:value1|name2:value2 154 const currentUrl = new URL(window.location); 155 const hashRaw = currentUrl.hash ? currentUrl.hash.slice(1) : undefined; 156 return parseHash(hashRaw); 157 } 158 159 const kAnd = "&"; 160 const kEquals = "="; 161 162 function parseHash(hash) { 163 if (!hash) { 164 return undefined; 165 } 166 const hasValuesStrs = hash.split(kAnd); 167 const hashValues = hasValuesStrs 168 .map((hashValueStr) => { 169 const vals = hashValueStr.split(kEquals); 170 if (vals.length === 2) { 171 return { name: vals[0], value: vals[1] }; 172 } else { 173 return undefined; 174 } 175 }) 176 .filter((value) => { 177 return value !== undefined; 178 }); 179 180 const hashObj = {}; 181 hashValues.forEach((hashValue) => { 182 hashObj[hashValue.name] = decodeURIComponent(hashValue.value); 183 }); 184 return hashObj; 185 } 186 187 function makeHash(obj) { 188 return Object.keys(obj) 189 .map((key) => { 190 return `${key}${kEquals}${obj[key]}`; 191 }) 192 .join(kAnd); 193 } 194 195 function setHash(obj) { 196 const hash = makeHash(obj); 197 window.history.pushState(null, null, `#${hash}`); 198 } 199 200 function showPage(listingId, page) { 201 const list = window["quarto-listings"][listingId]; 202 if (list) { 203 list.show((page - 1) * list.page + 1, list.page); 204 } 205 } 206 207 function activateCategory(category) { 208 // Deactivate existing categories 209 const activeEls = window.document.querySelectorAll( 210 ".quarto-listing-category .category.active" 211 ); 212 for (const activeEl of activeEls) { 213 activeEl.classList.remove("active"); 214 } 215 216 // Activate this category 217 const categoryEl = window.document.querySelector( 218 `.quarto-listing-category .category[data-category='${btoa( 219 encodeURIComponent(category) 220 )}']` 221 ); 222 if (categoryEl) { 223 categoryEl.classList.add("active"); 224 } 225 226 // Filter the listings to this category 227 filterListingCategory(category); 228 } 229 230 function filterListingCategory(category) { 231 const listingIds = Object.keys(window["quarto-listings"]); 232 for (const listingId of listingIds) { 233 const list = window["quarto-listings"][listingId]; 234 if (list) { 235 if (category === "") { 236 // resets the filter 237 list.filter(); 238 } else { 239 // filter to this category 240 list.filter(function (item) { 241 const itemValues = item.values(); 242 if (itemValues.categories !== null) { 243 const categories = decodeURIComponent( 244 atob(itemValues.categories) 245 ).split(","); 246 return categories.includes(category); 247 } else { 248 return false; 249 } 250 }); 251 } 252 } 253 } 254 }