Make dashboard alerts short with click-to-expand full details
Publish Container / publish (push) Successful in 2m50s
Publish Container / publish (push) Successful in 2m50s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
fc93079109
commit
528dec753d
2 files changed
+38
-8
No files matched your search
@@ -166,6 +166,16 @@ a { color: var(--cool); text-decoration: none; }
|
||||
.alert-card.sev-unknown { background: var(--paper-dark); border-color: var(--border); }
|
||||
.alert-title { font-weight: 700; margin-bottom: 0.15rem; }
|
||||
.alert-meta { font-size: 0.75rem; color: var(--ink-light); }
|
||||
.alert-card summary { cursor: pointer; list-style: none; }
|
||||
.alert-card summary::-webkit-details-marker { display: none; }
|
||||
.alert-summary { margin-top: 0.2rem; line-height: 1.35; }
|
||||
.alert-hint { font-size: 0.72rem; color: var(--ink-light); margin-top: 0.2rem; }
|
||||
.alert-card details[open] .alert-hint { display: none; }
|
||||
.alert-full { margin-top: 0.5rem; white-space: pre-wrap; line-height: 1.45; font-size: 0.82rem; }
|
||||
.alert-full h4 { margin: 0.6rem 0 0.15rem; font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-light); }
|
||||
.ah-row { cursor: pointer; }
|
||||
.ah-row .ah-body { display: -webkit-box; -webkit-line-clamp: 1; -webkit-box-orient: vertical; overflow: hidden; }
|
||||
.ah-row.open .ah-body { display: block; white-space: pre-wrap; }
|
||||
/* Trend arrows */
|
||||
.stat-trend { font-size: 0.85rem; margin-left: 0.25rem; }
|
||||
.trend-up { color: #e07848; }
|
||||
@@ -1326,6 +1336,7 @@ function updateTodayBar(readings) {
|
||||
}
|
||||
|
||||
// ── NWS Alerts ────────────────────────────────────────────────────────────────
|
||||
const esc = s => String(s ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
const SEV_CLASS = { Extreme: 'sev-extreme', Severe: 'sev-severe', Moderate: 'sev-moderate', Minor: 'sev-minor' };
|
||||
async function loadAlerts() {
|
||||
const section = document.getElementById('alertsSection');
|
||||
@@ -1347,10 +1358,19 @@ async function loadAlerts() {
|
||||
const sev = p.severity || 'unknown';
|
||||
const cls = SEV_CLASS[sev] || 'sev-unknown';
|
||||
const ends = p.ends ? new Date(p.ends).toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' }) : '';
|
||||
return `<div class="alert-card ${cls}">
|
||||
<div class="alert-title">${p.event || 'Alert'}</div>
|
||||
<div class="alert-meta">${p.areaDesc || ''}${ends ? ' · until ' + ends : ''}</div>
|
||||
</div>`;
|
||||
const desc = (p.description || '').trim();
|
||||
const firstSentence = (p.headline && !desc) ? p.headline : (desc.replace(/\s+/g, ' ').match(/^.*?[.!?](\s|$)/) || [desc.replace(/\s+/g, ' ')])[0].trim();
|
||||
const short = firstSentence.length > 140 ? firstSentence.slice(0, 137) + '…' : firstSentence;
|
||||
const instr = (p.instruction || '').trim();
|
||||
return `<div class="alert-card ${cls}"><details>
|
||||
<summary>
|
||||
<div class="alert-title">${esc(p.event || 'Alert')}</div>
|
||||
<div class="alert-meta">${esc(p.areaDesc || '')}${ends ? ' · until ' + ends : ''}</div>
|
||||
${short ? `<div class="alert-summary">${esc(short)}</div>` : ''}
|
||||
<div class="alert-hint">Click for full details ▾</div>
|
||||
</summary>
|
||||
<div class="alert-full">${esc(desc || p.headline || 'No further details.')}${instr ? `<h4>What to do</h4>${esc(instr)}` : ''}</div>
|
||||
</details></div>`;
|
||||
}).join('');
|
||||
} catch {}
|
||||
}
|
||||
@@ -1410,16 +1430,26 @@ async function loadAlertHistory(days) {
|
||||
const badge = `<span class="ah-badge ${isNws ? 'nws' : 'threshold'}">${isNws ? 'NWS' : 'Threshold'}</span>`;
|
||||
const label = isNws ? (it.event || 'Alert') : (TAG_LABELS[it.tag] || it.tag || '—');
|
||||
const detail = isNws ? (it.headline || '') : (it.body || '');
|
||||
const full = isNws ? [it.headline, it.description, it.instruction && ('What to do: ' + it.instruction)].filter(Boolean).join('\n\n') : detail;
|
||||
const time = new Date(it.ts).toLocaleString([], { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
|
||||
return `<tr>
|
||||
return `<tr class="ah-row" data-full="${esc(full)}" title="Click to expand">
|
||||
<td style="white-space:nowrap;color:var(--ink-light);font-size:0.78rem">${time}</td>
|
||||
<td>${badge}</td>
|
||||
<td style="font-weight:600">${label}</td>
|
||||
<td><div class="ah-body">${detail}</div></td>
|
||||
<td><div class="ah-body">${esc(detail)}</div></td>
|
||||
</tr>`;
|
||||
}).join('')}</tbody>
|
||||
</table></div>`
|
||||
}`;
|
||||
body.querySelectorAll('.ah-row').forEach(row => {
|
||||
row.addEventListener('click', () => {
|
||||
const el = row.querySelector('.ah-body');
|
||||
const open = row.classList.toggle('open');
|
||||
el.textContent = open ? row.dataset.full : el.dataset.short;
|
||||
});
|
||||
const el = row.querySelector('.ah-body');
|
||||
el.dataset.short = el.textContent;
|
||||
});
|
||||
body.querySelectorAll('[data-ahdays]').forEach(btn => {
|
||||
btn.addEventListener('click', () => loadAlertHistory(parseInt(btn.dataset.ahdays)));
|
||||
});
|
||||
|
||||
+2
-2
@@ -827,7 +827,7 @@ async function alertWatcherCron() {
|
||||
if (alertId && !loggedAlertIds.has(alertId)) {
|
||||
loggedAlertIds.add(alertId);
|
||||
const p = a.properties || {};
|
||||
recordAlertLog({ type: 'nws', alertId, event: p.event, headline: p.headline, effective: p.effective, expires: p.expires, lat: dashLat, lon: dashLon });
|
||||
recordAlertLog({ type: 'nws', alertId, event: p.event, headline: p.headline, description: p.description, instruction: p.instruction, effective: p.effective, expires: p.expires, lat: dashLat, lon: dashLon });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -853,7 +853,7 @@ async function alertWatcherCron() {
|
||||
const alertId = a.id || p.id;
|
||||
if (alertId && !loggedAlertIds.has(alertId)) {
|
||||
loggedAlertIds.add(alertId);
|
||||
recordAlertLog({ type: 'nws', alertId, event: p.event, headline: p.headline, effective: p.effective, expires: p.expires, lat: sub.lat, lon: sub.lon });
|
||||
recordAlertLog({ type: 'nws', alertId, event: p.event, headline: p.headline, description: p.description, instruction: p.instruction, effective: p.effective, expires: p.expires, lat: sub.lat, lon: sub.lon });
|
||||
}
|
||||
}
|
||||
sub.seenAlertIds = [...new Set([...sub.seenAlertIds, ...newAlerts.map(f => f.id || f.properties?.id)])].slice(-200);
|
||||
|
||||
Reference in new issue
Block a user