// Helpers shared by the staff panel (admin.js) and the public site (public.js). const $ = (sel, root = document) => root.querySelector(sel); const esc = (s) => String(s ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c])); const MIN = 60 * 1000; const ICON = { search: '', plus: '', }; // api calls the panel's JSON API and throws an Error carrying the server's message on failure. async function api(path, { method = "GET", body } = {}) { const opts = { method, headers: { "X-Requested-With": "simpleadmin-web" }, credentials: "same-origin" }; if (body !== undefined) { opts.headers["Content-Type"] = "application/json"; opts.body = JSON.stringify(body); } let res; try { res = await fetch(path, opts); } catch { throw new Error("Couldn't reach the panel. Check your connection."); } let data = null; try { data = await res.json(); } catch { /* empty or non-JSON body */ } if (!res.ok) throw new Error(data?.error || `The panel answered ${res.status}.`); return data; } function qs(params) { const u = new URLSearchParams(); for (const [k, v] of Object.entries(params)) if (v !== undefined && v !== null && v !== "") u.set(k, v); const s = u.toString(); return s ? `?${s}` : ""; } function debounce(fn, ms) { let t; return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), ms); }; } function fmtDuration(min) { if (min === 0) return "Permanent"; if (min < 60) return `${min} min`; if (min < 1440) return `${Math.round(min / 60)} h`; const d = Math.round(min / 1440); return d === 1 ? "1 day" : `${d} days`; } function fmtAgo(iso) { const m = Math.round((Date.now() - new Date(iso).getTime()) / MIN); if (m < 1) return "now"; if (m < 60) return `${m} min ago`; if (m < 1440) return `${Math.round(m / 60)} h ago`; const d = Math.round(m / 1440); return d === 1 ? "yesterday" : `${d} days ago`; } function fmtLeft(min) { if (min < 60) return `${Math.max(1, Math.round(min))} min left`; if (min < 1440) return `${Math.round(min / 60)} h left`; return `${Math.round(min / 1440)} d left`; } function fmtDate(iso) { return new Date(iso).toLocaleString(undefined, { day: "numeric", month: "short", hour: "2-digit", minute: "2-digit" }); } // Minutes served and left on a timed penalty. With SimpleAdmin's online-only time mode, "passed" // counts minutes served. function served(p) { if (p.passed != null && p.status === "active" && !p.ends) return { done: p.passed, left: p.duration - p.passed }; const start = new Date(p.created).getTime(); const end = p.ends ? new Date(p.ends).getTime() : start + p.duration * MIN; const done = (Math.min(Date.now(), end) - start) / MIN; return { done, left: (end - Date.now()) / MIN }; } function rankTag(rank) { if (!rank) return 'Player'; return `${esc(rank.label)}`; } function initial(name) { return esc(String(name || "").replace(/[^a-z0-9]/gi, "").charAt(0).toUpperCase() || "?"); } // Time served against the full term: the bar under each length. function termCell(p) { const cls = ["term", p.kind === "comm" ? "comm" : ""]; let right; let pct = 100; if (p.duration === 0) { cls.push("perm"); right = p.status === "lifted" ? "Lifted" : "Never expires"; if (p.status === "lifted") cls.push("lifted"); } else { const s = served(p); pct = Math.max(0, Math.min(100, (s.done / p.duration) * 100)); if (p.status === "active") right = fmtLeft(s.left); else if (p.status === "expired") { right = "Served"; cls.push("done"); } else { right = "Lifted"; cls.push("lifted"); } } const bar = p.duration === 0 ? "" : ``; return `
${fmtDuration(p.duration)}${right}
`; } function statusTag(p) { if (p.status === "active") return 'Active'; if (p.status === "expired") return 'Expired'; return 'Lifted'; } const COMM_LABEL = { GAG: "Gag", MUTE: "Mute", SILENCE: "Silence" }; const COMM_HELP = { GAG: "Text chat blocked", MUTE: "Voice blocked", SILENCE: "Text and voice blocked" }; function segmented(name, value, options) { return `
${options.map(([v, l]) => ``).join("")}
`; } function searchBox(placeholder, value) { return ``; } function pager(list) { const pages = Math.max(1, Math.ceil(list.total / list.pageSize)); if (pages <= 1) return ""; return `
Page ${list.page} of ${pages}
`; } function loading() { return '
Loading…
'; } function errorBox(err) { return `
${esc(err.message)}
`; } function closeDrawer() { $("#drawer-root").innerHTML = ""; } function toast(msg) { const root = $("#toast-root"); root.innerHTML = `
${esc(msg)}
`; clearTimeout(toast.t); toast.t = setTimeout(() => (root.innerHTML = ""), 3600); } const steamProfile = (sid) => `https://steamcommunity.com/profiles/${encodeURIComponent(sid)}`;