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