Files
reflector-dashboard/lib/fmt.js
T
bjorneandClaude Fable 5.1 25b481e2ae Public web-only release: SvxPortal front-end + auth backend, install templates and INSTALL.md
Stripped from the internal repo: per-box deploy trees, Go services (nodelist/astmeta/proxy/svxdr),
deploy/sync scripts and all secrets. Config templates for nginx, PHP-FPM, Redis and tmpfiles
live in install/ with CHANGEME placeholders.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-26 23:07:05 +02:00

96 lines
3.3 KiB
JavaScript

// fmt.js — shared formatting helpers for every web (dmr-web, svx-web, svxlink-web).
// Canonical copy: callmgr2/www-mumien/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 => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[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;
})();