PageSourceSearch

https://denis-engemann.de/site_libs/quarto-listing/quarto-listing.js

js denis-engemann.de collected 2026-10-01 13:08:04 UTC 7,028 bytes, 253 lines download raw bytes

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.