From 8f8bda1d0db8662c8071cfcc530eb7362827217c Mon Sep 17 00:00:00 2001 From: Kibi Kelburton Date: Mon, 18 May 2026 18:02:11 +0200 Subject: [PATCH] adding online presence for dms --- public/s/css/f0ckm.css | 47 ++++++++++++++++++++++++++++++++ public/s/js/messages.js | 33 ++++++++++++++++++++++ src/inc/routes/messages.mjs | 18 ++++++++++++ views/messages-conversation.html | 5 +++- 4 files changed, 102 insertions(+), 1 deletion(-) diff --git a/public/s/css/f0ckm.css b/public/s/css/f0ckm.css index d033d33..81ddda9 100644 --- a/public/s/css/f0ckm.css +++ b/public/s/css/f0ckm.css @@ -10845,6 +10845,53 @@ body.layout-modern .tag-controls { 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 ───────────────────────────────────── */ .dm-key-notice { background: rgba(255, 200, 80, 0.15); diff --git a/public/s/js/messages.js b/public/s/js/messages.js index 9a6885d..1bc6f99 100644 --- a/public/s/js/messages.js +++ b/public/s/js/messages.js @@ -392,6 +392,39 @@ if (window.__dmLoaded) { (el.textContent = timeAgo(el.dataset.ts))); tickTimestamps(); // Run immediately so values are fresh 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 = '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 = `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) { diff --git a/src/inc/routes/messages.mjs b/src/inc/routes/messages.mjs index f9bc199..f9d3771 100644 --- a/src/inc/routes/messages.mjs +++ b/src/inc/routes/messages.mjs @@ -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\/(?\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) router.get('/api/dm/unread', async (req, res) => { if (!getPrivateMessages()) return json(res, { success: true, count: 0 }); diff --git a/views/messages-conversation.html b/views/messages-conversation.html index b8caeb3..315f9d2 100644 --- a/views/messages-conversation.html +++ b/views/messages-conversation.html @@ -13,7 +13,10 @@ @else @endif - {!! other.display_name || other.user !!} +