diff --git a/wwwroot/css/tooltip.css b/wwwroot/css/tooltip.css new file mode 100644 index 0000000..af591aa --- /dev/null +++ b/wwwroot/css/tooltip.css @@ -0,0 +1,20 @@ +.tooltip { + position: fixed; + z-index: 10000; + max-width: 320px; + padding: .4em .7em; + background: var(--f1-bg); + color: var(--text); + border: var(--border-width) solid var(--f3-border); + border-radius: var(--border-radius); + font-size: .85em; + line-height: 1.4; + pointer-events: none; + opacity: 0; + transition: opacity 120ms; + white-space: pre-line; +} + +.tooltip.visible { + opacity: 1; +} diff --git a/wwwroot/index.html b/wwwroot/index.html index 54b5ff9..ba64dd5 100644 --- a/wwwroot/index.html +++ b/wwwroot/index.html @@ -19,6 +19,7 @@ +
@@ -214,6 +215,7 @@ + diff --git a/wwwroot/js/pages/dashboard.js b/wwwroot/js/pages/dashboard.js index 25f6c9a..e4b8178 100644 --- a/wwwroot/js/pages/dashboard.js +++ b/wwwroot/js/pages/dashboard.js @@ -51,27 +51,25 @@ registerPage('dashboard', { if (!metaEl) return; const parts = []; - const tooltipParts = []; if (addCount > 0) { - parts.push(`+${addCount}`); - tooltipParts.push(`${addCount} ${addCount === 1 ? 'Datei' : 'Dateien'} hinzugefügt`); + const tip = `${addCount} ${addCount === 1 ? 'Datei' : 'Dateien'} hinzugefügt`; + parts.push(`+${addCount}`); } if (editCount > 0) { - parts.push(`~${editCount}`); - tooltipParts.push(`${editCount} ${editCount === 1 ? 'Datei' : 'Dateien'} geändert`); + const tip = `${editCount} ${editCount === 1 ? 'Datei' : 'Dateien'} geändert`; + parts.push(`~${editCount}`); } if (deleteCount > 0) { - parts.push(`-${deleteCount}`); - tooltipParts.push(`${deleteCount} ${deleteCount === 1 ? 'Datei' : 'Dateien'} gelöscht`); + const tip = `${deleteCount} ${deleteCount === 1 ? 'Datei' : 'Dateien'} gelöscht`; + parts.push(`-${deleteCount}`); } if (outgoing > 0) { - parts.push(` ${outgoing}`); - tooltipParts.push(`${outgoing} ${outgoing === 1 ? 'Commit muss' : 'Commits müssen'} noch gepusht werden`); + const tip = `${outgoing} ${outgoing === 1 ? 'Commit muss' : 'Commits müssen'} noch gepusht werden`; + parts.push(` ${outgoing}`); } const gitEl = document.createElement('span'); gitEl.className = 'card-meta-item card-git-status'; - gitEl.title = tooltipParts.join('\n'); gitEl.innerHTML = ` ${parts.join(' ')}`; metaEl.appendChild(gitEl); }); diff --git a/wwwroot/js/tooltip.js b/wwwroot/js/tooltip.js new file mode 100644 index 0000000..8dbf75f --- /dev/null +++ b/wwwroot/js/tooltip.js @@ -0,0 +1,123 @@ +// ── Custom Tooltip ── +// Ersetzt native browser-tooltips für alle [title]-Elemente + +(function () { + const DELAY = 200; + + let tooltipEl = null; + let timeout = null; + let currentTarget = null; + + function createTooltipEl() { + const el = document.createElement('div'); + el.className = 'tooltip'; + document.body.appendChild(el); + return el; + } + + function show(target) { + const text = target.getAttribute('data-tooltip'); + if (!text) return; + + if (!tooltipEl) tooltipEl = createTooltipEl(); + + tooltipEl.textContent = ''; + const lines = text.split('\n'); + for (let i = 0; i < lines.length; i++) { + if (i > 0) tooltipEl.appendChild(document.createElement('br')); + tooltipEl.appendChild(document.createTextNode(lines[i])); + } + + tooltipEl.classList.add('visible'); + position(target); + currentTarget = target; + } + + function position(target) { + if (!tooltipEl) return; + + const rect = target.getBoundingClientRect(); + const gap = 8; + + // Reset für Messung + tooltipEl.style.left = '0'; + tooltipEl.style.top = '0'; + + const tw = tooltipEl.offsetWidth; + const th = tooltipEl.offsetHeight; + + // Versuche oben zu platzieren + let top = rect.top - th - gap; + let left = rect.left + rect.width / 2 - tw / 2; + + // Wenn oben kein Platz → unten + if (top < gap) { + top = rect.bottom + gap; + } + + // Horizontal begrenzen + if (left < gap) left = gap; + if (left + tw > window.innerWidth - gap) left = window.innerWidth - gap - tw; + + tooltipEl.style.left = left + 'px'; + tooltipEl.style.top = top + 'px'; + } + + function hide() { + clearTimeout(timeout); + timeout = null; + if (tooltipEl) tooltipEl.classList.remove('visible'); + if (currentTarget) { + // title zurücksetzen + const text = currentTarget.getAttribute('data-tooltip'); + if (text) { + currentTarget.setAttribute('title', text); + currentTarget.removeAttribute('data-tooltip'); + } + currentTarget = null; + } + } + + function findTitleTarget(el) { + while (el && el !== document.body) { + if (el.getAttribute && (el.getAttribute('title') || el.getAttribute('data-tooltip'))) return el; + el = el.parentElement; + } + return null; + } + + document.addEventListener('mouseenter', function (e) { + const target = findTitleTarget(e.target); + if (!target) return; + + // title → data-tooltip verschieben (native tooltip unterdrücken) + const title = target.getAttribute('title'); + if (title) { + target.setAttribute('data-tooltip', title); + target.removeAttribute('title'); + } + + const text = target.getAttribute('data-tooltip'); + if (!text) return; + + // Falls bereits ein anderer tooltip offen → sofort wechseln + if (currentTarget && currentTarget !== target) { + hide(); + } + + clearTimeout(timeout); + timeout = setTimeout(function () { show(target); }, DELAY); + }, true); + + document.addEventListener('mouseleave', function (e) { + const target = findTitleTarget(e.target); + if (!target) return; + if (target === currentTarget || timeout) { + hide(); + } + }, true); + + // Auch bei Scroll/Resize verstecken + document.addEventListener('scroll', hide, true); + window.addEventListener('resize', hide); +})();