PageSourceSearch

https://edatop.ru/templates/eda/js/dietquiz.js?v=3.1.18

js edatop.ru collected 2026-10-02 10:26:08 UTC 11,820 bytes, 290 lines download raw bytes

1(function () {
2  "use strict";
3  if (window.__EDATOP_DIETQUIZ_V2__) return;
4  window.__EDATOP_DIETQUIZ_V2__ = true;
5
6  function onReady(fn) {
7    if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", fn);
8    else fn();
9  }
10  function qs(sel, root) { return (root || document).querySelector(sel); }
11  function qsa(sel, root) { return Array.prototype.slice.call((root || document).querySelectorAll(sel)); }
12  function clamp(n, a, b) { return Math.max(a, Math.min(b, n)); }
13
14  function cleanDietQuizUrl() {
15    try {
16      var u = new URL(window.location.href);
17      var changed = false;
18      Array.from(u.searchParams.keys()).forEach(function (key) {
19        if (/^dq_/i.test(key)) { u.searchParams.delete(key); changed = true; }
20      });
21      if (changed && window.history && history.replaceState) {
22        history.replaceState({}, document.title, u.pathname + (u.search ? u.search : "") + u.hash);
23      }
24    } catch (e) {}
25  }
26
27  function replaceQuizFromHtml(html, currentWrap, userInitiated) {
28    var doc = new DOMParser().parseFromString(html, "text/html");
29    var next = doc.getElementById("dietquiz");
30    if (!next) throw new Error("DietQuiz fragment not found");
31    currentWrap.replaceWith(next);
32    cleanDietQuizUrl();
33    initDietQuiz(next, !!userInitiated);
34    return next;
35  }
36
37  function loadQuizFragment(url, wrap, fetchOptions) {
38    var opts = fetchOptions || {};
39    opts.credentials = "same-origin";
40    opts.headers = Object.assign({ "X-Requested-With": "XMLHttpRequest" }, opts.headers || {});
41    return fetch(url, opts)
42      .then(function (r) { if (!r.ok) throw new Error("HTTP " + r.status); return r.text(); })
43      .then(function (html) { return replaceQuizFromHtml(html, wrap, true); });
44  }
45
46  function submitQuizFormAjax(form, wrap) {
47    var method = String(form.getAttribute("method") || "get").toLowerCase();
48    var action = form.getAttribute("action") || window.location.href;
49    var fd = new FormData(form);
50    if (method === "post") {
51      return loadQuizFragment(action, wrap, { method: "POST", body: fd });
52    }
53    var u = new URL(action, window.location.href);
54    fd.forEach(function (v, k) { u.searchParams.set(k, v); });
55    return loadQuizFragment(u.toString(), wrap);
56  }
57
58  function enhanceStatusSelect(select, wrap, markUserInitiated) {
59    if (!select || select.dataset.dqEnhanced === "1") return;
60    select.dataset.dqEnhanced = "1";
61    select.classList.add("dq-status-select-native");
62
63    var shell = document.createElement("div");
64    shell.className = "dq-status-select";
65    var button = document.createElement("button");
66    button.type = "button";
67    button.className = "dq-status-select__button";
68    button.setAttribute("aria-haspopup", "listbox");
69    button.setAttribute("aria-expanded", "false");
70    var menu = document.createElement("div");
71    menu.className = "dq-status-select__menu";
72    menu.setAttribute("role", "listbox");
73
74    function selectedText() {
75      var opt = select.options[select.selectedIndex];
76      return opt ? opt.textContent.trim() : "Без статуса";
77    }
78    function closeMenu() {
79      shell.classList.remove("is-open");
80      var card = shell.closest ? shell.closest(".dq-citem") : null;
81      if (card) card.classList.remove("dq-citem--status-open");
82      button.setAttribute("aria-expanded", "false");
83    }
84    function render() {
85      button.textContent = selectedText();
86      menu.innerHTML = "";
87      Array.prototype.forEach.call(select.options, function (opt) {
88        var item = document.createElement("button");
89        item.type = "button";
90        item.className = "dq-status-select__option" + (opt.selected ? " is-selected" : "");
91        item.setAttribute("role", "option");
92        item.setAttribute("aria-selected", opt.selected ? "true" : "false");
93        item.textContent = opt.textContent;
94        item.addEventListener("click", function (ev) {
95          ev.preventDefault();
96          ev.stopPropagation();
97          if (select.value === opt.value) { closeMenu(); return; }
98          markUserInitiated();
99          select.value = opt.value;
100          closeMenu();
101          select.dispatchEvent(new Event("change", { bubbles: true, cancelable: true }));
102        });
103        menu.appendChild(item);
104      });
105    }
106    button.addEventListener("click", function (ev) {
107      ev.preventDefault();
108      ev.stopPropagation();
109      var open = !shell.classList.contains("is-open");
110      qsa(".dq-status-select.is-open", wrap).forEach(function (x) {
111        if (x !== shell) {
112          x.classList.remove("is-open");
113          var oldCard = x.closest ? x.closest(".dq-citem") : null;
114          if (oldCard) oldCard.classList.remove("dq-citem--status-open");
115          var b = qs(".dq-status-select__button", x);
116          if (b) b.setAttribute("aria-expanded", "false");
117        }
118      });
119      shell.classList.toggle("is-open", open);
120      var card = shell.closest ? shell.closest(".dq-citem") : null;
121      if (card) card.classList.toggle("dq-citem--status-open", open);
122      button.setAttribute("aria-expanded", open ? "true" : "false");
123    });
124    shell.appendChild(button);
125    shell.appendChild(menu);
126    select.insertAdjacentElement("afterend", shell);
127    render();
128  }
129
130  function initDietQuiz(wrap, userInitiatedFromAjax) {
131    if (!wrap || wrap.dataset.dqInitV2 === "1") return;
132    wrap.dataset.dqInitV2 = "1";
133
134    var AUTOSCROLL_KEY = "dq_autoscroll";
135    var userInitiated = false;
136    function markUserInitiated() {
137      userInitiated = true;
138      try { sessionStorage.setItem(AUTOSCROLL_KEY, "1"); } catch (e) {}
139    }
140    function consumeUserInitiated() {
141      try {
142        var v = sessionStorage.getItem(AUTOSCROLL_KEY);
143        sessionStorage.removeItem(AUTOSCROLL_KEY);
144        return v === "1";
145      } catch (e) {}
146      return false;
147    }
148    userInitiated = !!userInitiatedFromAjax || consumeUserInitiated();
149
150    function getFixedHeaderOffset() {
151      var pad = 16;
152      try {
153        var header = document.querySelector(".head-line");
154        if (header) {
155          var cs = window.getComputedStyle(header);
156          if (cs && (cs.position === "fixed" || cs.position === "sticky")) pad += header.getBoundingClientRect().height || 0;
157        }
158      } catch (e) {}
159      return Math.max(0, Math.round(pad));
160    }
161
162    qsa("img[data-dq-fallback]", wrap).forEach(function (img) {
163      var raw = img.getAttribute("data-dq-fallback") || "";
164      if (!raw) return;
165      var list = raw.split("|").map(function (s) { return s.trim(); }).filter(Boolean);
166      function applyFallback() { if (list.length) { var next = list.shift(); if (next && img.src !== next) img.src = next; } }
167      img.addEventListener("error", applyFallback);
168      try { if (img.complete && img.naturalWidth === 0) applyFallback(); } catch (e) {}
169    });
170
171    var goalInput = qs("[data-dq-goal-input]", wrap);
172    var serverStep = parseInt(wrap.getAttribute("data-dq-server-step") || "1", 10);
173    var els = {
174      steps: qsa("[data-dq-step]", wrap),
175      progress: qs(".dq-progress__bar", wrap),
176      progressLabel: qs(".dq-progress__label", wrap),
177      prevBtns: qsa("[data-dq-prev]", wrap),
178      goalCards: qsa("[data-dq-goal]", wrap)
179    };
180
181    function scrollToQuizTop() {
182      try {
183        var rect = wrap.getBoundingClientRect();
184        var top = rect.top + window.pageYOffset - getFixedHeaderOffset();
185        window.scrollTo({ top: Math.max(0, top), behavior: "smooth" });
186      } catch (e) {}
187    }
188    function scrollToStepLabel() {
189      try {
190        var el = els.progressLabel || wrap.querySelector(".dq-progress__label");
191        if (!el) return scrollToQuizTop();
192        var rect = el.getBoundingClientRect();
193        var top = rect.top + window.pageYOffset - getFixedHeaderOffset();
194        window.scrollTo({ top: Math.max(0, top), behavior: "smooth" });
195      } catch (e) {}
196    }
197    function setStep(n, doScroll) {
198      n = clamp(n, 1, 3);
199      els.steps.forEach(function (s) { s.style.display = parseInt(s.getAttribute("data-dq-step"), 10) === n ? "" : "none"; });
200      var pct = Math.round(((n - 1) / 2) * 100);
201      if (els.progress) els.progress.style.width = pct + "%";
202      if (els.progressLabel) els.progressLabel.textContent = "Шаг " + n + "/3";
203      if (doScroll && userInitiated) { if (n === 2) scrollToStepLabel(); else scrollToQuizTop(); }
204    }
205
206    els.goalCards.forEach(function (card) {
207      card.addEventListener("click", function () {
208        markUserInitiated();
209        els.goalCards.forEach(function (c) { c.classList.remove("is-active"); });
210        card.classList.add("is-active");
211        if (goalInput) goalInput.value = card.getAttribute("data-dq-goal");
212        setStep(2, true);
213      });
214    });
215    els.prevBtns.forEach(function (btn) {
216      btn.addEventListener("click", function () {
217        markUserInitiated();
218        var visibleStep = 1;
219        els.steps.forEach(function (s) { if (s.style.display !== "none") visibleStep = parseInt(s.getAttribute("data-dq-step"), 10); });
220        setStep(visibleStep - 1, true);
221      });
222    });
223
224    qsa('a[href*="dq_"]', wrap).forEach(function (a) {
225      var serverUrl = a.href;
226      a.setAttribute("rel", "nofollow");
227      a.setAttribute("href", "#dietquiz");
228      a.addEventListener("click", function (ev) {
229        ev.preventDefault();
230        markUserInitiated();
231        loadQuizFragment(serverUrl, wrap).catch(function () { /* keep the page in place on AJAX failure */ });
232      });
233    });
234
235    var calcForm = wrap.querySelector("form.dq-formwrap");
236    if (calcForm) {
237      calcForm.addEventListener("submit", function (ev) {
238        ev.preventDefault();
239        markUserInitiated();
240        submitQuizFormAjax(calcForm, wrap).catch(function () {});
241      });
242    }
243
244    /* Status changes: intercept before inline onchange/form.submit can reload the page. */
245    wrap.addEventListener("change", function (ev) {
246      var select = ev.target && ev.target.closest ? ev.target.closest(".dq-statusform select") : null;
247      if (!select || !wrap.contains(select)) return;
248      ev.preventDefault();
249      ev.stopPropagation();
250      if (ev.stopImmediatePropagation) ev.stopImmediatePropagation();
251      var form = select.form || select.closest("form");
252      if (!form) return;
253      markUserInitiated();
254      form.classList.add("is-saving");
255      var shell = select.nextElementSibling && select.nextElementSibling.classList.contains("dq-status-select") ? select.nextElementSibling : null;
256      if (shell) shell.classList.add("is-loading");
257      submitQuizFormAjax(form, wrap).catch(function () {
258        form.classList.remove("is-saving");
259        if (shell) shell.classList.remove("is-loading");
260      });
261    }, true);
262
263    qsa(".dq-statusform select", wrap).forEach(function (select) { enhanceStatusSelect(select, wrap, markUserInitiated); });
264
265    document.addEventListener("click", function () {
266      qsa(".dq-status-select.is-open", wrap).forEach(function (x) {
267        x.classList.remove("is-open");
268        var card = x.closest ? x.closest(".dq-citem") : null;
269        if (card) card.classList.remove("dq-citem--status-open");
270        var b = qs(".dq-status-select__button", x);
271        if (b) b.setAttribute("aria-expanded", "false");
272      });
273    });
274    document.addEventListener("keydown", function (ev) {
275      if (ev.key === "Escape") qsa(".dq-status-select.is-open", wrap).forEach(function (x) {
276        x.classList.remove("is-open");
277        var card = x.closest ? x.closest(".dq-citem") : null;
278        if (card) card.classList.remove("dq-citem--status-open");
279      });
280    });
281
282    if (serverStep === 3) setStep(3, userInitiated);
283    else setStep(1, false);
284  }
285
286  onReady(function () {
287    cleanDietQuizUrl();
288    initDietQuiz(document.getElementById("dietquiz"), false);
289  });
290})();

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.