309 lines
8.9 KiB
JavaScript
309 lines
8.9 KiB
JavaScript
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ý <details> 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 = '<p class="muted">Načítám obsah</p>';
|
|
|
|
fetch(url, { headers: { "X-Requested-With": "fetch" } })
|
|
.then((response) => {
|
|
if (!response.ok) {
|
|
throw new Error("HTTP " + response.status);
|
|
}
|
|
return response.text();
|
|
})
|
|
.then((text) => {
|
|
// Prázdná odpověď by panel jen vyprázdnila a vypadalo by to jako zaseknuté načítání.
|
|
if (!text.trim()) {
|
|
throw new Error("server vrátil prázdnou odpověď");
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
});
|
|
});
|