diff --git a/static/account.js b/static/account.js index 8e42d03..94b3f21 100644 --- a/static/account.js +++ b/static/account.js @@ -8,10 +8,17 @@ let currentUser = null; // {id, email, display_name} or null const authCbs = []; // notified on login/logout so pages can re-gate +// The app's base path (e.g. "/thermograph"), derived from this module's own URL +// — it's always served from `${base}/account.js`, so this resolves correctly on +// both the depth-1 interactive pages and the deep SEO URLs (/climate/{slug}/…), +// where a directory-relative "api/v2/…" would otherwise resolve wrong and 404. +const APP_BASE = new URL(".", import.meta.url).pathname.replace(/\/$/, ""); +const u = (path) => `${APP_BASE}/${String(path).replace(/^\//, "")}`; + // --- shared fetch helper ----------------------------------------------------- -// Base-relative URL (resolves under /thermograph automatically), cookie sent, -// and a constant X-TG-Auth header the server can require as cheap CSRF defense -// (trivial same-origin, impossible cross-site without a preflight we never grant). +// Absolute app-base URL (so it works from any page depth), cookie sent, and a +// constant X-TG-Auth header the server can require as cheap CSRF defense (trivial +// same-origin, impossible cross-site without a preflight we never grant). export async function apiFetch(path, { method = "GET", json, form } = {}) { const opts = { method, credentials: "same-origin", headers: { "X-TG-Auth": "1" } }; if (json !== undefined) { @@ -21,7 +28,7 @@ export async function apiFetch(path, { method = "GET", json, form } = {}) { opts.headers["Content-Type"] = "application/x-www-form-urlencoded"; opts.body = new URLSearchParams(form).toString(); } - return fetch(path, opts); + return fetch(u(path), opts); } // Parse a JSON response, throwing a friendly Error on failure. fastapi-users @@ -285,7 +292,7 @@ function renderHeader() { - Manage alerts → + Manage alerts →
@@ -293,7 +300,7 @@ function renderHeader() { ${USER_IC}${escapeHtml(name)}
`; diff --git a/static/climate.js b/static/climate.js new file mode 100644 index 0000000..d30f9b6 --- /dev/null +++ b/static/climate.js @@ -0,0 +1,18 @@ +// Client-side temperature converter for the server-rendered climate/city/record +// pages. Those pages render every temperature as `72°F` (see content.py `_temp`/`_temp_bare`) so the +// value is real in the HTML for crawlers and no-JS visitors. Here we rewrite each +// span to the active unit on load and whenever the °F/°C toggle changes — reusing +// the same unit machinery as the interactive pages. Importing units.js also boots +// its toggle-injection IIFE, so this one script gives the pages a working toggle. +import { fmtTemp, onUnitChange } from "./units.js"; + +function paint() { + for (const el of document.querySelectorAll(".temp[data-temp-f]")) { + const f = parseFloat(el.dataset.tempF); + if (!Number.isNaN(f)) el.textContent = fmtTemp(f, !el.hasAttribute("data-bare")); + } +} + +paint(); +onUnitChange(paint); diff --git a/static/units.js b/static/units.js index 8a81e68..c4b0187 100644 --- a/static/units.js +++ b/static/units.js @@ -4,7 +4,24 @@ // stored/plotted values stay in °F and only the numbers shown flip. const UNIT_KEY = "thermograph:unit"; -let _unit = (localStorage.getItem(UNIT_KEY) === "C") ? "C" : "F"; + +// First-time default: pick °F only for the countries that actually use it (the US +// plus a handful of territories/nations), otherwise °C — read off the browser +// locale's region subtag (en-US → F, de-DE → C). A stored choice always wins, so +// this only affects visitors who've never toggled. Not persisted, so an unstored +// visitor keeps re-deriving the same locale default until they pick one. +const F_REGIONS = new Set(["US", "PR", "GU", "VI", "AS", "MP", "UM", "BS", "BZ", "KY", "PW", "FM", "MH", "LR"]); +function defaultUnit() { + const langs = (navigator.languages && navigator.languages.length) + ? navigator.languages : [navigator.language]; + for (const l of langs) { + const m = /-([A-Za-z]{2})\b/.exec(l || ""); + if (m) return F_REGIONS.has(m[1].toUpperCase()) ? "F" : "C"; + } + return "F"; // no region subtag to go on → keep the historical default +} +const _stored = localStorage.getItem(UNIT_KEY); +let _unit = (_stored === "C" || _stored === "F") ? _stored : defaultUnit(); const _unitCbs = []; export function getUnit() { return _unit; }