// Submitting the moment a reader meant, rather than the words they typed. // // A `datetime-local` box asks for a time the way a person says one -- "the 14th // at half past two" -- and that names a different moment in Denver than it does // in Berlin. A route storing an instant needs the moment, and nothing in HTML // converts one to the other: the box submits the characters. So somebody // converts, and in this renderer that is here. // // It is here rather than above the renderer because the browser is the only // party that knows what "your computer's time zone" means. A description cannot // answer it; a server can only guess; a terminal and an egui host would each // answer it their own way. `makeover_layout::Field::as_instant` is the // description saying it wants the conversion and declining to say how, and this // file is the how for one host. // // It reads one attribute the form renderer writes, `data-instant`, and it is // the whole of the per-app publish-at plumbing this stack exists to delete. The // MNW server carried its own copy under a private `data-config` vocabulary. (() => { "use strict"; // The mark makeover-webview puts on a datetime input whose description // asked for an instant. Only that kind is marked: a date and a time are // each half a moment and cannot name one on their own. const MARK = "data-instant"; /** * The absolute instant a local wall-clock string names, as RFC 3339. * * `new Date(v)` reads a bare `YYYY-MM-DDTHH:mm` in the browser's own zone, * which is exactly the reading wanted. A value the browser cannot parse is * left alone rather than replaced with `Invalid Date`: an unparseable value * is a validation failure the route should see and answer, and rewriting it * here would turn a legible error into an unreadable one. */ const instant = (local) => { const at = new Date(local); return Number.isNaN(at.getTime()) ? null : at.toISOString(); }; // Rewrite on the way out, on the request htmx is about to make, so nothing // the reader can see changes: the box keeps showing the time they typed and // the wire carries the moment it names. // // The walk is over the submitting element's own marked inputs rather than // over the document, so a page with two forms on it converts the one being // submitted. `htmx:config:request` is htmx 4's name; 2.x spelled it // `htmx:configRequest`. document.body.addEventListener("htmx:config:request", (event) => { const source = event.detail?.ctx?.sourceElement; const body = event.detail?.ctx?.request?.body; if (!source?.querySelectorAll || !body) return; const marked = source.matches?.(`[${MARK}]`) === true ? [source] : source.querySelectorAll(`[${MARK}]`); for (const input of marked) { const name = input.getAttribute("name"); if (!name) continue; const local = body.get(name); if (typeof local !== "string" || !local) continue; const moment = instant(local); if (moment !== null) body.set(name, moment); } }); })();