feat(ui): enhance vessel details in map popups (#1094) (#1457)

* feat(popups): enhance vessel details with flags, USNI intel, and tracking history

- Add flag emojis for operator countries in vessel and cluster popups
- Add collapsible USNI intel section (strike group, region, description)
- Add collapsible tracking history trail from vessel.track
- Display nearChokepoint, nearBase, lastSeen with aisGapMinutes
- Restructure header with hull badge and badge row
- Fix hemisphere bug: use N/S and E/W based on coordinate sign
- Fix lastAisUpdate null check to prevent "Invalid Date"
- Deduplicate cluster vessel rendering into shared helper
- Add i18n keys (recentTracking, lastReport, nearChokepoint,
  nearBase, lastSeen) across all 21 locale files

Co-authored-by: lspassos1 <lspassos@icloud.com>

* fix(popups): correct track coord order, fix CSS variables, remove dead styles

- Fix coordinate swap: track stores [lat, lon], was passing (lon, lat)
  to formatCoord, now correctly passes (lat, lon)
- Replace undefined --semantic-high-rgb CSS variable with literal
  rgba(255, 136, 0, ...) values matching --semantic-high: #ff8800
- Remove dead CSS classes (.popup-badge.usni-deployed/underway/in-port)
  that were never applied in HTML
- Add missing CSS for .popup-stat.warning (orange highlight for
  nearChokepoint) and .popup-stat.full-width (span full grid row)

* fix(security): use sanitizeUrl for usniArticleUrl hrefs to block javascript: XSS

---------

Co-authored-by: Elie Habib <elie.habib@gmail.com>
This commit is contained in:
Lucas Passos
2026-03-19 07:53:23 +00:00
committed by GitHub
parent 2deccac691
commit f2b84ac4c7

View File

@@ -2389,9 +2389,9 @@ ${isFeatureAvailable('wingbitsEnrichment') ? '<div class="wingbits-live-section"
${vessel.usniStrikeGroup ? `<div class="usni-field"><strong>${t('popups.militaryVessel.strikeGroup')}:</strong> ${escapeHtml(vessel.usniStrikeGroup)}</div>` : ''}
${vessel.usniRegion ? `<div class="usni-field"><strong>${t('popups.militaryVessel.region')}:</strong> ${escapeHtml(vessel.usniRegion)}</div>` : ''}
${vessel.usniActivityDescription ? `<p class="usni-description">${escapeHtml(vessel.usniActivityDescription)}</p>` : ''}
${vessel.usniArticleUrl ? `
${vessel.usniArticleUrl && sanitizeUrl(vessel.usniArticleUrl) ? `
<div class="usni-source-row">
<a href="${escapeHtml(vessel.usniArticleUrl)}" target="_blank" rel="noopener noreferrer" class="usni-link">
<a href="${sanitizeUrl(vessel.usniArticleUrl)}" target="_blank" rel="noopener noreferrer" class="usni-link">
${t('popups.militaryVessel.usniSource')} ${vessel.usniArticleDate ? `(${new Date(vessel.usniArticleDate).toLocaleDateString()})` : ''}
</a>
</div>
@@ -2462,7 +2462,7 @@ ${isFeatureAvailable('wingbitsEnrichment') ? '<div class="wingbits-live-section"
${vessel.note ? `<p class="popup-description">${vesselNote}</p>` : ''}
${vessel.isDark ? `<p class="popup-description alert">${t('popups.militaryVessel.darkDescription')}</p>` : ''}
${vessel.usniSource ? `<p class="popup-description" style="opacity:0.7;font-size:0.85em">${t('popups.militaryVessel.approximatePosition')}</p>` : ''}
${vessel.usniArticleUrl && !usniIntel ? `<div class="popup-attribution"><a href="${escapeHtml(vessel.usniArticleUrl)}" target="_blank" rel="noopener noreferrer">${t('popups.militaryVessel.usniSource')}${vessel.usniArticleDate ? ` (${new Date(vessel.usniArticleDate).toLocaleDateString()})` : ''}</a></div>` : ''}
${vessel.usniArticleUrl && !usniIntel && sanitizeUrl(vessel.usniArticleUrl) ? `<div class="popup-attribution"><a href="${sanitizeUrl(vessel.usniArticleUrl)}" target="_blank" rel="noopener noreferrer">${t('popups.militaryVessel.usniSource')}${vessel.usniArticleDate ? ` (${new Date(vessel.usniArticleDate).toLocaleDateString()})` : ''}</a></div>` : ''}
</div>
`;
}