diff --git a/srv/tesm/VERSION b/srv/tesm/VERSION index 512a1fa..5ed5faa 100644 --- a/srv/tesm/VERSION +++ b/srv/tesm/VERSION @@ -1 +1 @@ -1.1.9 +1.1.10 diff --git a/srv/tesm/static/css/style.css b/srv/tesm/static/css/style.css index 3a2cbe3..9cb8f76 100644 --- a/srv/tesm/static/css/style.css +++ b/srv/tesm/static/css/style.css @@ -968,6 +968,48 @@ select { flex-direction: column; animation: modal-in 0.16s ease; } + +/* ---- Hinweis-Icons (Hover-Tooltip + Klick-Modal), siehe _hint_icon.html + und initHintIcons() in app.js ---- */ +.hint-icon { + display: inline-flex; + align-items: center; + justify-content: center; + width: 15px; + height: 15px; + flex: 0 0 auto; + border-radius: 50%; + border: 1.5px solid var(--text-faint); + background: transparent; + color: var(--text-faint); + font-size: 10px; + font-weight: 700; + font-style: italic; + font-family: Georgia, "Times New Roman", serif; + line-height: 1; + cursor: help; + vertical-align: middle; + margin-left: 6px; + padding: 0; + transition: border-color 0.15s, color 0.15s; +} +.hint-icon:hover, .hint-icon:focus-visible { border-color: var(--accent); color: var(--accent); } + +#hint-tooltip { + position: fixed; + z-index: 10000; + max-width: 280px; + padding: 8px 12px; + border-radius: 8px; + font-size: 12px; + line-height: 1.5; + background: var(--bg-elevated); + color: var(--text); + border: 1px solid var(--border); + box-shadow: var(--shadow); + pointer-events: none; + display: none; +} @keyframes modal-in { from { opacity: 0; transform: translateY(8px) scale(0.98); } to { opacity: 1; transform: translateY(0) scale(1); } diff --git a/srv/tesm/static/js/app.js b/srv/tesm/static/js/app.js index 1555e80..b10e85f 100644 --- a/srv/tesm/static/js/app.js +++ b/srv/tesm/static/js/app.js @@ -285,6 +285,102 @@ .catch(() => { content.textContent = "Fehler beim Laden des Logs."; }); }; + /* ---------------- Hinweis-Icons (Hover-Tooltip + Klick-Modal) ---------------- */ + /* Zentraler Mechanismus für ALLE "i"-Icons (siehe _hint_icon.html) app-weit + -- ein einziges delegiertes Set von Listenern statt pro Icon eigener + Handler, damit auch nachträglich per JS eingefügte Icons (z.B. in + dynamisch nachgeladenen Tabellenzeilen) ohne weiteres Zutun funktionieren. + Hover zeigt den Text als Tooltip neben dem Mauszeiger -- aber NUR auf + Geräten, die tatsächlich sinnvoll hovern können (matchMedia-Check bei + JEDEM mouseenter neu ausgewertet, nicht einmalig beim Laden gecacht, + damit z.B. ein Convertible/Tablet beim Umschalten Maus/Touch korrekt + reagiert). Auf reinen Touch-Geräten bleibt so nur Tap -> Modal übrig, + da dort ohnehin kein echtes mouseenter/mousemove-Hover stattfindet. */ + function ensureHintTooltip() { + if (document.getElementById("hint-tooltip")) return; + const el = document.createElement("div"); + el.id = "hint-tooltip"; + document.body.appendChild(el); + } + + function ensureHintModal() { + if (document.getElementById("hint-modal")) return; + const html = ` +
`; + document.body.insertAdjacentHTML("beforeend", html); + document.querySelectorAll("#hint-modal [data-close-modal]").forEach((btn) => { + btn.addEventListener("click", () => closeModal(btn)); + }); + document.getElementById("hint-modal").addEventListener("click", (e) => { + if (e.target.id === "hint-modal") closeModal(e.target); + }); + } + + function canHover() { + return window.matchMedia && window.matchMedia("(hover: hover) and (pointer: fine)").matches; + } + + function initHintIcons() { + if (!document.querySelector(".hint-icon")) return; + ensureHintTooltip(); + const tooltip = document.getElementById("hint-tooltip"); + + function positionTooltip(e) { + const offset = 14; + const rect = tooltip.getBoundingClientRect(); + let x = e.clientX + offset; + let y = e.clientY + offset; + if (x + rect.width > window.innerWidth - 8) x = e.clientX - rect.width - offset; + if (y + rect.height > window.innerHeight - 8) y = e.clientY - rect.height - offset; + tooltip.style.left = Math.max(8, x) + "px"; + tooltip.style.top = Math.max(8, y) + "px"; + } + + // mouseenter/mouseleave bubbeln nicht -- Delegation via Capture-Phase + // auf document funktioniert dafür trotzdem zuverlässig. + document.addEventListener("mouseenter", function (e) { + const icon = e.target.closest && e.target.closest(".hint-icon"); + if (!icon || !canHover()) return; + tooltip.textContent = icon.dataset.hint || ""; + tooltip.style.display = "block"; + positionTooltip(e); + }, true); + + document.addEventListener("mouseleave", function (e) { + const icon = e.target.closest && e.target.closest(".hint-icon"); + if (!icon) return; + tooltip.style.display = "none"; + }, true); + + document.addEventListener("mousemove", function (e) { + if (tooltip.style.display === "block") positionTooltip(e); + }); + + document.addEventListener("click", function (e) { + const icon = e.target.closest && e.target.closest(".hint-icon"); + if (!icon) return; + e.preventDefault(); + tooltip.style.display = "none"; + ensureHintModal(); + document.getElementById("hint-modal-title").textContent = icon.dataset.hintTitle || "Hinweis"; + document.getElementById("hint-modal-body").textContent = icon.dataset.hint || ""; + openModal("hint-modal"); + }); + } + /* ---------------- Warnung bei ungespeicherten Änderungen ---------------- */ /* Erkennt generisch auf JEDER Seite, ob ein Formular mit echten Eingabefeldern (nicht nur versteckten Aktions-Feldern wie bei Löschen/ @@ -628,6 +724,7 @@ initLeaseCountdowns(); initUnsavedChangesGuard(); initSortableTables(); + initHintIcons(); document.querySelectorAll("[data-theme-toggle]").forEach((btn) => btn.addEventListener("click", toggleTheme)); }); diff --git a/srv/tesm/templates/_hint_icon.html b/srv/tesm/templates/_hint_icon.html new file mode 100644 index 0000000..b776835 --- /dev/null +++ b/srv/tesm/templates/_hint_icon.html @@ -0,0 +1,9 @@ +{# + Gemeinsames "i"-Hinweis-Icon: Hover zeigt den Text als Tooltip neben dem + Mauszeiger, Klick/Tap öffnet dasselbe (wiederverwendete) Modal mit dem + vollen Text -- Mechanik lebt zentral in app.js (initHintIcons), hier wird + nur das Icon mit seinen Daten-Attributen erzeugt. title = Kontext für die + Modal-Überschrift (z.B. das zugehörige Feld-Label), text = der eigentliche + Erklärungstext. +#} +{% macro hint_icon(text, title='Hinweis') %}{% endmacro %} diff --git a/srv/tesm/templates/credentials.html b/srv/tesm/templates/credentials.html index 20e86a3..ebe4c8d 100644 --- a/srv/tesm/templates/credentials.html +++ b/srv/tesm/templates/credentials.html @@ -8,6 +8,7 @@ {% block page_sub %}{% endblock %} {% block content %} +{% import "_hint_icon.html" as hi %}