bookclub-advr

DSLC Advanced R Book Club
git clone https://git.eamoncaddigan.net/bookclub-advr.git
Log | Files | Refs | README | LICENSE

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 }