/** * Переключение светлой/тёмной темы: data-theme на , ключ localStorage. * Начальная тема задаётся инлайн-скриптом в base.html (без мигания). * * Автор: Сергей Антропов * Сайт: https://devops.org.ru */ (function () { "use strict"; var STORAGE_KEY = "kind_k8s_theme"; function getTheme() { var t = document.documentElement.getAttribute("data-theme"); return t === "light" ? "light" : "dark"; } function setTheme(theme) { var next = theme === "light" ? "light" : "dark"; document.documentElement.setAttribute("data-theme", next); try { localStorage.setItem(STORAGE_KEY, next); } catch (e) { /* приватный режим и т.п. */ } syncToggleButton(); } function syncToggleButton() { var btn = document.getElementById("theme-toggle"); if (!btn) return; var dark = getTheme() === "dark"; btn.setAttribute("aria-pressed", dark ? "true" : "false"); if (dark) { btn.setAttribute("aria-label", "Включить светлую тему"); btn.setAttribute("title", "Светлая тема"); } else { btn.setAttribute("aria-label", "Включить тёмную тему"); btn.setAttribute("title", "Тёмная тема"); } } function toggleTheme() { setTheme(getTheme() === "dark" ? "light" : "dark"); } /** * Нормализует pathname: убирает хвостовые слэши (кроме корня «/»). * Нужен для сопоставления с маршрутами FastAPI без зависимости от редиректов. */ function normalizePathname(path) { var p = path || "/"; if (p.length > 1 && p.charAt(p.length - 1) === "/") { p = p.replace(/\/+$/, ""); } return p || "/"; } /** * Какой пункт шапки (href пилюли) должен быть активен по текущему URL. * Дублирует логику nav_active из шаблонов: страницы кластера и /cluster/…/edit * относятся к разделу «Кластеры», чтобы подсветка не зависела только от SSR * (кэш образа, устаревший контейнер и т.п.). */ function desiredNavHrefFromPath() { var path = normalizePathname(window.location.pathname); if (path === "/") return "/"; if (path === "/clusters") return "/clusters"; if (/^\/cluster\/[^/]+\/edit$/.test(path)) return "/clusters"; if (/^\/cluster\/[^/]+$/.test(path)) return "/clusters"; if (path.indexOf("/cluster-addons") === 0) return "/cluster-addons"; if (path.indexOf("/journal") === 0) return "/journal"; if (path.indexOf("/documentation") === 0) return "/documentation"; if (path.indexOf("/cluster-create") === 0) return "/clusters"; return null; } /** Снимает активность со всех пилюль в прокручиваемой полосе и включает одну по href. */ function syncNavPillActive() { var want = desiredNavHrefFromPath(); if (!want) return; var scroll = document.querySelector(".nav-links-scroll"); if (!scroll) return; var pills = scroll.querySelectorAll("a.nav-link.nav-pill[href]"); pills.forEach(function (a) { a.classList.remove("nav-pill--active"); }); pills.forEach(function (a) { if (a.getAttribute("href") === want) { a.classList.add("nav-pill--active"); } }); } function init() { var btn = document.getElementById("theme-toggle"); if (btn) btn.addEventListener("click", toggleTheme); syncToggleButton(); syncNavPillActive(); } /* defer: скрипт в выполняется после разбора документа — кнопка уже есть. */ if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init); } else { init(); } })();