feat(watchlist): exact match by default, * for a family
The prefix matching ported from DXHunter swallowed too much: a three-letter special-event entry N8W lit up NEEDED for N8WCR, a different station entirely. The implicit becomes explicit — a bare entry matches exactly that call, a trailing star makes it a family: VK9* catches every VK9…, RI0SP* the expedition's portable forms. A star anywhere else (or alone) is refused at Add rather than silently matching nothing. Same rule in the backend Match and the tab's own matcher, pinned by test.
This commit is contained in:
@@ -43,9 +43,16 @@ interface Props {
|
||||
// A spot is ON AIR for the badge while its last sighting is this fresh.
|
||||
const ON_AIR_MS = 10 * 60 * 1000;
|
||||
|
||||
// Exact unless the entry carries a trailing * — the same rule the backend's
|
||||
// Match applies to the live stream, mirrored so the tab and the alerts can
|
||||
// never disagree about what an entry covers.
|
||||
function matchesEntry(call: string, pattern: string): boolean {
|
||||
const c = call.toUpperCase();
|
||||
return c === pattern || c.startsWith(pattern);
|
||||
if (pattern.endsWith('*')) {
|
||||
const p = pattern.slice(0, -1);
|
||||
return p !== '' && c.startsWith(p);
|
||||
}
|
||||
return c === pattern;
|
||||
}
|
||||
|
||||
export function WatchlistTab({ spots, spotStatus, onSpotSelect, onSpotClick }: Props) {
|
||||
|
||||
@@ -39,11 +39,11 @@ const en: Dict = {
|
||||
'cwd.tipOnIdle': 'CW decoder — on, idle until CW mode · click to disable',
|
||||
'cwd.tipOff': 'CW decoder · click to enable (decodes RX audio in CW mode)',
|
||||
'tools.watchlist': 'Watchlist…', 'tab.watchlist': 'Watchlist',
|
||||
'wl.addPh': 'Callsign or prefix', 'wl.add': 'Add', 'wl.contest': 'Contest', 'wl.addAsContest': 'as contest', 'wl.cTotal': 'Watchlist:', 'wl.cActive': 'Active:', 'wl.cNeeded': 'Needed:', 'wl.allModes': 'All modes', 'wl.modeFilter': 'Only show spots in this mode', 'wl.patternPh': 'Auto contest (e.g. WWA)', 'wl.patternHint': 'Auto-add as contest: any spotted callsign CONTAINING this text joins the watchlist as a contest entry by itself. Empty = off; collected entries stay.',
|
||||
'wl.addPh': 'Callsign or VK9*', 'wl.add': 'Add', 'wl.contest': 'Contest', 'wl.addAsContest': 'as contest', 'wl.cTotal': 'Watchlist:', 'wl.cActive': 'Active:', 'wl.cNeeded': 'Needed:', 'wl.allModes': 'All modes', 'wl.modeFilter': 'Only show spots in this mode', 'wl.patternPh': 'Auto contest (e.g. WWA)', 'wl.patternHint': 'Auto-add as contest: any spotted callsign CONTAINING this text joins the watchlist as a contest entry by itself. Empty = off; collected entries stay.',
|
||||
'wl.contestHint': 'Contest station: worked/needed is judged against the current UTC day — at 00:00 UTC every slot can be worked again.',
|
||||
'wl.searchPh': 'Search…', 'wl.famAll': 'All', 'wl.famNormal': 'DX', 'wl.famContest': 'Contest',
|
||||
'wl.activeOnly': 'Active', 'wl.neededOnly': 'Needed',
|
||||
'wl.empty': 'Add the callsigns or prefixes you are hunting — a prefix catches the portable forms too (RI0SP matches RI0SP/MM). Spots from the cluster appear under each entry with what they are worth.',
|
||||
'wl.empty': 'Add the callsigns you are hunting. A bare entry matches exactly that call; end it with * for a family — VK9* catches every VK9…, RI0SP* the expedition\u2019s portable forms. Spots from the cluster appear under each entry with what they are worth.',
|
||||
'wl.noneMatch': 'Nothing matches the current filters.',
|
||||
'wl.onAir': 'ON AIR', 'wl.expedition': 'DXpedition',
|
||||
'wl.nNeeded': '{n} needed', 'wl.nToday': '{n} today', 'wl.allWorked': 'All worked', 'wl.workedToday': 'Worked today',
|
||||
@@ -563,11 +563,11 @@ const fr: Dict = {
|
||||
'cwd.tipOnIdle': 'Décodeur CW — actif, en veille hors mode CW · clic pour désactiver',
|
||||
'cwd.tipOff': 'Décodeur CW · clic pour activer (décode l’audio RX en mode CW)',
|
||||
'tools.watchlist': 'Watchlist…', 'tab.watchlist': 'Watchlist',
|
||||
'wl.addPh': 'Indicatif ou préfixe', 'wl.add': 'Ajouter', 'wl.contest': 'Contest', 'wl.addAsContest': 'comme contest', 'wl.cTotal': 'Watchlist :', 'wl.cActive': 'Actives :', 'wl.cNeeded': 'Manquantes :', 'wl.allModes': 'Tous les modes', 'wl.modeFilter': 'Ne montrer que les spots de ce mode', 'wl.patternPh': 'Auto contest (ex. WWA)', 'wl.patternHint': "Ajout auto comme contest : tout indicatif spotté CONTENANT ce texte rejoint la watchlist en entrée contest tout seul. Vide = désactivé ; les entrées déjà collectées restent.",
|
||||
'wl.addPh': 'Indicatif ou VK9*', 'wl.add': 'Ajouter', 'wl.contest': 'Contest', 'wl.addAsContest': 'comme contest', 'wl.cTotal': 'Watchlist :', 'wl.cActive': 'Actives :', 'wl.cNeeded': 'Manquantes :', 'wl.allModes': 'Tous les modes', 'wl.modeFilter': 'Ne montrer que les spots de ce mode', 'wl.patternPh': 'Auto contest (ex. WWA)', 'wl.patternHint': "Ajout auto comme contest : tout indicatif spotté CONTENANT ce texte rejoint la watchlist en entrée contest tout seul. Vide = désactivé ; les entrées déjà collectées restent.",
|
||||
'wl.contestHint': "Station contest : contacté/manquant est jugé sur la journée UTC courante — à 00:00 UTC chaque créneau redevient à faire.",
|
||||
'wl.searchPh': 'Chercher…', 'wl.famAll': 'Tous', 'wl.famNormal': 'DX', 'wl.famContest': 'Contest',
|
||||
'wl.activeOnly': 'Actifs', 'wl.neededOnly': 'Manquants',
|
||||
'wl.empty': "Ajoutez les indicatifs ou préfixes que vous chassez — un préfixe attrape aussi les formes portables (RI0SP matche RI0SP/MM). Les spots du cluster apparaissent sous chaque entrée avec ce qu'ils valent.",
|
||||
'wl.empty': "Ajoutez les indicatifs que vous chassez. Une entrée nue matche exactement ce call ; terminez par * pour une famille — VK9* attrape tous les VK9…, RI0SP* les formes portables de l'expédition. Les spots du cluster apparaissent sous chaque entrée avec ce qu'ils valent.",
|
||||
'wl.noneMatch': 'Rien ne correspond aux filtres actuels.',
|
||||
'wl.onAir': 'ON AIR', 'wl.expedition': 'DXpédition',
|
||||
'wl.nNeeded': '{n} manquants', 'wl.nToday': "{n} aujourd'hui", 'wl.allWorked': 'Tout contacté', 'wl.workedToday': "Contacté aujourd'hui",
|
||||
|
||||
Reference in New Issue
Block a user