spirit-tracker/viz/app/dom.js
Brennan Wilkes (Text Groove) 7b7bf44dd3 feat: One day only
2026-01-21 20:30:56 -08:00

57 lines
No EOL
1.6 KiB
JavaScript

export function esc(s) {
return String(s ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]));
}
export function normImg(s) {
const v = String(s || "").trim();
if (!v) return "";
if (/^data:/i.test(v)) return "";
return v;
}
export function dateOnly(iso) {
const m = String(iso ?? "").match(/^(\d{4}-\d{2}-\d{2})/);
return m ? m[1] : "";
}
export function prettyTs(iso) {
const s = String(iso || "");
if (!s) return "";
const d = new Date(s);
if (!Number.isFinite(d.getTime())) return "";
const parts = new Intl.DateTimeFormat("en-US", {
timeZone: "America/Vancouver",
month: "long",
day: "numeric",
hour: "numeric",
minute: "2-digit",
hour12: true,
}).formatToParts(d);
let month = "";
let day = "";
let hour = "";
let minute = "";
let dayPeriod = "";
for (const p of parts) {
if (p.type === "month") month = p.value;
else if (p.type === "day") day = p.value;
else if (p.type === "hour") hour = p.value;
else if (p.type === "minute") minute = p.value;
else if (p.type === "dayPeriod") dayPeriod = p.value;
}
const ampm = String(dayPeriod || "").toLowerCase(); // "am"/"pm"
return `${month} ${day} ${hour}:${minute}${ampm}`;
}
export function renderThumbHtml(imgUrl, cls = "thumb") {
const img = normImg(imgUrl);
if (!img) return `<div class="thumbPlaceholder"></div>`;
return `<img class="${esc(cls)}" src="${esc(img)}" alt="" loading="lazy" onerror="this.style.display='none'" />`;
}