Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"HashColor": "readonly",
"HopDisplay": "readonly",
"HopResolver": "readonly",
"I18n": "readonly",
"IATA_CITIES": "readonly",
"IATA_COORDS_GEO": "readonly",
"L": "readonly",
Expand Down Expand Up @@ -283,6 +284,8 @@
"syncBadgeColors": "readonly",
"timeAgo": "readonly",
"toggleFavorite": "readonly",
"tr": "readonly",
"trn": "readonly",
"transportBadge": "readonly",
"truncate": "readonly",
"viewGeneration": "readonly",
Expand Down
1,219 changes: 619 additions & 600 deletions public/analytics.js

Large diffs are not rendered by default.

34 changes: 25 additions & 9 deletions public/app.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
/* === CoreScope — app.js === */
'use strict';

// --- UI translations ---
// i18n.js loads before this file in the browser and defines tr()/trn().
// Sandboxes that load app.js on its own get English with {placeholders} filled.
if (typeof globalThis.tr !== 'function') {
globalThis.tr = function (s, p) {
return p ? s.replace(/\{(\w+)\}/g, (m, k) => (Object.prototype.hasOwnProperty.call(p, k) ? String(p[k]) : m)) : s;
};
globalThis.trn = function (n, one, other, p) { return globalThis.tr(n === 1 ? one : other, Object.assign({ count: n }, p)); };
}

// --- Route/Payload name maps ---
const ROUTE_TYPES = { 0: 'TRANSPORT_FLOOD', 1: 'FLOOD', 2: 'DIRECT', 3: 'TRANSPORT_DIRECT' };
const PAYLOAD_TYPES = { 0: 'Request', 1: 'Response', 2: 'Direct Msg', 3: 'ACK', 4: 'Advert', 5: 'Channel Msg', 6: 'Group Data', 7: 'Anon Req', 8: 'Path', 9: 'Trace', 10: 'Multipart', 11: 'Control', 15: 'Raw Custom' };
Expand Down Expand Up @@ -341,8 +351,8 @@ function timeAgo(iso) {
else if (abs < 3600) { value = Math.floor(abs / 60); suffix = 'm'; }
else if (abs < 86400) { value = Math.floor(abs / 3600); suffix = 'h'; }
else { value = Math.floor(abs / 86400); suffix = 'd'; }
if (s < 0) return 'in ' + value + suffix;
return value + suffix + ' ago';
if (s < 0) return tr('in {time}', { time: value + suffix });
return tr('{time} ago', { time: value + suffix });
}

function getHashParams() {
Expand Down Expand Up @@ -1153,10 +1163,10 @@ registerPage('tools-landing', {
init: function (container) {
container.innerHTML =
'<div class="tools-landing">' +
'<h2>Tools</h2>' +
'<h2>' + tr('Tools') + '</h2>' +
'<div class="tools-menu">' +
'<a href="#/tools/path-inspector" class="tools-card"><h3><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor-sprite.svg#ph-magnifying-glass"/></svg> Path Inspector</h3><p>Resolve prefix paths to candidate full-pubkey routes with confidence scoring.</p></a>' +
'<a href="#/tools/trace/" class="tools-card"><h3><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor-sprite.svg#ph-broadcast"/></svg> Trace Viewer</h3><p>View detailed packet traces by hash.</p></a>' +
'<a href="#/tools/path-inspector" class="tools-card"><h3><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor-sprite.svg#ph-magnifying-glass"/></svg> ' + tr('Path Inspector') + '</h3><p>' + tr('Resolve prefix paths to candidate full-pubkey routes with confidence scoring.') + '</p></a>' +
'<a href="#/tools/trace/" class="tools-card"><h3><svg class="ph-icon" aria-hidden="true"><use href="/icons/phosphor-sprite.svg#ph-broadcast"/></svg> ' + tr('Trace Viewer') + '</h3><p>' + tr('View detailed packet traces by hash.') + '</p></a>' +
'</div>' +
'</div>';
},
Expand Down Expand Up @@ -1317,7 +1327,7 @@ function navigate() {
else { app.setAttribute('tabindex', '-1'); app.focus({ preventScroll: true }); }
});
} else {
app.innerHTML = `<div style="padding:40px;text-align:center;color:#6b7280"><h2>${escapeHtml(route)}</h2><p>Page not yet implemented.</p></div>`;
app.innerHTML = `<div style="padding:40px;text-align:center;color:#6b7280"><h2>${escapeHtml(route)}</h2><p>${tr('Page not yet implemented.')}</p></div>`;
}
}

Expand Down Expand Up @@ -1971,9 +1981,15 @@ window.addEventListener('DOMContentLoaded', () => {
if (window._customizerV2) window._customizerV2.init(window.SITE_CONFIG);
});

// Navigate immediately — don't gate data-fetching pages on cosmetic theme fetch
if (!location.hash || location.hash === '#/') location.hash = '#/home';
else navigate();
// Navigate immediately — don't gate data-fetching pages on cosmetic theme fetch.
// The UI language does gate the first render (i18n.js resolves at once for
// English and gives up after a few seconds), so pages render translated.
const startRouting = () => {
if (!location.hash || location.hash === '#/') location.hash = '#/home';
else navigate();
};
if (window.I18n && window.I18n.ready) window.I18n.ready.then(startRouting);
else startRouting();
});

/**
Expand Down
12 changes: 7 additions & 5 deletions public/area-filter.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
/* === CoreScope — area-filter.js (single-select area filter component) === */
'use strict';
// i18n.js defines tr()/trn(); sandboxes that load this file alone get English.
var tr = globalThis.tr || function (s, p) { return p ? s.replace(/\{(\w+)\}/g, function (m, k) { return Object.prototype.hasOwnProperty.call(p, k) ? String(p[k]) : m; }) : s; };

(function () {
var LS_KEY = 'meshcore-area-filter';
Expand Down Expand Up @@ -48,9 +50,9 @@
function areaQueryString() { return _selected ? '&area=' + encodeURIComponent(_selected) : ''; }

function triggerLabel() {
if (!_selected) return 'Area: All ▾';
if (!_selected) return tr('Area: {area}', { area: tr('All') }) + ' ▾';
var area = _areas.find(function (a) { return a.key === _selected; });
return 'Area: ' + (area ? area.label : _selected) + ' ▾';
return tr('Area: {area}', { area: area ? area.label : _selected }) + ' ▾';
}

function render(container) {
Expand All @@ -63,12 +65,12 @@

if (container._areaCleanup) { container._areaCleanup(); container._areaCleanup = null; }

var html = '<div class="region-dropdown-wrap" role="group" aria-label="Area filter">';
var html = '<div class="region-dropdown-wrap" role="group" aria-label="' + tr('Area filter') + '">';
html += '<button class="region-dropdown-trigger" aria-haspopup="listbox" aria-expanded="false">' +
triggerLabel() + '</button>';
html += '<div class="region-dropdown-menu area-dropdown-menu" role="listbox" aria-label="Select area" hidden>';
html += '<div class="region-dropdown-menu area-dropdown-menu" role="listbox" aria-label="' + tr('Select area') + '" hidden>';
html += '<button class="region-dropdown-item area-dropdown-item' + (!_selected ? ' area-item-active' : '') +
'" data-area="__all__">All</button>';
'" data-area="__all__">' + tr('All') + '</button>';
_areas.forEach(function (a) {
var active = _selected === a.key;
html += '<button class="region-dropdown-item area-dropdown-item' + (active ? ' area-item-active' : '') +
Expand Down
Loading