function togglePortalNav() { const wrap = document.getElementById("portal-nav"); if (wrap) { wrap.classList.toggle("open"); } return false; } function writeClipboard(value) { if (navigator.clipboard && window.isSecureContext) { return navigator.clipboard.writeText(value); } const textarea = document.createElement("textarea"); textarea.value = value; textarea.setAttribute("readonly", ""); textarea.style.position = "fixed"; textarea.style.top = "-1000px"; document.body.appendChild(textarea); textarea.select(); document.execCommand("copy"); document.body.removeChild(textarea); return Promise.resolve(); } function nearestCopyValue(button) { if (button.dataset.copyValue) { return button.dataset.copyValue; } const scopes = [ button.closest(".copy-row"), button.closest(".cmd-row"), button.closest("td"), button.parentElement, ]; for (const scope of scopes) { if (!scope) { continue; } const target = scope.querySelector("input, textarea, pre, code"); if (target) { return "value" in target ? target.value : target.textContent; } } return ""; } function showCopied(button, ok) { const label = button.textContent; button.textContent = ok ? "Zkopírováno" : "Nelze kopírovat"; window.setTimeout(() => { button.textContent = label; }, 1200); } function copyValue(idOrButton) { const target = typeof idOrButton === "string" ? document.getElementById(idOrButton) : idOrButton; if (!target) { return false; } const value = "value" in target ? target.value : nearestCopyValue(target); if ("select" in target) { target.select(); target.setSelectionRange(0, target.value.length); } writeClipboard(value) .then(() => { if (!("value" in target)) { showCopied(target, true); } }) .catch(() => { if (!("value" in target)) { showCopied(target, false); } }); return false; } function copyText(button) { const value = nearestCopyValue(button); writeClipboard(value) .then(() => showCopied(button, true)) .catch(() => showCopied(button, false)); return false; } function openResourceModal(id) { const modal = document.getElementById(id); if (!modal) { return false; } if (typeof modal.showModal === "function") { modal.showModal(); } else { modal.setAttribute("open", ""); } return false; } function closeResourceModal(id) { const modal = document.getElementById(id); if (!modal) { return false; } if (typeof modal.close === "function") { modal.close(); } else { modal.removeAttribute("open"); } return false; } document.addEventListener("click", (event) => { const modal = event.target; if (typeof HTMLDialogElement !== "undefined" && modal instanceof HTMLDialogElement && modal.classList.contains("resource-modal")) { modal.close(); } }); // Sbalitelné sekce detailu služby: odkaz s kotvou (#metadata) musí cílovou sekci i otevřít, // jinak by prohlížeč skočil na zavřený
a uživatel by neviděl žádný obsah. function openHashSection() { let hash = window.location.hash.slice(1); if (!hash) { return; } try { hash = decodeURIComponent(hash); } catch (error) { return; } const target = document.getElementById(hash); if (!target) { return; } let node = target; while (node) { if (node.tagName === "DETAILS") { node.open = true; } node = node.parentElement; } target.scrollIntoView(); } window.addEventListener("hashchange", openHashSection); document.addEventListener("DOMContentLoaded", openHashSection); // Obsah načítaný až při rozbalení (seznam .md souborů a jejich náhledy). Načte se jen jednou; // při chybě se příznak vrátí, aby šlo zkusit znovu dalším rozbalením. function loadLazyPanel(panel) { if (panel.dataset.lazyLoaded === "1") { return; } panel.dataset.lazyLoaded = "1"; const url = panel.dataset.lazySrc; panel.innerHTML = '

Načítám obsah

'; fetch(url, { headers: { "X-Requested-With": "fetch" } }) .then((response) => { if (!response.ok) { throw new Error("HTTP " + response.status); } return response.text(); }) .then((text) => { panel.innerHTML = text; }) .catch((error) => { panel.dataset.lazyLoaded = ""; const message = document.createElement("p"); message.className = "alert alert-danger"; message.textContent = "Obsah se nepodařilo načíst: " + (error.message || error); panel.replaceChildren(message); }); } // Událost toggle nebublá, proto ji odchytáváme v capture fázi na dokumentu. document.addEventListener( "toggle", (event) => { const details = event.target; if (typeof HTMLDetailsElement === "undefined" || !(details instanceof HTMLDetailsElement) || !details.open) { return; } // Jen panely patřící přímo této sekci - vnořené soubory se načtou až po svém rozbalení. for (const panel of details.querySelectorAll("[data-lazy-src]")) { if (panel.closest("details") === details) { loadLazyPanel(panel); } } }, true ); function setFormDisabled(form, disabled) { for (const field of form.querySelectorAll("input, select, textarea, button")) { field.disabled = disabled; } } function extractMainHtml(responseText) { const parser = new DOMParser(); const doc = parser.parseFromString(responseText, "text/html"); const main = doc.querySelector("main"); return main ? main.innerHTML : ""; } document.addEventListener("DOMContentLoaded", () => { const form = document.querySelector('[data-ajax-form="new-app"]'); if (!form) { return; } const loader = document.getElementById("new-app-loader"); const status = document.getElementById("new-app-status"); const result = document.getElementById("new-app-result"); // ID služby musí být vždy lowercase (Docker image/container, Gitea repo, URL). // Přepisujeme rovnou při psaní, ať uživatel vidí skutečnou hodnotu, která se odešle. const appIdInput = form.querySelector('input[name="app_id"]'); if (appIdInput) { appIdInput.addEventListener("input", () => { const lowered = appIdInput.value.toLowerCase(); if (appIdInput.value !== lowered) { const cursor = appIdInput.selectionStart; appIdInput.value = lowered; appIdInput.setSelectionRange(cursor, cursor); } }); } form.addEventListener("submit", async (event) => { event.preventDefault(); const formData = new FormData(form); if (status) { status.className = "async-status"; status.textContent = "Vytv\u00e1\u0159\u00edm slu\u017ebu, neobnovujte str\u00e1nku."; } if (result) { result.innerHTML = ""; } if (loader) { loader.hidden = false; } setFormDisabled(form, true); try { const response = await fetch(form.action, { method: "POST", body: formData, headers: { "X-Requested-With": "fetch", }, }); const responseText = await response.text(); const mainHtml = extractMainHtml(responseText); if (mainHtml) { const main = document.querySelector("main"); if (main) { main.innerHTML = mainHtml; } return; } if (!response.ok) { throw new Error(responseText || "Vytvo\u0159en\u00ed slu\u017eby selhalo."); } if (result) { result.innerHTML = responseText; } if (status) { status.textContent = ""; } } catch (error) { if (status) { status.className = "async-status async-status-error"; status.textContent = error.message || "Vytvo\u0159en\u00ed slu\u017eby selhalo."; } setFormDisabled(form, false); } finally { if (loader) { loader.hidden = true; } } }); });