Custom Tooltip-System: eigenes Tooltip statt Browser-Standard, 200ms Delay, bevorzugt oben, individuelle Tooltips pro Git-Badge
This commit is contained in:
1 parent
6b7df138de
commit
c3812fbf2b
4 files changed
+153
-10
No files matched your search
@@ -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;
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
<link rel="stylesheet" href="css/easymde.min.css">
|
<link rel="stylesheet" href="css/easymde.min.css">
|
||||||
<link rel="stylesheet" href="css/file-explorer.css">
|
<link rel="stylesheet" href="css/file-explorer.css">
|
||||||
<link rel="stylesheet" href="css/markdown.css">
|
<link rel="stylesheet" href="css/markdown.css">
|
||||||
|
<link rel="stylesheet" href="css/tooltip.css">
|
||||||
<link rel="stylesheet" href="css/mobile.css">
|
<link rel="stylesheet" href="css/mobile.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
@@ -214,6 +215,7 @@
|
|||||||
<script src="js/lib/cm-mode-clike.min.js"></script>
|
<script src="js/lib/cm-mode-clike.min.js"></script>
|
||||||
<script src="js/lib/cm-mode-yaml.min.js"></script>
|
<script src="js/lib/cm-mode-yaml.min.js"></script>
|
||||||
<script src="js/lib/easymde.min.js"></script>
|
<script src="js/lib/easymde.min.js"></script>
|
||||||
|
<script src="js/tooltip.js"></script>
|
||||||
<script src="js/ui.js"></script>
|
<script src="js/ui.js"></script>
|
||||||
<script src="js/api.js"></script>
|
<script src="js/api.js"></script>
|
||||||
<script src="js/session.js"></script>
|
<script src="js/session.js"></script>
|
||||||
|
|||||||
@@ -51,27 +51,25 @@ registerPage('dashboard', {
|
|||||||
if (!metaEl) return;
|
if (!metaEl) return;
|
||||||
|
|
||||||
const parts = [];
|
const parts = [];
|
||||||
const tooltipParts = [];
|
|
||||||
if (addCount > 0) {
|
if (addCount > 0) {
|
||||||
parts.push(`<span class="git-badge git-add">+${addCount}</span>`);
|
const tip = `${addCount} ${addCount === 1 ? 'Datei' : 'Dateien'} hinzugefügt`;
|
||||||
tooltipParts.push(`${addCount} ${addCount === 1 ? 'Datei' : 'Dateien'} hinzugefügt`);
|
parts.push(`<span class="git-badge git-add" title="${esc(tip)}">+${addCount}</span>`);
|
||||||
}
|
}
|
||||||
if (editCount > 0) {
|
if (editCount > 0) {
|
||||||
parts.push(`<span class="git-badge git-edit">~${editCount}</span>`);
|
const tip = `${editCount} ${editCount === 1 ? 'Datei' : 'Dateien'} geändert`;
|
||||||
tooltipParts.push(`${editCount} ${editCount === 1 ? 'Datei' : 'Dateien'} geändert`);
|
parts.push(`<span class="git-badge git-edit" title="${esc(tip)}">~${editCount}</span>`);
|
||||||
}
|
}
|
||||||
if (deleteCount > 0) {
|
if (deleteCount > 0) {
|
||||||
parts.push(`<span class="git-badge git-delete">-${deleteCount}</span>`);
|
const tip = `${deleteCount} ${deleteCount === 1 ? 'Datei' : 'Dateien'} gelöscht`;
|
||||||
tooltipParts.push(`${deleteCount} ${deleteCount === 1 ? 'Datei' : 'Dateien'} gelöscht`);
|
parts.push(`<span class="git-badge git-delete" title="${esc(tip)}">-${deleteCount}</span>`);
|
||||||
}
|
}
|
||||||
if (outgoing > 0) {
|
if (outgoing > 0) {
|
||||||
parts.push(`<span class="git-badge git-outgoing"><i class="fa fa-arrow-up"></i> ${outgoing}</span>`);
|
const tip = `${outgoing} ${outgoing === 1 ? 'Commit muss' : 'Commits müssen'} noch gepusht werden`;
|
||||||
tooltipParts.push(`${outgoing} ${outgoing === 1 ? 'Commit muss' : 'Commits müssen'} noch gepusht werden`);
|
parts.push(`<span class="git-badge git-outgoing" title="${esc(tip)}"><i class="fa fa-arrow-up"></i> ${outgoing}</span>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const gitEl = document.createElement('span');
|
const gitEl = document.createElement('span');
|
||||||
gitEl.className = 'card-meta-item card-git-status';
|
gitEl.className = 'card-meta-item card-git-status';
|
||||||
gitEl.title = tooltipParts.join('\n');
|
|
||||||
gitEl.innerHTML = `<i class="fa fa-code-fork"></i> ${parts.join(' ')}`;
|
gitEl.innerHTML = `<i class="fa fa-code-fork"></i> ${parts.join(' ')}`;
|
||||||
metaEl.appendChild(gitEl);
|
metaEl.appendChild(gitEl);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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);
|
||||||
|
})();
|
||||||
Reference in new issue
Block a user