PageSourceSearch

https://vsh25.net/wp-content/themes/vsh25/assets/js/account/accoun…?ver=143a47a63fb6efde5424d9f09607edc2

js vsh25.net collected 2026-10-01 23:49:40 UTC 28,755 bytes, 795 lines download raw bytes

1// assets/js/account/account.core.js
2document.addEventListener("DOMContentLoaded", function () {
3   window.VSH25Account = window.VSH25Account || {
4      dom: {},
5      state: {},
6      utils: {},
7      api: {},
8      ui: {},
9      layout: {},
10      calendar: {},
11      wellbeing: {},
12      video: {},
13      flags: {},
14      keys: {},
15      selectors: {},
16   };
17
18   const A = window.VSH25Account;
19   if (A.__coreReady) return;
20   A.__coreReady = true;
21
22   const LS_KEY_HIDE_BIO_POPUP = "vsh25_hide_bio_popup";
23   const LS_KEY_SIDEBAR_COLLAPSED = "vsh25_lk_sidebar_collapsed";
24   const LS_KEY_SELECTED_DATE = "vsh25_selected_date";
25   const LS_KEY_SELECTED_DAY = "vsh25_saved_day";
26
27   const __VSH25_AJAX_ABORT = new Map();
28   const __VSH25_OVERLAY_QS = ".preloader,#preloader,.loader,.page-loader,.overlay,.modal-backdrop";
29   const __VSH25_OVERLAY_STALE_MS = 10000;
30   const __VSH25_OVERLAY_SWEEP_DELAY_MS = 180;
31   const __VSH25_OVERLAY_ERROR_FORCE_MIN_AGE_MS = 1200;
32   const __VSH25_OVERLAY_DEBUG = !!window.VSH25_DEBUG_OVERLAY;
33
34   const POPUPS_OPEN = "#ratingSelectionPopup.show, #wellbeingDiaryPopup.show";
35
36   const currentMonthYear = document.getElementById("currentMonthYear");
37   const wpTodayStr = currentMonthYear?.dataset.today;
38
39   let __VSH25_ACTIVE_API_REQUESTS = 0;
40   let __VSH25_OVERLAY_SWEEP_TIMER = null;
41
42   function localIsoDate(d = new Date()) {
43      return d.toLocaleDateString("sv-SE");
44   }
45
46   let today = wpTodayStr ? new Date(wpTodayStr + "T00:00:00") : new Date();
47   const TODAY_ISO = localIsoDate(today);
48
49   function isLocked(date) {
50      return (today - date) / 864e5 > 14;
51   }
52
53   // Выставляем глобальный флаг для свободной перемотки
54   window.vsh25AllowFreeSeek = Boolean(window.VSH25_RUNTIME && VSH25_RUNTIME.allowFreeSeek);
55
56   // Сохраняем шаблоны rating popup сразу, как в актуальном монолите
57   document.querySelectorAll(".rating-popup").forEach((p) => {
58      if (!p.dataset.tpl) p.dataset.tpl = p.innerHTML.trim();
59   });
60
61   function vsh25DebugOverlay(...args) {
62      if (!__VSH25_OVERLAY_DEBUG) return;
63      try {
64         // eslint-disable-next-line no-console
65         console.log("[vsh25][overlay]", ...args);
66      } catch {}
67   }
68
69   function vsh25IsLkContext() {
70      try {
71         return !!document.querySelector(".lk-calendar, .personal_wrapp, .lk-video, .course-video, .vshp");
72      } catch {
73         return false;
74      }
75   }
76
77   function vsh25ClosestSafe(node, selector) {
78      try {
79         if (!node || !node.closest) return null;
80         return node.closest(selector);
81      } catch {
82         return null;
83      }
84   }
85
86   function vsh25HasOpenPopup() {
87      try {
88         return !!document.querySelector(POPUPS_OPEN);
89      } catch {
90         return false;
91      }
92   }
93
94   function vsh25OverlayLooksVisible(el) {
95      if (!el) return false;
96
97      try {
98         const style = window.getComputedStyle(el);
99         if (!style) return false;
100         if (style.display === "none") return false;
101         if (style.visibility === "hidden") return false;
102         if (Number(style.opacity || "1") === 0) return false;
103
104         const rect = typeof el.getBoundingClientRect === "function" ? el.getBoundingClientRect() : null;
105         if (rect && rect.width <= 0 && rect.height <= 0) return false;
106
107         return true;
108      } catch {
109         return false;
110      }
111   }
112
113   function vsh25ShouldSkipOverlayNode(el) {
114      if (!el) return true;
115
116      try {
117         if (vsh25ClosestSafe(el, ".vshp")) return true;
118         if (vsh25ClosestSafe(el, ".rating-popup.show")) return true;
119         if (vsh25ClosestSafe(el, "#wellbeingDiaryPopup.show")) return true;
120         if (vsh25ClosestSafe(el, ".modal.show")) return true;
121         if (el.matches && el.matches("[data-vsh25-overlay-ignore='1']")) return true;
122      } catch {}
123
124      return false;
125   }
126
127   function vsh25MarkOverlaySeen(el) {
128      if (!el || !el.dataset) return;
129
130      try {
131         if (!el.dataset.vsh25SeenAt) {
132            el.dataset.vsh25SeenAt = String(Date.now());
133         }
134      } catch {}
135   }
136
137   function vsh25GetOverlayAgeMs(el) {
138      if (!el || !el.dataset) return 0;
139
140      try {
141         const seenAt = Number(el.dataset.vsh25SeenAt || 0) || 0;
142         if (!seenAt) return 0;
143         return Math.max(0, Date.now() - seenAt);
144      } catch {
145         return 0;
146      }
147   }
148
149   function vsh25HideOverlayNode(el) {
150      if (!el) return;
151
152      try {
153         el.style.display = "none";
154         el.style.visibility = "hidden";
155         el.style.opacity = "0";
156         el.style.pointerEvents = "none";
157      } catch {}
158
159      try {
160         if (el.dataset) {
161            el.dataset.vsh25HiddenAt = String(Date.now());
162         }
163      } catch {}
164   }
165
166   function vsh25CollectOverlayNodes() {
167      try {
168         return Array.from(document.querySelectorAll(__VSH25_OVERLAY_QS)).filter((el) => {
169            if (!el) return false;
170            if (vsh25ShouldSkipOverlayNode(el)) return false;
171            if (!vsh25OverlayLooksVisible(el)) return false;
172
173            vsh25MarkOverlaySeen(el);
174            return true;
175         });
176      } catch {
177         return [];
178      }
179   }
180
181   // UI fail-safe: иногда из-за ошибок/Abort/сторонних скриптов "прелоадер" остаётся и даёт белый экран.
182   // Мы НЕ ломаем UX: скрываем только реально зависший overlay, а не любой overlay без разбора.
183   function vsh25ForceHideOverlays(reason, opts = {}) {
184      try {
185         if (!vsh25IsLkContext()) return 0;
186         if (vsh25HasOpenPopup()) return 0;
187
188         const force = !!opts.force;
189         const minAgeMs = Math.max(0, Number(Object.prototype.hasOwnProperty.call(opts, "minAgeMs") ? opts.minAgeMs : __VSH25_OVERLAY_STALE_MS) || 0);
190
191         const nodes = vsh25CollectOverlayNodes();
192         if (!nodes.length) return 0;
193
194         if (!force && __VSH25_ACTIVE_API_REQUESTS > 0) {
195            vsh25DebugOverlay("skip hide: active requests", {
196               reason: reason || "",
197               activeRequests: __VSH25_ACTIVE_API_REQUESTS,
198               overlays: nodes.length,
199            });
200            return 0;
201         }
202
203         let hiddenCount = 0;
204
205         nodes.forEach((el) => {
206            const ageMs = vsh25GetOverlayAgeMs(el);
207
208            if (!force && ageMs < minAgeMs) {
209               return;
210            }
211
212            if (force && minAgeMs > 0 && ageMs > 0 && ageMs < minAgeMs) {
213               return;
214            }
215
216            vsh25HideOverlayNode(el);
217            hiddenCount += 1;
218         });
219
220         if (!hiddenCount) return 0;
221
222         // на всякий снимаем lock, если его держал прелоадер
223         try {
224            document.body && document.body.classList && document.body.classList.remove("lock");
225         } catch {}
226
227         vsh25DebugOverlay("hidden stale overlays", {
228            reason: reason || "",
229            hiddenCount,
230            force,
231            minAgeMs,
232         });
233
234         return hiddenCount;
235      } catch {
236         return 0;
237      }
238   }
239
240   function vsh25ScheduleOverlaySweep(reason, opts = {}) {
241      try {
242         if (__VSH25_OVERLAY_SWEEP_TIMER) {
243            clearTimeout(__VSH25_OVERLAY_SWEEP_TIMER);
244            __VSH25_OVERLAY_SWEEP_TIMER = null;
245         }
246
247         const delay = Math.max(0, Number(opts.delayMs || __VSH25_OVERLAY_SWEEP_DELAY_MS) || 0);
248
249         __VSH25_OVERLAY_SWEEP_TIMER = setTimeout(() => {
250            __VSH25_OVERLAY_SWEEP_TIMER = null;
251            vsh25ForceHideOverlays(reason, opts);
252         }, delay);
253      } catch {}
254   }
255
256   // если прилетела глобальная ошибка/необработанный промис — мягко проверяем stale overlay
257   window.addEventListener(
258      "error",
259      function () {
260         vsh25ScheduleOverlaySweep("window_error", {
261            force: true,
262            minAgeMs: __VSH25_OVERLAY_ERROR_FORCE_MIN_AGE_MS,
263            delayMs: 80,
264         });
265      },
266      { passive: true },
267   );
268
269   window.addEventListener(
270      "unhandledrejection",
271      function () {
272         vsh25ScheduleOverlaySweep("unhandledrejection", {
273            force: true,
274            minAgeMs: __VSH25_OVERLAY_ERROR_FORCE_MIN_AGE_MS,
275            delayMs: 80,
276         });
277      },
278      { passive: true },
279   );
280
281   // watchdog: если overlay живёт слишком долго и запросов уже нет — убираем
282   setTimeout(function () {
283      vsh25ForceHideOverlays("watchdog_10s", {
284         force: false,
285         minAgeMs: __VSH25_OVERLAY_STALE_MS,
286      });
287   }, 10000);
288
289   setTimeout(function () {
290      vsh25ForceHideOverlays("watchdog_18s", {
291         force: true,
292         minAgeMs: 2000,
293      });
294   }, 18000);
295
296   function toggleHidePopup(checked) {
297      const val = checked ? "false" : "true"; // то, что реально кладём в LS
298      localStorage.setItem(LS_KEY_HIDE_BIO_POPUP, val);
299
300      // тот же самый val отправляем в newValue
301      window.dispatchEvent(
302         new StorageEvent("storage", {
303            key: LS_KEY_HIDE_BIO_POPUP,
304            newValue: val,
305         }),
306      );
307   }
308
309   function setBodyLock() {
310      document.body.classList.toggle("lock", !!document.querySelector(POPUPS_OPEN));
311   }
312
313   function debounce(func, wait) {
314      let timeout;
315
316      return function executedFunction(...args) {
317         const later = () => {
318            clearTimeout(timeout);
319            func(...args);
320         };
321
322         clearTimeout(timeout);
323         timeout = setTimeout(later, wait);
324      };
325   }
326
327   // Применяем debounce к setBodyLock — это есть в актуальном файле
328   const debouncedSetBodyLock = debounce(setBodyLock, 100);
329   window.addEventListener("resize", debouncedSetBodyLock);
330
331   function monthFloor(d) {
332      return new Date(d.getFullYear(), d.getMonth(), 1);
333   }
334
335   function isSameDay(d1, d2) {
336      return d1.getDate() === d2.getDate() && d1.getMonth() === d2.getMonth() && d1.getFullYear() === d2.getFullYear();
337   }
338
339   function isValidDateFormat(dateStr) {
340      const regex = /^\d{4}-\d{2}-\d{2}$/;
341      if (!regex.test(dateStr)) return false;
342      const date = new Date(dateStr + "T00:00:00");
343      return date instanceof Date && !isNaN(date);
344   }
345
346   function isDateInRange(dateStr) {
347      const date = new Date(dateStr + "T00:00:00");
348      const diffDays = (today - date) / 864e5;
349      return diffDays <= 14 && date <= today;
350   }
351
352   function getTodayStorageKey() {
353      return wpTodayStr || TODAY_ISO || localIsoDate(today || new Date());
354   }
355
356   function forgetStoredDate() {
357      try {
358         sessionStorage.removeItem(LS_KEY_SELECTED_DAY);
359         sessionStorage.removeItem(LS_KEY_SELECTED_DATE);
360      } catch {}
361   }
362
363   function getStoredDate() {
364      let savedDay = null;
365      let selectedDate = null;
366
367      try {
368         savedDay = sessionStorage.getItem(LS_KEY_SELECTED_DAY);
369         selectedDate = sessionStorage.getItem(LS_KEY_SELECTED_DATE);
370      } catch {
371         return null;
372      }
373
374      if (!savedDay || !selectedDate) return null;
375
376      if (savedDay !== getTodayStorageKey()) {
377         forgetStoredDate();
378         return null;
379      }
380
381      if (!isValidDateFormat(selectedDate) || !isDateInRange(selectedDate)) {
382         forgetStoredDate();
383         return null;
384      }
385
386      return selectedDate;
387   }
388
389   function saveSelectedDate(dateStr) {
390      if (!isValidDateFormat(dateStr) || !isDateInRange(dateStr)) return false;
391
392      try {
393         sessionStorage.setItem(LS_KEY_SELECTED_DATE, dateStr);
394         sessionStorage.setItem(LS_KEY_SELECTED_DAY, getTodayStorageKey());
395         return true;
396      } catch {
397         return false;
398      }
399   }
400
401   function getUrlSelectedDate() {
402      try {
403         const params = new URLSearchParams(window.location.search || "");
404         const selectedDate = String(params.get("date") || params.get("selected_date") || "").trim();
405
406         if (isValidDateFormat(selectedDate) && isDateInRange(selectedDate)) {
407            return selectedDate;
408         }
409      } catch {}
410
411      return "";
412   }
413
414   function getBootstrapSelectedDate(accountBootstrap = {}) {
415      const bootSelectedDate = String(accountBootstrap.selectedDate || "").trim();
416      if (isValidDateFormat(bootSelectedDate) && isDateInRange(bootSelectedDate)) {
417         return bootSelectedDate;
418      }
419
420      const progress = accountBootstrap.progress && typeof accountBootstrap.progress === "object" ? accountBootstrap.progress : null;
421      if (progress) {
422         const progressDate = String(progress.selectedDate || progress.currentDate || "").trim();
423         if (isValidDateFormat(progressDate) && isDateInRange(progressDate)) {
424            return progressDate;
425         }
426      }
427
428      return "";
429   }
430
431   function getInitialSharedSelectedDate(accountBootstrap = {}) {
432      return getUrlSelectedDate() || getStoredDate() || getBootstrapSelectedDate(accountBootstrap) || TODAY_ISO;
433   }
434
435   function applyInitialSharedSelectedDate(accountBootstrap = {}) {
436      const selectedDate = getInitialSharedSelectedDate(accountBootstrap);
437
438      if (!isValidDateFormat(selectedDate) || !isDateInRange(selectedDate)) {
439         return "";
440      }
441
442      accountBootstrap.selectedDate = selectedDate;
443
444      if (accountBootstrap.progress && typeof accountBootstrap.progress === "object") {
445         accountBootstrap.progress.selectedDate = selectedDate;
446         accountBootstrap.progress.currentDate = selectedDate;
447      }
448
449      saveSelectedDate(selectedDate);
450
451      return selectedDate;
452   }
453
454   function saveSelectedDate(dateStr) {
455      if (isValidDateFormat(dateStr) && isDateInRange(dateStr)) {
456         sessionStorage.setItem("vsh25_selected_date", dateStr);
457         sessionStorage.setItem("vsh25_saved_day", wpTodayStr);
458      }
459   }
460
461   function errorText(key) {
462      const messages = {
463         unknownError: ['Не удалось выполнить действие. Попробуйте ещё раз.', 'Could not complete this action. Please try again.'],
464         networkError: ['Не удалось связаться с сервером. Проверьте соединение и попробуйте ещё раз.', 'Could not connect. Check your connection and try again.'],
465         progressBarMissing: ['Не удалось показать прогресс. Обновите страницу.', 'Could not display your progress. Reload the page.'],
466         calendar_not_found: ['Не удалось показать календарь. Обновите страницу.', 'Could not display the calendar. Reload the page.'],
467         dateElementNotFound: ['Не удалось показать выбранный день. Обновите страницу.', 'Could not display the selected day. Reload the page.'],
468         dateFormatError: ['Не удалось показать выбранный день. Попробуйте выбрать его ещё раз.', 'Could not display the selected day. Please select it again.'],
469         progressUpdateError: ['Не удалось сохранить прогресс. Проверьте соединение и попробуйте ещё раз.', 'Could not save your progress. Check your connection and try again.'],
470         progressLoadError: ['Не удалось загрузить прогресс. Попробуйте ещё раз.', 'Could not load your progress. Please try again.'],
471         progressNetworkError: ['Не удалось загрузить прогресс. Проверьте соединение и попробуйте ещё раз.', 'Could not load your progress. Check your connection and try again.'],
472         wellbeingHistoryError: ['Не удалось загрузить дневник. Попробуйте ещё раз.', 'Could not load your diary. Please try again.'],
473         ratingError: ['Не удалось сохранить оценку. Попробуйте ещё раз.', 'Could not save your rating. Please try again.'],
474         ratingSaved: ['Оценка сохранена.', 'Rating saved.'],
475         bioprogramVideoNetworkError: ['Не удалось загрузить биопрограмму. Проверьте соединение и попробуйте ещё раз.', 'Could not load the biological program. Check your connection and try again.'],
476         tabletVideoNetworkError: ['Не удалось загрузить цифровую таблетку. Проверьте соединение и попробуйте ещё раз.', 'Could not load the digital tablet. Check your connection and try again.'],
477         noVideoMsg: ['Видео для выбранной даты пока недоступно. Выберите другой день.', 'The video for this date is not available yet. Choose another day.'],
478      };
479      const message = Object.prototype.hasOwnProperty.call(messages, key) ? messages[key] : messages.unknownError;
480      return message[/^en(?:[-_]|$)/i.test(document.documentElement?.lang || '') ? 1 : 0];
481   }
482
483   function createNotificationsContainer() {
484      const container = document.createElement("div");
485      container.id = "notifications-container";
486      container.style.cssText =
487         "position: fixed; bottom: calc(20px + env(safe-area-inset-bottom)); right: 16px; max-width: calc(100vw - 32px); z-index: 9999; display: flex; flex-direction: column; align-items: flex-end; gap: 10px;";
488      document.body.appendChild(container);
489      return container;
490   }
491
492   function showNotification(message, type = "info") {
493      const notificationsContainer = document.getElementById("notifications-container") || createNotificationsContainer();
494      if (!notificationsContainer) return;
495
496      const notification = document.createElement("div");
497      notification.className = `notification vsh25-customer-notice ${type === "error" ? "error" : ""}`;
498      notification.setAttribute("role", type === "error" ? "alert" : "status");
499      const messageText = document.createElement("span");
500      messageText.textContent = typeof message === "string" ? message : errorText("unknownError");
501      const closeButton = document.createElement("button");
502      closeButton.className = "close-btn";
503      closeButton.type = "button";
504      closeButton.setAttribute("aria-label", /^en(?:[-_]|$)/i.test(document.documentElement?.lang || '') ? "Close notification" : "Закрыть уведомление");
505      notification.appendChild(messageText);
506      notification.appendChild(closeButton);
507
508      if (notificationsContainer.firstChild) {
509         notificationsContainer.insertBefore(notification, notificationsContainer.firstChild);
510      } else {
511         notificationsContainer.appendChild(notification);
512      }
513
514      closeButton.addEventListener("click", () => notification.remove());
515
516      setTimeout(() => notification.remove(), 10000);
517   }
518
519   function apiPost(action, params = {}, opts = {}) {
520      const { showLoader = true, timeoutMs = 12000, retries = 3, retryBaseDelayMs = 450, retryStatuses = [429, 500, 502, 503, 504], abortKey = null } = opts || {};
521
522      const ajaxUrl = ajax_object && ajax_object.ajaxurl ? ajax_object.ajaxurl : "";
523      if (!ajaxUrl) return Promise.reject(new Error("ajaxurl_missing"));
524
525      const body = new URLSearchParams({ action, ...params });
526      const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
527      const ms = Math.max(1000, Number(timeoutMs) || 0);
528
529      // One controller per request (keeps retries tied to a single cancellation/timeout).
530      const ctrl = typeof AbortController === "function" ? new AbortController() : null;
531
532      // Abort previous request with same key (latest-wins).
533      if (abortKey && ctrl) {
534         const prev = __VSH25_AJAX_ABORT.get(abortKey);
535         if (prev && prev.ctrl && !prev.ctrl.signal.aborted) {
536            try {
537               prev.ctrl.abort();
538            } catch {}
539         }
540         __VSH25_AJAX_ABORT.set(abortKey, { ctrl, at: Date.now() });
541      }
542
543      const t = ctrl
544         ? setTimeout(() => {
545              try {
546                 ctrl.abort();
547              } catch {}
548           }, ms)
549         : null;
550
551      const fetchAttempt = async (attempt) => {
552         try {
553            const res = await fetch(ajaxUrl, {
554               method: "POST",
555               credentials: "same-origin",
556               headers: { "Content-Type": "application/x-www-form-urlencoded" },
557               body,
558               signal: ctrl ? ctrl.signal : undefined,
559            });
560
561            if (!res.ok && retryStatuses.includes(res.status) && attempt < retries) {
562               const backoff = retryBaseDelayMs * Math.pow(2, attempt);
563               const jitter = Math.floor(Math.random() * 180);
564               await sleep(backoff + jitter);
565               return fetchAttempt(attempt + 1);
566            }
567
568            return res;
569         } catch (e) {
570            // If aborted (timeout or superseded), surface a stable error code for callers to ignore.
571            if (ctrl && ctrl.signal && ctrl.signal.aborted) {
572               const err = new Error("aborted");
573               err.code = "aborted";
574               throw err;
575            }
576
577            if (attempt < retries) {
578               const backoff = retryBaseDelayMs * Math.pow(2, attempt);
579               const jitter = Math.floor(Math.random() * 180);
580               await sleep(backoff + jitter);
581               return fetchAttempt(attempt + 1);
582            }
583
584            throw e;
585         }
586      };
587
588      if (showLoader) {
589         __VSH25_ACTIVE_API_REQUESTS += 1;
590
591         if (typeof showPreloader === "function") {
592            try {
593               showPreloader(true);
594            } catch (e) {
595               vsh25ScheduleOverlaySweep("showPreloader_true_throw", {
596                  force: true,
597                  minAgeMs: __VSH25_OVERLAY_ERROR_FORCE_MIN_AGE_MS,
598                  delayMs: 80,
599               });
600            }
601         }
602      }
603
604      return fetchAttempt(0).finally(() => {
605         if (t) clearTimeout(t);
606
607         if (abortKey && ctrl) {
608            const cur = __VSH25_AJAX_ABORT.get(abortKey);
609            if (cur && cur.ctrl === ctrl) __VSH25_AJAX_ABORT.delete(abortKey);
610         }
611
612         if (showLoader) {
613            __VSH25_ACTIVE_API_REQUESTS = Math.max(0, __VSH25_ACTIVE_API_REQUESTS - 1);
614
615            if (typeof showPreloader === "function") {
616               try {
617                  showPreloader(false);
618               } catch (e) {
619                  vsh25ScheduleOverlaySweep("showPreloader_false_throw", {
620                     force: true,
621                     minAgeMs: __VSH25_OVERLAY_ERROR_FORCE_MIN_AGE_MS,
622                     delayMs: 80,
623                  });
624               }
625            }
626
627            vsh25ScheduleOverlaySweep("apiPost_finally", {
628               force: false,
629               minAgeMs: 1200,
630               delayMs: __VSH25_OVERLAY_SWEEP_DELAY_MS,
631            });
632         }
633      });
634   }
635
636   function abortApiRequest(abortKey) {
637      const key = String(abortKey || "");
638      if (!key || !__VSH25_AJAX_ABORT.has(key)) return false;
639
640      const entry = __VSH25_AJAX_ABORT.get(key);
641      let aborted = false;
642
643      if (entry && entry.ctrl && entry.ctrl.signal && !entry.ctrl.signal.aborted) {
644         try {
645            entry.ctrl.abort();
646            aborted = true;
647         } catch {}
648      }
649
650      __VSH25_AJAX_ABORT.delete(key);
651      return aborted;
652   }
653
654   function abortApiRequestsByPrefix(prefix) {
655      const normalizedPrefix = String(prefix || "");
656      if (!normalizedPrefix) return 0;
657
658      let abortedCount = 0;
659      Array.from(__VSH25_AJAX_ABORT.keys()).forEach((key) => {
660         if (!String(key).startsWith(normalizedPrefix)) return;
661         if (abortApiRequest(key)) abortedCount += 1;
662      });
663
664      return abortedCount;
665   }
666
667   A.keys.LS_KEY_HIDE_BIO_POPUP = LS_KEY_HIDE_BIO_POPUP;
668   A.keys.LS_KEY_SIDEBAR_COLLAPSED = LS_KEY_SIDEBAR_COLLAPSED;
669   A.keys.LS_KEY_SELECTED_DATE = LS_KEY_SELECTED_DATE;
670   A.keys.LS_KEY_SELECTED_DAY = LS_KEY_SELECTED_DAY;
671
672   A.selectors.POPUPS_OPEN = POPUPS_OPEN;
673   A.selectors.OVERLAY_QS = __VSH25_OVERLAY_QS;
674
675   A.flags.allowFreeSeek = window.vsh25AllowFreeSeek;
676   A.flags.hasCalendar = !!document.querySelector(".lk-calendar");
677
678   A.dom.currentMonthYear = currentMonthYear;
679
680   A.state.wpTodayStr = wpTodayStr;
681   A.state.today = today;
682   A.state.TODAY_ISO = TODAY_ISO;
683
684   const accountBootstrap = window.VSH25_ACCOUNT_BOOTSTRAP && typeof window.VSH25_ACCOUNT_BOOTSTRAP === "object" ? window.VSH25_ACCOUNT_BOOTSTRAP : {};
685
686   A.state.accountBootstrap = accountBootstrap;
687   const initialSharedSelectedDate = applyInitialSharedSelectedDate(accountBootstrap);
688
689   if (!A.state.watchHistory || typeof A.state.watchHistory !== "object") {
690      A.state.watchHistory = {};
691   }
692
693   if (!A.state.wellbeingHistory || typeof A.state.wellbeingHistory !== "object") {
694      A.state.wellbeingHistory = {};
695   }
696
697   if (!A.state.newVideoMap || typeof A.state.newVideoMap !== "object") {
698      A.state.newVideoMap = {};
699   }
700
701   if (accountBootstrap.progress && typeof accountBootstrap.progress === "object") {
702      if (accountBootstrap.progress.watchHistory && typeof accountBootstrap.progress.watchHistory === "object") {
703         A.state.watchHistory = accountBootstrap.progress.watchHistory;
704      }
705
706      if (accountBootstrap.progress.newVideoMap && typeof accountBootstrap.progress.newVideoMap === "object") {
707         A.state.newVideoMap = accountBootstrap.progress.newVideoMap;
708      }
709
710      if (accountBootstrap.progress.currentDate && typeof accountBootstrap.progress.currentDate === "string") {
711         A.state.currentDate = new Date(accountBootstrap.progress.currentDate + "T00:00:00");
712      }
713   }
714
715   if (accountBootstrap.wellbeingHistory && typeof accountBootstrap.wellbeingHistory === "object") {
716      A.state.wellbeingHistory = accountBootstrap.wellbeingHistory;
717      A.state.wellbeingHistoryBootstrapped = true;
718   }
719
720   if (initialSharedSelectedDate) {
721      const sharedDate = new Date(initialSharedSelectedDate + "T00:00:00");
722
723      if (!Number.isNaN(sharedDate.getTime())) {
724         A.state.currentDate = new Date(sharedDate);
725         A.state.wellbeingCurrentDate = new Date(sharedDate);
726      }
727   } else if (accountBootstrap.selectedDate && typeof accountBootstrap.selectedDate === "string") {
728      const bootstrappedDate = new Date(accountBootstrap.selectedDate + "T00:00:00");
729
730      if (!Number.isNaN(bootstrappedDate.getTime())) {
731         A.state.currentDate = new Date(bootstrappedDate);
732         A.state.wellbeingCurrentDate = new Date(bootstrappedDate);
733      }
734   }
735
736   if (!(A.state.shownNewVideoNotices instanceof Set)) {
737      A.state.shownNewVideoNotices = new Set();
738   }
739
740   if (typeof A.state.bioprogramNotified !== "boolean") {
741      A.state.bioprogramNotified = false;
742   }
743
744   if (typeof A.state.bioprogramWatched !== "boolean") {
745      A.state.bioprogramWatched = false;
746   }
747
748   if (typeof A.state.tabletWatched !== "boolean") {
749      A.state.tabletWatched = false;
750   }
751
752   if (typeof A.state.initialVideosLoaded !== "boolean") {
753      A.state.initialVideosLoaded = false;
754   }
755
756   if (!("currentDate" in A.state)) {
757      A.state.currentDate = null;
758   }
759
760   if (!("wellbeingCurrentDate" in A.state)) {
761      A.state.wellbeingCurrentDate = null;
762   }
763
764   if (!("wellbeingInFlight" in A.state)) {
765      A.state.wellbeingInFlight = null;
766   }
767
768   A.utils.localIsoDate = localIsoDate;
769   A.utils.monthFloor = monthFloor;
770   A.utils.isSameDay = isSameDay;
771   A.utils.isLocked = isLocked;
772   A.utils.isValidDateFormat = isValidDateFormat;
773   A.utils.isDateInRange = isDateInRange;
774   A.utils.getStoredDate = getStoredDate;
775   A.utils.saveSelectedDate = saveSelectedDate;
776   A.utils.getUrlSelectedDate = getUrlSelectedDate;
777   A.utils.debounce = debounce;
778
779   A.ui.forceHideOverlays = vsh25ForceHideOverlays;
780   A.ui.scheduleOverlaySweep = vsh25ScheduleOverlaySweep;
781   A.ui.setBodyLock = setBodyLock;
782   A.ui.debouncedSetBodyLock = debouncedSetBodyLock;
783   A.ui.toggleHidePopup = toggleHidePopup;
784   A.ui.showNotification = showNotification;
785   A.ui.errorText = errorText;
786   A.ui.createNotificationsContainer = createNotificationsContainer;
787
788   A.api.post = apiPost;
789   A.api.apiPost = apiPost;
790   A.api.abortRequest = abortApiRequest;
791   A.api.abortRequestsByPrefix = abortApiRequestsByPrefix;
792
793   window.toggleHidePopup = toggleHidePopup;
794   window.showNotification = showNotification;
795});

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.