PageSourceSearch

https://quartzmountain.org/js/app/app.favorites.js?v=swg_b2MvKoy7ZCJsw1bxDCt6FuHGZiGY_F_SfSMU1uE

js quartzmountain.org collected 2026-10-02 06:32:51 UTC 3,081 bytes, 80 lines download raw bytes

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.