// SvxPortal — web front-end for SvxReflector // Copyright (C) 2026 SA7AUX (Björn) // SPDX-License-Identifier: AGPL-3.0-or-later // // This program is free software: you can redistribute it and/or modify it under // the terms of the GNU Affero General Public License as published by the Free // Software Foundation, either version 3 of the License, or (at your option) any // later version. See the LICENSE file for details. // fmt.js — shared formatting helpers for every web (dmr-web, svx-web, svxlink-web). // Canonical copy: /lib/fmt.js; other repos carry a COPY. // Every function here replaced an identical local copy; outputs are unchanged. (function () { 'use strict'; // HTML-escape for innerHTML/attribute use. function escHtml(s) { return String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); } // Two-digit zero pad (clock fields). function fmt2(n) { return String(n).padStart(2, '0'); } // Age in seconds → "12s" / "5m" / "3h" / "2d"; "—" when unknown. function fmtAge(s) { if (s == null || s < 0) return '—'; if (s < 60) return s + 's'; if (s < 3600) return Math.floor(s / 60) + 'm'; if (s < 86400) return Math.floor(s / 3600) + 'h'; return Math.floor(s / 86400) + 'd'; } // Duration in seconds → "42s" / "3m 5s" / "1h 07m". function fmtDur(s) { s = Number(s) || 0; if (s < 60) return s + 's'; const m = Math.floor(s / 60), r = s % 60; if (s < 3600) return m + 'm ' + r + 's'; const h = Math.floor(s / 3600), mm = Math.floor((s % 3600) / 60); return h + 'h ' + mm + 'm'; } // Unix seconds → local date/time string; "—" when missing. function fmtTs(ts) { if (!ts) return '—'; try { return new Date(ts * 1000).toLocaleString(); } catch (_) { return '—'; } } // Callsigns: "SA7AUX-R" / "SA7AUX (x)" → "SA7AUX"; natural sort by prefix, // number, suffix so SM7A < SM7B < SM70A. function baseCallsign(cs) { return String(cs || '').split(/[-(]/)[0].trim(); } function splitForSort(cs) { const b = baseCallsign(cs).toUpperCase(); const m = b.match(/^([A-Z]+)(\d+)([A-Z0-9]*)$/); if (!m) return { pre: b, num: Number.POSITIVE_INFINITY, post: '' }; return { pre: m[1], num: Number(m[2]), post: (m[3] || '') }; } function callsignCompare(a, b) { const A = splitForSort(a); const B = splitForSort(b); if (A.pre !== B.pre) return A.pre < B.pre ? -1 : 1; if (A.num !== B.num) return A.num - B.num; if (A.post !== B.post) return A.post < B.post ? -1 : 1; const ba = baseCallsign(a).toUpperCase(); const bb = baseCallsign(b).toUpperCase(); if (ba !== bb) return ba < bb ? -1 : 1; const fa = String(a).toUpperCase(); const fb = String(b).toUpperCase(); if (fa !== fb) return fa < fb ? -1 : 1; return 0; } // Frequencies: "145,600 MHz" → 145.6; fmtFreq → "145.60000" (5 decimals). function parseMHz(v) { if (v == null) return NaN; if (typeof v === 'number') return v; const s = String(v).replace(',', '.').trim(); const m = s.match(/(\d+(?:\.\d+)?)/); return m ? Number(m[1]) : NaN; } function fmtFreq(v) { if (v == null) return ''; if (typeof v === 'number') { if (!Number.isFinite(v)) return ''; return v.toFixed(5); } const s = String(v).trim(); if (!s) return ''; const mhz = parseMHz(s); if (Number.isFinite(mhz) && mhz > 0) { return mhz.toFixed(5); } return s; } window.escHtml = escHtml; window.fmt2 = fmt2; window.fmtAge = fmtAge; window.fmtDur = fmtDur; window.fmtTs = fmtTs; window.baseCallsign = baseCallsign; window.callsignCompare = callsignCompare; window.parseMHz = parseMHz; window.fmtFreq = fmtFreq; })();