refactor: extract typed frontend core utilities

This commit is contained in:
sjg
2026-08-01 13:48:51 +02:00
parent 50e07e2715
commit e910e644d5
4 changed files with 67 additions and 47 deletions
@@ -50,6 +50,28 @@
return `${field1}${field2}${square1}${square2}${sub1}${sub2}`;
}
// src/core/dom.ts
function escapeHtml(input) {
return String(input).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
}
// src/core/settings.ts
var STORAGE_PREFIX2 = "trx_";
function saveSetting(key, value) {
try {
localStorage.setItem(`${STORAGE_PREFIX2}${key}`, JSON.stringify(value));
} catch {
}
}
function loadSetting(key, fallback) {
try {
const value = localStorage.getItem(`${STORAGE_PREFIX2}${key}`);
return value === null ? fallback : JSON.parse(value);
} catch {
return fallback;
}
}
// src/app.js
var decoderRegistry = [];
window.decoderRegistry = decoderRegistry;
@@ -105,24 +127,6 @@
}
});
}
var STORAGE_PREFIX = "trx_";
function saveSetting(key, value) {
try {
localStorage.setItem(STORAGE_PREFIX + key, JSON.stringify(value));
} catch (e) {
}
}
function loadSetting(key, fallback) {
try {
const v = localStorage.getItem(STORAGE_PREFIX + key);
return v !== null ? JSON.parse(v) : fallback;
} catch (e) {
return fallback;
}
}
function escapeMapHtml(input) {
return String(input).replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
}
var authRole = null;
var authEnabled = true;
async function checkAuthStatus() {
@@ -1891,7 +1895,7 @@
const mainClass = hasPs ? "rds-ps-main" : "rds-ps-fallback";
const metaText = hasPs ? `${formatOverlayPi(rds?.pi)} · ${formatOverlayPty(rds?.pty, rds?.pty_name)}` : rds?.pty_name ?? (rds?.pty != null ? String(rds.pty) : "");
const trafficFlags = `<span class="rds-ps-flags">${overlayTrafficFlagHtml("TP", rds?.traffic_program)}${overlayTrafficFlagHtml("TA", rds?.traffic_announcement)}</span>`;
return `<span class="${mainClass}">${hasPs ? formatPsHtml(ps) : escapeMapHtml(mainText)}</span><span class="rds-ps-meta"><span class="rds-ps-meta-text">${escapeMapHtml(metaText)}</span>${trafficFlags}</span>`;
return `<span class="${mainClass}">${hasPs ? formatPsHtml(ps) : escapeHtml(mainText)}</span><span class="rds-ps-meta"><span class="rds-ps-meta-text">${escapeHtml(metaText)}</span>${trafficFlags}</span>`;
}
function collectRdsOverlayEntries() {
const entries = [];
@@ -2901,7 +2905,7 @@ ${unsupportedBandSummary()}`;
if (titleEl) {
if (ownerWebsiteUrl) {
const label = ownerWebsiteName || displayLabelFromUrl(ownerWebsiteUrl);
titleEl.innerHTML = `<a class="title-link" href="${escapeMapHtml(ownerWebsiteUrl)}" target="_blank" rel="noopener">${escapeMapHtml(label)}</a>`;
titleEl.innerHTML = `<a class="title-link" href="${escapeHtml(ownerWebsiteUrl)}" target="_blank" rel="noopener">${escapeHtml(label)}</a>`;
} else {
titleEl.textContent = serverVersion ? `trx-rs v${serverVersion}` : "trx-rs";
}
@@ -2994,14 +2998,14 @@ ${unsupportedBandSummary()}`;
}
if (serverSubtitle && update.server_callsign) {
const base = serverSubtitle.textContent.split(" hosted by")[0];
const safeCallsign = escapeMapHtml(update.server_callsign);
const safeCallsign = escapeHtml(update.server_callsign);
const encodedCallsign = encodeURIComponent(update.server_callsign);
serverSubtitle.innerHTML = `${escapeMapHtml(base)} hosted by <a href="https://qrzcq.com/call/${encodedCallsign}" target="_blank" rel="noopener">${safeCallsign}</a>`;
serverSubtitle.innerHTML = `${escapeHtml(base)} hosted by <a href="https://qrzcq.com/call/${encodedCallsign}" target="_blank" rel="noopener">${safeCallsign}</a>`;
}
updateRigSubtitle(lastActiveRigId);
if (ownerSubtitle) {
if (ownerCallsign) {
const safeOwner = escapeMapHtml(ownerCallsign);
const safeOwner = escapeHtml(ownerCallsign);
const encodedOwner = encodeURIComponent(ownerCallsign);
ownerSubtitle.innerHTML = `Owner: <a href="https://qrzcq.com/call/${encodedOwner}" target="_blank" rel="noopener">${safeOwner}</a>`;
} else {
@@ -4549,7 +4553,7 @@ ${unsupportedBandSummary()}`;
saveSetting,
loadSetting,
showHint,
escapeMapHtml,
escapeMapHtml: escapeHtml,
formatFreq,
formatFreqForHumans,
formatWavelength,
@@ -5591,7 +5595,7 @@ ${unsupportedBandSummary()}`;
for (const r of list) {
const started = new Date(r.started_at * 1e3).toLocaleTimeString();
const fname = r.path.split("/").pop();
html += `<tr><td>${escapeMapHtml(r.rig_id)}</td><td>${r.vchan_id ? escapeMapHtml(r.vchan_id) : "-"}</td><td>${escapeMapHtml(fname)}</td><td>${started}</td></tr>`;
html += `<tr><td>${escapeHtml(r.rig_id)}</td><td>${r.vchan_id ? escapeHtml(r.vchan_id) : "-"}</td><td>${escapeHtml(fname)}</td><td>${started}</td></tr>`;
}
html += "</tbody></table>";
el.innerHTML = html;
@@ -5665,7 +5669,7 @@ ${unsupportedBandSummary()}`;
}
let html = '<table class="recorder-table"><thead><tr><th>File</th><th>Size</th><th>Actions</th></tr></thead><tbody>';
for (const f of page) {
const safeName = escapeMapHtml(f.name);
const safeName = escapeHtml(f.name);
const encodedName = encodeURIComponent(f.name);
html += '<tr data-name="' + safeName + '"><td>' + safeName + "</td><td>" + recorderFormatSize(f.size) + '</td><td><div class="rec-file-actions"><button class="rec-file-btn rec-play-btn" data-name="' + safeName + '" data-url="/api/recorder/download/' + encodedName + '" type="button" aria-expanded="false">Play</button><a class="rec-file-btn" href="/api/recorder/download/' + encodedName + '" download="' + safeName + '">Download</a><button class="rec-file-btn rec-delete-btn" data-name="' + safeName + '" type="button">Remove</button></div></td></tr>';
}
@@ -6643,7 +6647,7 @@ ${unsupportedBandSummary()}`;
if (ch == null || ch === " ") {
html += `<span class="rds-ps-gap">_</span>`;
} else {
html += escapeMapHtml(ch);
html += escapeHtml(ch);
}
}
return html;
@@ -7165,7 +7169,7 @@ ${unsupportedBandSummary()}`;
}
span.title = buildBookmarkTooltipText(bm) || bm.name + " — " + freqStr + (bm.comment ? "\n" + bm.comment : "");
span.dataset.bmId = bm.id;
const labelHtml = options.sideStack ? `<span class="spectrum-bookmark-side-head"><svg class='bm-icon-svg' viewBox='0 0 8 12' width='8' height='12' aria-hidden='true'><path d='M0,0 h8 v10 l-4,2 l-4,-2 Z'/></svg><span class="spectrum-bookmark-freq">${escapeMapHtml(freqStr)}</span></span><span class="spectrum-bookmark-name">${escapeMapHtml(bm.name)}</span>` : "<svg class='bm-icon-svg' viewBox='0 0 8 12' width='8' height='12' aria-hidden='true'><path d='M0,0 h8 v10 l-4,2 l-4,-2 Z'/></svg> <span class='spectrum-bookmark-name'>" + escapeMapHtml(bm.name) + "</span>";
const labelHtml = options.sideStack ? `<span class="spectrum-bookmark-side-head"><svg class='bm-icon-svg' viewBox='0 0 8 12' width='8' height='12' aria-hidden='true'><path d='M0,0 h8 v10 l-4,2 l-4,-2 Z'/></svg><span class="spectrum-bookmark-freq">${escapeHtml(freqStr)}</span></span><span class="spectrum-bookmark-name">${escapeHtml(bm.name)}</span>` : "<svg class='bm-icon-svg' viewBox='0 0 8 12' width='8' height='12' aria-hidden='true'><path d='M0,0 h8 v10 l-4,2 l-4,-2 Z'/></svg> <span class='spectrum-bookmark-name'>" + escapeHtml(bm.name) + "</span>";
span.innerHTML = labelHtml;
const col = colorMap[bm.category || ""];
span.style.setProperty("--bm-cat-bg", col);