Add now.js: recompute "Last updated" relative phrase client-side
build/Now.hs renders the .now-stamp-relative phrase ("3 days ago") at
build time against the build machine's clock; a page served days later
from cache or a CDN would then read stale. now.js recomputes the
phrase in the browser from the <time datetime> attribute (an
unambiguous YYYY-MM-DD) against the visitor's clock, with bucket
thresholds that mirror Now.hs:relativeTime exactly so the no-JS
fallback and the recomputed value agree.
* static/js/now.js — the recomputation script.
* templates/default.html includes it via $if(now)$ so it only loads
on the Current page.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
70dda56625
commit
af27479c6e
|
|
@ -0,0 +1,75 @@
|
|||
/* 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();
|
||||
}
|
||||
})();
|
||||
|
|
@ -29,6 +29,7 @@ $partial("templates/partials/footer.html")$
|
|||
<script src="/js/lightbox.js" defer></script>
|
||||
$if(home)$<script src="/js/random.js" defer></script>$endif$
|
||||
$if(reading)$<script src="/js/reading.js" defer></script>$endif$
|
||||
$if(now)$<script src="/js/now.js" defer></script>$endif$
|
||||
$if(photography)$<script src="/js/photography-modes.js" defer></script>$endif$
|
||||
$if(photography-map)$<script src="/leaflet/leaflet.js" defer></script>$endif$
|
||||
$if(photography-map)$<script src="/leaflet/leaflet.markercluster.js" defer></script>$endif$
|
||||
|
|
|
|||
Loading…
Reference in New Issue