levineuwirth.org/static/js/now.js

76 lines
2.8 KiB
JavaScript

/* now.js — Keep the Current page's "Last updated" relative phrase
honest.
build/Now.hs renders `.now-stamp-relative` ("3 days ago") at build
time, relative to the build machine's clock. A page served days
later from cache/CDN would then lie. We recompute the phrase in the
browser from the `<time datetime>` attribute (an unambiguous
YYYY-MM-DD), against the visitor's own clock.
The bucket thresholds below mirror `relativeTime` in build/Now.hs
exactly — keep the two in sync. The server-rendered text remains the
no-JS fallback and is only replaced once we've recomputed. */
(function () {
'use strict';
function relative(days) {
if (days < 0) return ''; // future / clock skew
if (days === 0) return 'today';
if (days === 1) return 'yesterday';
if (days < 7) return days + ' days ago';
var n, unit;
if (days < 28) { n = Math.floor(days / 7); unit = 'week'; }
else if (days < 365) { n = Math.floor(days / 30); unit = 'month'; }
else { n = Math.floor(days / 365); unit = 'year'; }
return n === 1 ? ('1 ' + unit + ' ago')
: (n + ' ' + unit + 's ago');
}
function update() {
var stamp = document.querySelector('.now-stamp');
if (!stamp) return;
var timeEl = stamp.querySelector('.now-stamp-date');
if (!timeEl) return;
var iso = timeEl.getAttribute('datetime');
var m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(iso || '');
if (!m) return; // unparseable — leave the SSR fallback as-is
// Calendar-day difference, computed via UTC epoch days so DST
// transitions can't add or drop a day. "today" uses the
// visitor's *local* date components, matching what they'd
// read off a wall calendar.
var then = Date.UTC(+m[1], +m[2] - 1, +m[3]);
var local = new Date();
var today = Date.UTC(
local.getFullYear(),
local.getMonth(),
local.getDate()
);
var days = Math.round((today - then) / 86400000);
var text = relative(days);
var rel = stamp.querySelector('.now-stamp-relative');
if (!text) {
// No meaningful relative phrase (e.g. dated in the future):
// drop any stale server-rendered one rather than keep a lie.
if (rel) rel.remove();
return;
}
if (!rel) {
rel = document.createElement('span');
rel.className = 'now-stamp-relative';
stamp.appendChild(rel);
}
rel.textContent = text;
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', update);
} else {
update();
}
})();