Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/resources/projects/website/listing/quarto-listing.js
12923 views
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
}
255
256