253 lines
8.6 KiB
HTML
253 lines
8.6 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="de">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<link rel="icon" href="/img/icon.png" type="image/png">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Benachrichtigungen – xXx Sphere</title>
|
||
<link rel="stylesheet" href="/css/variables.css">
|
||
<link rel="stylesheet" href="/css/style.css">
|
||
<style>
|
||
.notif-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.5rem;
|
||
width: 100%;
|
||
}
|
||
|
||
.notif-item {
|
||
background: var(--color-card);
|
||
border: 1px solid var(--color-secondary);
|
||
border-radius: 10px;
|
||
padding: 0.75rem 1rem;
|
||
display: flex;
|
||
gap: 0.75rem;
|
||
align-items: flex-start;
|
||
cursor: pointer;
|
||
transition: border-color 0.15s, background 0.15s;
|
||
text-decoration: none;
|
||
color: inherit;
|
||
}
|
||
|
||
.notif-item:hover {
|
||
border-color: var(--color-primary);
|
||
background: rgba(255,255,255,0.03);
|
||
}
|
||
|
||
.notif-item.unread {
|
||
border-left: 3px solid var(--color-primary);
|
||
background: rgba(var(--color-primary-rgb, 200,0,0), 0.05);
|
||
}
|
||
|
||
.notif-item.unread:hover {
|
||
background: rgba(var(--color-primary-rgb, 200,0,0), 0.09);
|
||
}
|
||
|
||
.notif-avatar {
|
||
width: 42px;
|
||
height: 42px;
|
||
border-radius: 50%;
|
||
object-fit: cover;
|
||
flex-shrink: 0;
|
||
background: var(--color-secondary);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 1.1rem;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.notif-avatar img {
|
||
width: 100%;
|
||
height: 100%;
|
||
object-fit: cover;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
.notif-dot {
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
background: var(--color-primary);
|
||
flex-shrink: 0;
|
||
margin-top: 0.45rem;
|
||
}
|
||
|
||
.notif-dot.read { background: transparent; }
|
||
|
||
.notif-body { flex: 1; min-width: 0; }
|
||
|
||
.notif-text {
|
||
font-size: 0.9rem;
|
||
line-height: 1.5;
|
||
white-space: pre-wrap;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.notif-time {
|
||
font-size: 0.75rem;
|
||
color: var(--color-muted);
|
||
margin-top: 0.2rem;
|
||
}
|
||
|
||
.notif-arrow {
|
||
font-size: 0.75rem;
|
||
color: var(--color-muted);
|
||
flex-shrink: 0;
|
||
align-self: center;
|
||
}
|
||
|
||
.notif-empty {
|
||
color: var(--color-muted);
|
||
font-size: 0.9rem;
|
||
margin-top: 0.5rem;
|
||
}
|
||
|
||
.notif-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 1.25rem;
|
||
}
|
||
</style>
|
||
</head>
|
||
<body class="app">
|
||
<div class="main">
|
||
<div class="content">
|
||
<div class="notif-header">
|
||
<h1 style="margin:0;">🔔 Benachrichtigungen</h1>
|
||
<button id="markAllBtn" onclick="markAllRead()"
|
||
style="background:none;border:1px solid var(--color-secondary);color:var(--color-muted);
|
||
padding:0.35rem 0.85rem;border-radius:7px;cursor:pointer;font-size:0.82rem;width:auto;display:none;">
|
||
Alle als gelesen markieren
|
||
</button>
|
||
</div>
|
||
|
||
<div class="notif-list" id="notifList">
|
||
<p class="notif-empty" id="notifEmpty" style="display:none;">Keine Benachrichtigungen vorhanden.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="/js/icons.js"></script>
|
||
<script src="/js/sidebar.js"></script>
|
||
<script src="/js/social-sidebar.js"></script>
|
||
<script>
|
||
function fmtRelTime(isoStr) {
|
||
const diff = Date.now() - new Date(isoStr).getTime();
|
||
const min = Math.floor(diff / 60000);
|
||
const h = Math.floor(min / 60);
|
||
const d = Math.floor(h / 24);
|
||
if (d > 0) return `vor ${d} Tag${d > 1 ? 'en' : ''}`;
|
||
if (h > 0) return `vor ${h} Std.`;
|
||
if (min > 0) return `vor ${min} Min.`;
|
||
return 'gerade eben';
|
||
}
|
||
|
||
function esc(s) {
|
||
return String(s)
|
||
.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')
|
||
.replace(/"/g,'"');
|
||
}
|
||
|
||
async function handleClick(id, targetUrl) {
|
||
// Als gelesen markieren
|
||
await fetch(`/notifications/${id}/read`, { method: 'POST' }).catch(() => {});
|
||
|
||
// Dot des angeklickten Items auf "gelesen" setzen
|
||
const dot = document.querySelector(`[data-notif-id="${id}"] .notif-dot`);
|
||
if (dot) dot.classList.add('read');
|
||
const item = document.querySelector(`[data-notif-id="${id}"]`);
|
||
if (item) item.classList.remove('unread');
|
||
|
||
// Badge aktualisieren
|
||
const remaining = document.querySelectorAll('.notif-item.unread').length;
|
||
['socialNotifBadge','socialMobileNotifBadge'].forEach(bid => {
|
||
const el = document.getElementById(bid);
|
||
if (!el) return;
|
||
el.textContent = remaining;
|
||
el.style.display = remaining > 0 ? '' : 'none';
|
||
});
|
||
if (remaining === 0) document.getElementById('markAllBtn').style.display = 'none';
|
||
|
||
// Navigieren wenn Zielseite vorhanden
|
||
if (targetUrl) window.location.href = targetUrl;
|
||
}
|
||
|
||
async function markAllRead() {
|
||
await fetch('/notifications/read-all', { method: 'POST' }).catch(() => {});
|
||
document.querySelectorAll('.notif-item.unread').forEach(el => {
|
||
el.classList.remove('unread');
|
||
const dot = el.querySelector('.notif-dot');
|
||
if (dot) dot.classList.add('read');
|
||
});
|
||
document.getElementById('markAllBtn').style.display = 'none';
|
||
['socialNotifBadge','socialMobileNotifBadge'].forEach(id => {
|
||
const el = document.getElementById(id);
|
||
if (el) { el.textContent = '0'; el.style.display = 'none'; }
|
||
});
|
||
}
|
||
|
||
async function loadNotifications() {
|
||
const list = document.getElementById('notifList');
|
||
const empty = document.getElementById('notifEmpty');
|
||
const btn = document.getElementById('markAllBtn');
|
||
|
||
// Vorhandene Einträge (außer Empty-Hint) entfernen
|
||
list.querySelectorAll('.notif-item').forEach(el => el.remove());
|
||
|
||
try {
|
||
const res = await fetch('/notifications');
|
||
if (!res.ok) return;
|
||
const items = await res.json();
|
||
|
||
if (items.length === 0) {
|
||
empty.style.display = '';
|
||
btn.style.display = 'none';
|
||
return;
|
||
}
|
||
empty.style.display = 'none';
|
||
|
||
const hasUnread = items.some(n => !n.read);
|
||
btn.style.display = hasUnread ? '' : 'none';
|
||
|
||
items.forEach(n => {
|
||
const div = document.createElement('div');
|
||
div.className = 'notif-item' + (n.read ? '' : ' unread');
|
||
div.dataset.notifId = n.id;
|
||
div.setAttribute('role', 'button');
|
||
div.setAttribute('tabindex', '0');
|
||
div.onclick = () => handleClick(n.id, n.targetUrl);
|
||
div.onkeydown = e => { if (e.key === 'Enter') handleClick(n.id, n.targetUrl); };
|
||
|
||
const arrow = n.targetUrl
|
||
? `<span class="notif-arrow">›</span>`
|
||
: '';
|
||
|
||
const avatarInner = n.senderAvatar
|
||
? `<img src="data:image/png;base64,${n.senderAvatar}" alt="${esc(n.senderName || '')}">`
|
||
: `<span>🔔</span>`;
|
||
|
||
div.innerHTML = `
|
||
<div class="notif-avatar">${avatarInner}</div>
|
||
<div class="notif-dot${n.read ? ' read' : ''}"></div>
|
||
<div class="notif-body">
|
||
<div class="notif-text">${esc(n.text)}</div>
|
||
<div class="notif-time">${fmtRelTime(n.sentAt)}</div>
|
||
</div>
|
||
${arrow}`;
|
||
list.appendChild(div);
|
||
});
|
||
} catch(e) {
|
||
console.error(e);
|
||
}
|
||
}
|
||
|
||
// SSE: Neue Benachrichtigung → sofort neu laden
|
||
window.__sseOnNotification = () => loadNotifications();
|
||
|
||
loadNotifications();
|
||
</script>
|
||
</body>
|
||
</html>
|