adding online presence for dms
This commit is contained in:
@@ -10845,6 +10845,53 @@ body.layout-modern .tag-controls {
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dm-header-name-wrap {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-presence {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
font-size: 0.72em;
|
||||||
|
color: #666;
|
||||||
|
min-height: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-presence-dot {
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
border-radius: 50%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-presence--online .dm-presence-dot {
|
||||||
|
background: #3ddc84;
|
||||||
|
box-shadow: 0 0 0 2px rgba(61, 220, 132, 0.25);
|
||||||
|
animation: dm-presence-pulse 2s ease infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-presence--online {
|
||||||
|
color: #3ddc84;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-presence--recent .dm-presence-dot {
|
||||||
|
background: #f5a623;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dm-presence--recent {
|
||||||
|
color: #f5a623;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes dm-presence-pulse {
|
||||||
|
0%, 100% { box-shadow: 0 0 0 2px rgba(61, 220, 132, 0.25); }
|
||||||
|
50% { box-shadow: 0 0 0 4px rgba(61, 220, 132, 0.1); }
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Key notice banner ───────────────────────────────────── */
|
/* ── Key notice banner ───────────────────────────────────── */
|
||||||
.dm-key-notice {
|
.dm-key-notice {
|
||||||
background: rgba(255, 200, 80, 0.15);
|
background: rgba(255, 200, 80, 0.15);
|
||||||
|
|||||||
@@ -392,6 +392,39 @@ if (window.__dmLoaded) {
|
|||||||
(el.textContent = timeAgo(el.dataset.ts)));
|
(el.textContent = timeAgo(el.dataset.ts)));
|
||||||
tickTimestamps(); // Run immediately so values are fresh
|
tickTimestamps(); // Run immediately so values are fresh
|
||||||
window._dmTimestampTicker = setInterval(tickTimestamps, 10_000);
|
window._dmTimestampTicker = setInterval(tickTimestamps, 10_000);
|
||||||
|
|
||||||
|
// ── Online presence ───────────────────────────────────────────────────
|
||||||
|
if (window._dmPresenceTicker) clearInterval(window._dmPresenceTicker);
|
||||||
|
|
||||||
|
const presenceEl = document.getElementById('dm-presence');
|
||||||
|
const pollPresence = async () => {
|
||||||
|
if (!presenceEl) return;
|
||||||
|
try {
|
||||||
|
const data = await (await fetch(`/api/dm/presence/${currentOtherId}`)).json();
|
||||||
|
if (!data.success) { presenceEl.innerHTML = ''; return; }
|
||||||
|
|
||||||
|
const now = ~~(Date.now() / 1000);
|
||||||
|
const diff = now - (data.last_seen || 0); // seconds ago
|
||||||
|
|
||||||
|
if (data.online) {
|
||||||
|
presenceEl.className = 'dm-presence dm-presence--online';
|
||||||
|
presenceEl.innerHTML = '<span class="dm-presence-dot"></span>Online';
|
||||||
|
} else if (diff < 3600) {
|
||||||
|
// Active within the last hour
|
||||||
|
const mins = Math.max(1, Math.floor(diff / 60));
|
||||||
|
presenceEl.className = 'dm-presence dm-presence--recent';
|
||||||
|
presenceEl.innerHTML = `<span class="dm-presence-dot"></span>Active ${mins}m ago`;
|
||||||
|
} else {
|
||||||
|
presenceEl.className = 'dm-presence dm-presence--offline';
|
||||||
|
presenceEl.innerHTML = data.last_seen
|
||||||
|
? `Last seen ${timeAgo(new Date(data.last_seen * 1000).toISOString())}`
|
||||||
|
: 'Last seen a long time ago';
|
||||||
|
}
|
||||||
|
} catch { if (presenceEl) presenceEl.innerHTML = ''; }
|
||||||
|
};
|
||||||
|
|
||||||
|
await pollPresence();
|
||||||
|
window._dmPresenceTicker = setInterval(pollPresence, 30_000);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadThread(thread, prepend = false) {
|
async function loadThread(thread, prepend = false) {
|
||||||
|
|||||||
@@ -406,6 +406,24 @@ export default (router, tpl) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Presence check — last_seen timestamp for a given user (online = seen < 5 min ago)
|
||||||
|
router.get(/\/api\/dm\/presence\/(?<userId>\d+)/, async (req, res) => {
|
||||||
|
if (!getPrivateMessages()) return json(res, { success: false }, 404);
|
||||||
|
if (!req.session) return json(res, { success: false }, 401);
|
||||||
|
const userId = parseInt(req.params.userId, 10);
|
||||||
|
try {
|
||||||
|
const rows = await db`SELECT last_seen FROM "user" WHERE id = ${userId} AND banned = false LIMIT 1`;
|
||||||
|
if (!rows.length) return json(res, { success: false, msg: 'User not found' }, 404);
|
||||||
|
const lastSeen = rows[0].last_seen || 0; // unix seconds
|
||||||
|
const now = ~~(Date.now() / 1000);
|
||||||
|
const online = (now - lastSeen) < 300; // 5-minute window
|
||||||
|
return json(res, { success: true, online, last_seen: lastSeen });
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[DM] presence failed:', err);
|
||||||
|
return json(res, { success: false }, 500);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Total unread DM count (for navbar badge polling)
|
// Total unread DM count (for navbar badge polling)
|
||||||
router.get('/api/dm/unread', async (req, res) => {
|
router.get('/api/dm/unread', async (req, res) => {
|
||||||
if (!getPrivateMessages()) return json(res, { success: true, count: 0 });
|
if (!getPrivateMessages()) return json(res, { success: true, count: 0 });
|
||||||
|
|||||||
@@ -13,7 +13,10 @@
|
|||||||
@else
|
@else
|
||||||
<img class="dm-header-avatar" src="/a/default.png" alt="">
|
<img class="dm-header-avatar" src="/a/default.png" alt="">
|
||||||
@endif
|
@endif
|
||||||
<a href="/user/{{ other.user.toLowerCase() }}" class="dm-header-username" @if(other.username_color) style="color:{{ other.username_color }}" @endif>{!! other.display_name || other.user !!}</a>
|
<div class="dm-header-name-wrap">
|
||||||
|
<a href="/user/{{ other.user.toLowerCase() }}" class="dm-header-username" @if(other.username_color) style="color:{{ other.username_color }}" @endif>{!! other.display_name || other.user !!}</a>
|
||||||
|
<span id="dm-presence" class="dm-presence"></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="dm-manage-keys-btn btn-small" title="Manage encryption key">🔑 Keys</button>
|
<button class="dm-manage-keys-btn btn-small" title="Manage encryption key">🔑 Keys</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user