PageSourceSearch

https://www.hitprace.cz/js/job-favorites.js?v=357

js hitprace.cz collected 2026-10-02 11:46:33 UTC 5,278 bytes, 179 lines download raw bytes

1(function () {
2  "use strict";
3
4  var ACTION_ENDPOINT = "/webservices/job_favorite_action.php";
5  var STATUS_ENDPOINT = "/webservices/job_favorites_status.php";
6  var stateByJobId = Object.create(null);
7
8  function toBool(value) {
9    return value === true || value === 1 || value === "1" || value === "true";
10  }
11
12  function collectButtons() {
13    return Array.prototype.slice.call(document.querySelectorAll(".hp-favorite-toggle[data-job-id]"));
14  }
15
16  function buttonsForJob(jobId) {
17    return collectButtons().filter(function (btn) {
18      return btn.getAttribute("data-job-id") === jobId;
19    });
20  }
21
22  function setButtonState(btn, isFavorite) {
23    var addLabel = btn.getAttribute("data-label-add") || "Ulozit";
24    var removeLabel = btn.getAttribute("data-label-remove") || "Ulozeno";
25    btn.setAttribute("data-is-favorite", isFavorite ? "1" : "0");
26    btn.setAttribute("aria-pressed", isFavorite ? "true" : "false");
27    btn.textContent = isFavorite ? removeLabel : addLabel;
28
29    var wrap = btn.closest(".hp-favorite-toggle-wrap");
30    if (wrap) {
31      var hint = wrap.querySelector(".hp-favorite-hint");
32      if (hint) {
33        hint.hidden = !!isFavorite;
34      }
35      var feedback = wrap.querySelector(".hp-favorite-feedback");
36      if (feedback) {
37        if (feedback.__hideTimer) {
38          clearTimeout(feedback.__hideTimer);
39          feedback.__hideTimer = null;
40        }
41        if (isFavorite) {
42          feedback.hidden = false;
43          requestAnimationFrame(function () {
44            feedback.classList.add("is-visible");
45          });
46        } else {
47          feedback.classList.remove("is-visible");
48          feedback.__hideTimer = setTimeout(function () {
49            feedback.hidden = true;
50          }, 170);
51        }
52      }
53    }
54  }
55
56  function pulseButtonsForJob(jobId) {
57    buttonsForJob(jobId).forEach(function (btn) {
58      btn.classList.remove("is-pop");
59      void btn.offsetWidth;
60      btn.classList.add("is-pop");
61      setTimeout(function () {
62        btn.classList.remove("is-pop");
63      }, 420);
64    });
65  }
66
67  function syncJobState(jobId, isFavorite) {
68    stateByJobId[jobId] = !!isFavorite;
69    buttonsForJob(jobId).forEach(function (btn) {
70      setButtonState(btn, !!isFavorite);
71    });
72  }
73
74  function setJobBusy(jobId, isBusy) {
75    buttonsForJob(jobId).forEach(function (btn) {
76      btn.disabled = !!isBusy;
77    });
78  }
79
80  async function loadFavoriteStatesForPage(jobIds) {
81    if (!jobIds.length) return;
82    try {
83      var res = await fetch(STATUS_ENDPOINT, {
84        method: "POST",
85        headers: { "Content-Type": "application/json" },
86        body: JSON.stringify({ job_ids: jobIds })
87      });
88      var data = await res.json();
89      if (!res.ok || !data || data.ok !== true) return;
90      var favSet = Object.create(null);
91      var list = data.favorited_ids || [];
92      for (var i = 0; i < list.length; i++) {
93        favSet[list[i]] = true;
94      }
95      for (var j = 0; j < jobIds.length; j++) {
96        var jid = jobIds[j];
97        syncJobState(jid, !!favSet[jid]);
98      }
99    } catch (e) {
100      // keep server-rendered defaults
101    }
102  }
103
104  async function saveJobState(jobId, nextFavorite) {
105    var res = await fetch(ACTION_ENDPOINT, {
106      method: "POST",
107      headers: { "Content-Type": "application/json" },
108      body: JSON.stringify({ job_id: jobId, favorite: !!nextFavorite })
109    });
110    var data = await res.json();
111    if (!res.ok || !data || data.ok !== true) {
112      throw new Error((data && data.error) || "save_failed");
113    }
114    return toBool(data.is_favorite);
115  }
116
117  function pushDataLayerEvent(jobId, isFavorite) {
118    window.dataLayer = window.dataLayer || [];
119    window.dataLayer.push({
120      event: isFavorite ? "job_favorite_add" : "job_favorite_remove",
121      job_id: jobId
122    });
123  }
124
125  async function onToggleClick(btn) {
126    var jobId = btn.getAttribute("data-job-id");
127    if (!jobId) return;
128
129    var current = Object.prototype.hasOwnProperty.call(stateByJobId, jobId)
130      ? !!stateByJobId[jobId]
131      : toBool(btn.getAttribute("data-is-favorite"));
132    var next = !current;
133
134    setJobBusy(jobId, true);
135    syncJobState(jobId, next); // optimistic update
136
137    try {
138      var confirmed = await saveJobState(jobId, next);
139      syncJobState(jobId, confirmed);
140      if (confirmed && !current) {
141        pulseButtonsForJob(jobId);
142      }
143      pushDataLayerEvent(jobId, confirmed);
144    } catch (e) {
145      syncJobState(jobId, current); // rollback
146    } finally {
147      setJobBusy(jobId, false);
148    }
149  }
150
151  function initFavorites() {
152    var buttons = collectButtons();
153    if (!buttons.length) return;
154
155    var seen = Object.create(null);
156    var jobIdsOnPage = [];
157    buttons.forEach(function (btn) {
158      var jobId = btn.getAttribute("data-job-id");
159      if (!jobId) return;
160      setButtonState(btn, toBool(btn.getAttribute("data-is-favorite")));
161
162      btn.addEventListener("click", function () {
163        onToggleClick(btn);
164      });
165
166      if (!seen[jobId]) {
167        seen[jobId] = true;
168        jobIdsOnPage.push(jobId);
169      }
170    });
171    loadFavoriteStatesForPage(jobIdsOnPage);
172  }
173
174  if (document.readyState === "loading") {
175    document.addEventListener("DOMContentLoaded", initFavorites);
176  } else {
177    initFavorites();
178  }
179})();

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.