1/* 2 * QuartzMountain favorites / wishlist. 3 * 4 * Wires up every .fav-btn on the page: on load it asks the server which items the signed-in 5 * user has already saved and lights those hearts; on click it toggles the save (POST) and 6 * flips the heart. If the user isn't signed in, the first click sends them to log in and 7 * returns them here afterwards. 8 */ 9(function () { 10 "use strict"; 11 12 function buttons() { 13 return Array.prototype.slice.call(document.querySelectorAll(".fav-btn")); 14 } 15 function keyOf(btn) { 16 return btn.getAttribute("data-fav-type") + ":" + btn.getAttribute("data-fav-key"); 17 } 18 function setSaved(btn, saved) { 19 btn.classList.toggle("is-saved", !!saved); 20 btn.setAttribute("aria-pressed", saved ? "true" : "false"); 21 var lbl = btn.querySelector(".fav-btn-txt"); 22 if (lbl) lbl.textContent = saved ? "Saved" : "Save"; 23 } 24 25 var signedIn = false; 26 27 function hydrate() { 28 var btns = buttons(); 29 if (!btns.length) return; 30 fetch("/favorites/keys", { headers: { "Accept": "application/json" } }) 31 .then(function (r) { return r.ok ? r.json() : { signedIn: false, keys: [] }; }) 32 .then(function (data) { 33 signedIn = !!data.signedIn; 34 var set = {}; 35 (data.keys || []).forEach(function (k) { set[k] = true; }); 36 btns.forEach(function (b) { if (set[keyOf(b)]) setSaved(b, true); }); 37 }) 38 .catch(function () { /* leave hearts empty on error */ }); 39 } 40 41 function toggle(btn) { 42 if (btn.dataset.busy === "1") return; 43 btn.dataset.busy = "1"; 44 var payload = { 45 itemType: btn.getAttribute("data-fav-type"), 46 itemKey: btn.getAttribute("data-fav-key"), 47 title: btn.getAttribute("data-fav-title"), 48 subtitle: btn.getAttribute("data-fav-subtitle"), 49 url: btn.getAttribute("data-fav-url"), 50 imagePath: btn.getAttribute("data-fav-image") 51 }; 52 fetch("/favorites/toggle", { 53 method: "POST", 54 headers: { "Content-Type": "application/json", "Accept": "application/json" }, 55 body: JSON.stringify(payload) 56 }).then(function (r) { 57 if (r.status === 401) { 58 window.location.href = "/auth/log-in?returnUrl=" + encodeURIComponent(window.location.pathname + window.location.search); 59 return null; 60 } 61 return r.ok ? r.json() : null; 62 }).then(function (res) { 63 if (res) setSaved(btn, res.saved); 64 }).catch(function () { /* no-op */ }) 65 .finally(function () { btn.dataset.busy = "0"; }); 66 } 67 68 document.addEventListener("click", function (e) { 69 var btn = e.target.closest ? e.target.closest(".fav-btn") : null; 70 if (!btn) return; 71 e.preventDefault(); 72 toggle(btn); 73 }); 74 75 if (document.readyState === "loading") { 76 document.addEventListener("DOMContentLoaded", hydrate); 77 } else { 78 hydrate(); 79 } 80})();
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.