Fix channel list timeAgo counters: use monotonic lastActivityMs instead of ISO strings

- Track lastActivityMs (Date.now()) on each channel object instead of ISO lastActivity
- 1s interval iterates channels[] array and updates DOM text only (no re-render)
- Uses data-channel-hash attribute to find time elements after DOM rebuilds
- Simple formatSecondsAgo: <60s→Xs, <3600s→Xm, <86400s→Xh, else Xd
- Seed lastActivityMs from API ISO string on initial load
- WS handler sets lastActivityMs = Date.now() on receipt
- Bump channels.js cache buster
This commit is contained in:
you
2026-03-21 22:21:28 +00:00
parent d51e9ff7d0
commit 2fb0b8ae94
2 changed files with 26 additions and 11 deletions
+25 -10
View File
@@ -205,6 +205,14 @@
return str.length > len ? str.slice(0, len) + '…' : str;
}
function formatSecondsAgo(sec) {
if (sec < 0) sec = 0;
if (sec < 60) return sec + 's ago';
if (sec < 3600) return Math.floor(sec / 60) + 'm ago';
if (sec < 86400) return Math.floor(sec / 3600) + 'h ago';
return Math.floor(sec / 86400) + 'd ago';
}
function highlightMentions(text) {
if (!text) return '';
return escapeHtml(text).replace(/@\[([^\]]+)\]/g, function(_, name) {
@@ -420,7 +428,7 @@
var ch = channels.find(function (c) { return c.hash === channelName; });
if (ch) {
if (isFirstObservation) ch.messageCount = (ch.messageCount || 0) + 1;
ch.lastActivity = ts;
ch.lastActivityMs = Date.now();
ch.lastSender = sender;
ch.lastMessage = truncate(displayText, 100);
channelListDirty = true;
@@ -430,7 +438,7 @@
hash: channelName,
name: channelName,
messageCount: 1,
lastActivity: ts,
lastActivityMs: Date.now(),
lastSender: sender,
lastMessage: truncate(displayText, 100),
});
@@ -465,7 +473,7 @@
}
if (channelListDirty) {
channels.sort(function (a, b) { return (b.lastActivity || '').localeCompare(a.lastActivity || ''); });
channels.sort(function (a, b) { return (b.lastActivityMs || 0) - (a.lastActivityMs || 0); });
renderChannelList();
}
if (messagesDirty) {
@@ -486,11 +494,15 @@
}
});
// Tick relative timestamps every 30s
// Tick relative timestamps every 1s — iterates channels array, updates DOM text only
timeAgoTimer = setInterval(function () {
document.querySelectorAll('.ch-item-time[data-ts]').forEach(function (el) {
el.textContent = timeAgo(el.dataset.ts);
});
var now = Date.now();
for (var i = 0; i < channels.length; i++) {
var ch = channels[i];
if (!ch.lastActivityMs) continue;
var el = document.querySelector('.ch-item-time[data-channel-hash="' + ch.hash + '"]');
if (el) el.textContent = formatSecondsAgo(Math.floor((now - ch.lastActivityMs) / 1000));
}
}, 1000);
}
@@ -517,7 +529,10 @@
const rp = RegionFilter.getRegionParam();
const qs = rp ? '?region=' + encodeURIComponent(rp) : '';
const data = await api('/channels' + qs, { ttl: CLIENT_TTL.channels });
channels = (data.channels || []).sort((a, b) => (b.lastActivity || '').localeCompare(a.lastActivity || ''));
channels = (data.channels || []).map(ch => {
ch.lastActivityMs = ch.lastActivity ? new Date(ch.lastActivity).getTime() : 0;
return ch;
}).sort((a, b) => (b.lastActivityMs || 0) - (a.lastActivityMs || 0));
renderChannelList();
} catch (e) {
if (!silent) {
@@ -540,7 +555,7 @@
el.innerHTML = sorted.map(ch => {
const name = ch.name || `Channel ${ch.hash}`;
const color = getChannelColor(ch.hash);
const time = ch.lastActivity ? timeAgo(ch.lastActivity) : '';
const time = ch.lastActivityMs ? formatSecondsAgo(Math.floor((Date.now() - ch.lastActivityMs) / 1000)) : '';
const preview = ch.lastSender && ch.lastMessage
? `${ch.lastSender}: ${truncate(ch.lastMessage, 28)}`
: `${ch.messageCount} messages`;
@@ -552,7 +567,7 @@
<div class="ch-item-body">
<div class="ch-item-top">
<span class="ch-item-name">${escapeHtml(name)}</span>
<span class="ch-item-time" data-ts="${ch.lastActivity || ''}">${time}</span>
<span class="ch-item-time" data-channel-hash="${ch.hash}">${time}</span>
</div>
<div class="ch-item-preview">${escapeHtml(preview)}</div>
</div>
+1 -1
View File
@@ -86,7 +86,7 @@
<script src="home.js?v=1774042199"></script>
<script src="packets.js?v=1774330600"></script>
<script src="map.js?v=1774126708" onerror="console.error('Failed to load:', this.src)"></script>
<script src="channels.js?v=1774131325" onerror="console.error('Failed to load:', this.src)"></script>
<script src="channels.js?v=1774331200" onerror="console.error('Failed to load:', this.src)"></script>
<script src="nodes.js?v=1774126708" onerror="console.error('Failed to load:', this.src)"></script>
<script src="traces.js?v=1774350000" onerror="console.error('Failed to load:', this.src)"></script>
<script src="analytics.js?v=1774126708" onerror="console.error('Failed to load:', this.src)"></script>