- Lagebild-Dashboard mit Snapshot-Daten hinzugefuegt - Impressum und Datenschutz (DE/EN) aktualisiert - Uebersetzungen in translations.js erweitert - Video-Dateien als LFS-Objekte aktualisiert Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
455 Zeilen
23 KiB
JavaScript
455 Zeilen
23 KiB
JavaScript
/**
|
|
* AegisSight Lagebild Page v3
|
|
* Vollständige Darstellung aller Daten mit Tabs
|
|
*/
|
|
var Lagebild = {
|
|
data: null,
|
|
allSnapshots: {},
|
|
currentView: null,
|
|
map: null,
|
|
|
|
async init() {
|
|
if (typeof initTranslations === 'function') {
|
|
try { initTranslations(); } catch(e) {}
|
|
}
|
|
try {
|
|
var resp = await fetch('data/current.json?t=' + Date.now());
|
|
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
|
this.data = await resp.json();
|
|
this.currentView = {
|
|
summary: this.data.current_lagebild.summary_markdown,
|
|
sources_json: this.data.current_lagebild.sources_json,
|
|
updated_at: this.data.current_lagebild.updated_at || this.data.generated_at,
|
|
articles: this.data.articles,
|
|
fact_checks: this.data.fact_checks
|
|
};
|
|
this.render();
|
|
this.initTabs();
|
|
this.initLangToggle();
|
|
} catch (e) {
|
|
console.error('Lagebild laden fehlgeschlagen:', e);
|
|
this.showError();
|
|
}
|
|
},
|
|
|
|
render: function() {
|
|
this.renderHero();
|
|
this.renderSnapshotSelector();
|
|
this.renderCurrentView();
|
|
},
|
|
|
|
renderHero: function() {
|
|
var d = this.data;
|
|
document.getElementById('incident-title').textContent = this.fixUmlauts(d.incident.title);
|
|
var metaHtml = '';
|
|
metaHtml += this.metaBadge('clock', 'Stand: ' + this.fmtDT(this.data.generated_at));
|
|
metaHtml += this.metaBadge('doc', d.incident.article_count + ' Artikel');
|
|
metaHtml += this.metaBadge('globe', d.incident.source_count + ' Quellen');
|
|
metaHtml += this.metaBadge('check', d.incident.factcheck_count + ' Faktenchecks');
|
|
document.getElementById('hero-meta').innerHTML = metaHtml;
|
|
},
|
|
|
|
metaBadge: function(icon, text) {
|
|
var icons = {
|
|
clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
|
|
doc: '<path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/>',
|
|
globe: '<circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/>',
|
|
check: '<path d="M22 11.08V12a10 10 0 11-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/>'
|
|
};
|
|
return '<div class="meta-badge"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">' + icons[icon] + '</svg><span>' + text + '</span></div>';
|
|
},
|
|
|
|
renderSnapshotSelector: function() {
|
|
var select = document.getElementById('snapshot-select');
|
|
select.innerHTML = '';
|
|
var optC = document.createElement('option');
|
|
optC.value = 'current';
|
|
optC.textContent = this.fmtDT(this.data.current_lagebild.updated_at || this.data.generated_at) + ' — Aktuell';
|
|
optC.selected = true;
|
|
select.appendChild(optC);
|
|
var snaps = this.data.available_snapshots || [];
|
|
for (var i = 0; i < snaps.length; i++) {
|
|
var s = snaps[i];
|
|
var opt = document.createElement('option');
|
|
opt.value = s.id;
|
|
opt.textContent = this.fmtDT(s.created_at) + ' — ' + s.article_count + ' Artikel, ' + s.fact_check_count + ' Faktenchecks';
|
|
select.appendChild(opt);
|
|
}
|
|
var self = this;
|
|
select.addEventListener('change', function() {
|
|
if (this.value === 'current') {
|
|
self.currentView = {
|
|
summary: self.data.current_lagebild.summary_markdown,
|
|
sources_json: self.data.current_lagebild.sources_json,
|
|
updated_at: self.data.current_lagebild.updated_at || self.data.generated_at,
|
|
articles: self.data.articles,
|
|
fact_checks: self.data.fact_checks
|
|
};
|
|
self.renderCurrentView();
|
|
} else {
|
|
self.loadSnapshot(parseInt(this.value));
|
|
}
|
|
});
|
|
},
|
|
|
|
loadSnapshot: async function(id) {
|
|
if (this.allSnapshots[id]) {
|
|
this.currentView = this.allSnapshots[id];
|
|
this.renderCurrentView();
|
|
return;
|
|
}
|
|
try {
|
|
var resp = await fetch('data/snapshot-' + id + '.json');
|
|
if (!resp.ok) throw new Error('HTTP ' + resp.status);
|
|
var sd = await resp.json();
|
|
var sj = sd.sources_json;
|
|
if (typeof sj === 'string') { try { sj = JSON.parse(sj); } catch(e) { sj = []; } }
|
|
this.currentView = {
|
|
summary: sd.summary,
|
|
sources_json: sj || [],
|
|
updated_at: sd.created_at,
|
|
articles: this.data.articles,
|
|
fact_checks: this.data.fact_checks
|
|
};
|
|
this.allSnapshots[id] = this.currentView;
|
|
this.renderCurrentView();
|
|
} catch (e) { console.error('Snapshot Fehler:', e); }
|
|
},
|
|
|
|
renderCurrentView: function() {
|
|
this.renderSummary();
|
|
this.renderInlineSources();
|
|
this.renderSourcesTab();
|
|
this.renderArticlesTab();
|
|
this.renderFactChecksTab();
|
|
if (document.getElementById('panel-karte').classList.contains('active')) {
|
|
this.renderMap();
|
|
}
|
|
},
|
|
|
|
// ===== TAB: LAGEBILD =====
|
|
renderSummary: function() {
|
|
var v = this.currentView;
|
|
document.getElementById('lagebild-timestamp').textContent = this.fmtDT(v.updated_at);
|
|
var md = this.fixUmlauts(v.summary || '');
|
|
var html = this.mdToHtml(md);
|
|
html = html.replace(/\[(\d+)\]/g, '<a class="citation-ref" href="#src-$1" title="Quelle $1">[$1]</a>');
|
|
document.getElementById('summary-content').innerHTML = html;
|
|
},
|
|
|
|
renderInlineSources: function() {
|
|
var sources = this.currentView.sources_json || [];
|
|
if (!sources.length) { document.getElementById('inline-sources').innerHTML = ''; return; }
|
|
var h = '<h3 style="font-size:1rem;font-weight:700;color:#1a1a2e;margin:0 0 12px;">Quellenverzeichnis</h3>';
|
|
h += '<ul class="inline-sources-list">';
|
|
for (var i = 0; i < sources.length; i++) {
|
|
var s = sources[i];
|
|
h += '<li id="src-' + s.nr + '"><span class="src-nr">[' + s.nr + ']</span><span>';
|
|
h += '<strong>' + this.esc(s.name || '') + '</strong>';
|
|
if (s.url) h += ' — <a href="' + this.esc(s.url) + '" target="_blank" rel="noopener">' + this.esc(s.url) + '</a>';
|
|
h += '</span></li>';
|
|
}
|
|
h += '</ul>';
|
|
document.getElementById('inline-sources').innerHTML = h;
|
|
},
|
|
|
|
// ===== TAB: QUELLEN =====
|
|
renderSourcesTab: function() {
|
|
var sources = this.currentView.sources_json || [];
|
|
var articles = this.currentView.articles || [];
|
|
var h = '';
|
|
|
|
// Zitierte Quellen
|
|
h += '<div class="sources-section"><h3>Im Lagebild zitierte Quellen (' + sources.length + ')</h3>';
|
|
if (sources.length) {
|
|
h += '<ol class="sources-list">';
|
|
for (var i = 0; i < sources.length; i++) {
|
|
var s = sources[i];
|
|
h += '<li><span class="source-name">' + this.esc(s.name || '') + '</span>';
|
|
if (s.url) h += ' — <a href="' + this.esc(s.url) + '" target="_blank" rel="noopener">' + this.esc(s.url) + '</a>';
|
|
h += '</li>';
|
|
}
|
|
h += '</ol>';
|
|
}
|
|
h += '</div>';
|
|
document.getElementById('sources-cited').innerHTML = h;
|
|
|
|
// Alle Artikel
|
|
document.getElementById('articles-heading').textContent = 'Alle gesammelten Artikel (' + articles.length + ')';
|
|
var ah = '';
|
|
for (var i = 0; i < articles.length; i++) {
|
|
var a = articles[i];
|
|
var dt = a.published_at || a.collected_at || '';
|
|
var dObj = dt ? new Date(this.toUTC(dt)) : null;
|
|
var hl = this.fixUmlauts(a.headline_de || a.headline || '');
|
|
ah += '<div class="article-card">';
|
|
ah += '<div class="article-date">';
|
|
if (dObj && !isNaN(dObj.getTime())) {
|
|
ah += '<span class="day">' + dObj.getDate() + '</span>';
|
|
ah += '<span class="month">' + dObj.toLocaleDateString('de-DE', {month:'short'}) + '</span>';
|
|
}
|
|
ah += '</div><div class="article-info">';
|
|
ah += '<p class="article-headline">';
|
|
if (a.source_url) ah += '<a href="' + this.esc(a.source_url) + '" target="_blank" rel="noopener">';
|
|
ah += this.esc(hl);
|
|
if (a.source_url) ah += '</a>';
|
|
ah += '</p><div class="article-meta">';
|
|
ah += '<span class="article-source">' + this.esc(a.source || '') + '</span>';
|
|
if (a.language) ah += '<span class="article-lang">' + a.language.toUpperCase() + '</span>';
|
|
if (dObj && !isNaN(dObj.getTime())) {
|
|
ah += '<span>' + dObj.toLocaleDateString('de-DE', {day:'2-digit',month:'2-digit',year:'numeric'}) + ', ' + dObj.toLocaleTimeString('de-DE', {hour:'2-digit',minute:'2-digit'}) + ' Uhr</span>';
|
|
}
|
|
if (a.source_url) ah += '<span><a href="' + this.esc(a.source_url) + '" target="_blank" rel="noopener" style="color:#0f72b5;text-decoration:none;font-size:0.78rem;">Artikel lesen →</a></span>';
|
|
ah += '</div></div></div>';
|
|
}
|
|
document.getElementById('articles-content').innerHTML = ah;
|
|
},
|
|
|
|
// Placeholder for separate articles render (not used in tab structure)
|
|
renderArticlesTab: function() {},
|
|
|
|
// ===== TAB: KARTE =====
|
|
renderMap: function() {
|
|
if (this.map) { this.map.remove(); this.map = null; }
|
|
this.map = L.map('map-container').setView([33.0, 48.0], 5);
|
|
|
|
// Deutsche Kartenbeschriftungen via OpenStreetMap DE
|
|
L.tileLayer('https://tile.openstreetmap.de/{z}/{x}/{y}.png', {
|
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
|
|
maxZoom: 18
|
|
}).addTo(this.map);
|
|
|
|
var redIcon = L.icon({
|
|
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-red.png',
|
|
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-shadow.png',
|
|
iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41]
|
|
});
|
|
var blueIcon = L.icon({
|
|
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-blue.png',
|
|
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-shadow.png',
|
|
iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41]
|
|
});
|
|
var orangeIcon = L.icon({
|
|
iconUrl: 'https://raw.githubusercontent.com/pointhi/leaflet-color-markers/master/img/marker-icon-2x-orange.png',
|
|
shadowUrl: 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/images/marker-shadow.png',
|
|
iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], shadowSize: [41, 41]
|
|
});
|
|
|
|
var locs = [
|
|
{n:'Teheran, Iran',lat:35.6892,lng:51.3890,d:'Hauptziel der US-israelischen Luftschläge. Über 1.000 Tote nach fünf Tagen Krieg.',ic:redIcon},
|
|
{n:'Beirut, Libanon',lat:33.8938,lng:35.5018,d:'Gleichzeitige US-israelische Luftschläge auf Beirut und Teheran [6].',ic:redIcon},
|
|
{n:'Juffair, Bahrain',lat:26.2235,lng:50.6001,d:'US-Marinebasis — Ziel iranischer Vergeltungsraketen [3].',ic:orangeIcon},
|
|
{n:'Al Udeid, Katar',lat:25.1173,lng:51.3150,d:'US-Luftwaffenstützpunkt — Ziel iranischer Gegenangriffe.',ic:orangeIcon},
|
|
{n:'Tel Aviv, Israel',lat:32.0853,lng:34.7818,d:'Operationsbasis für israelische Angriffe auf den Iran.',ic:blueIcon},
|
|
{n:'Ankara, Türkei',lat:39.9334,lng:32.8597,d:'NATO vermutet iranischen Raketenbeschuss auf Türkei [10]. Keine NATO-Beteiligung geplant.',ic:orangeIcon},
|
|
{n:'Bagdad, Irak',lat:33.3152,lng:44.3661,d:'Lage im Irak als Faktor im Kriegsverlauf [2].',ic:blueIcon},
|
|
{n:'Persischer Golf',lat:27.0,lng:51.5,d:'Iran greift Energieinfrastruktur und diplomatische Einrichtungen in der Golfregion an.',ic:orangeIcon},
|
|
{n:'Dubai, VAE',lat:25.2048,lng:55.2708,d:'US-Botschaft in Dubai durch iranischen Angriff getroffen.',ic:redIcon},
|
|
{n:'Washington D.C., USA',lat:38.9072,lng:-77.0369,d:'War-Powers-Abstimmung im Senat gescheitert (47:53). Trump verteidigt Iran-Krieg vor Kongress.',ic:blueIcon}
|
|
];
|
|
|
|
for (var i = 0; i < locs.length; i++) {
|
|
var l = locs[i];
|
|
L.marker([l.lat, l.lng], {icon: l.ic})
|
|
.addTo(this.map)
|
|
.bindPopup('<strong>' + l.n + '</strong><br><span style="font-size:0.85rem;color:#444;">' + l.d + '</span>');
|
|
}
|
|
|
|
// Legende
|
|
var legend = L.control({position: 'bottomright'});
|
|
legend.onAdd = function() {
|
|
var div = L.DomUtil.create('div', 'map-legend');
|
|
div.style.cssText = 'background:#fff;padding:10px 14px;border-radius:8px;box-shadow:0 2px 8px rgba(0,0,0,0.15);font-size:0.8rem;line-height:1.8;';
|
|
div.innerHTML = '<strong>Legende</strong><br>'
|
|
+ '<span style="color:#cb2b3e;">●</span> Angegriffene Ziele<br>'
|
|
+ '<span style="color:#f39c12;">●</span> Vergeltung / Eskalation<br>'
|
|
+ '<span style="color:#2a81cb;">●</span> Strategische Akteure';
|
|
return div;
|
|
};
|
|
legend.addTo(this.map);
|
|
|
|
setTimeout(function() { if (Lagebild.map) Lagebild.map.invalidateSize(); }, 300);
|
|
},
|
|
|
|
// ===== TAB: FAKTENCHECKS =====
|
|
renderFactChecksTab: function() {
|
|
var checks = this.currentView.fact_checks || [];
|
|
if (!checks.length) {
|
|
document.getElementById('factchecks-content').innerHTML = '<p style="color:#888">Keine Faktenchecks verfügbar.</p>';
|
|
return;
|
|
}
|
|
|
|
// Statistik
|
|
var stats = {confirmed:0, unconfirmed:0, contradicted:0, developing:0, established:0, disputed:0};
|
|
for (var k = 0; k < checks.length; k++) {
|
|
var st = checks[k].status || 'developing';
|
|
if (stats[st] !== undefined) stats[st]++;
|
|
}
|
|
|
|
var h = '<div class="fc-stats">';
|
|
h += '<div class="fc-stat"><span class="fc-stat-num">' + checks.length + '</span><span class="fc-stat-label">Gesamt</span></div>';
|
|
h += '<div class="fc-stat confirmed"><span class="fc-stat-num">' + (stats.confirmed + stats.established) + '</span><span class="fc-stat-label">Bestätigt</span></div>';
|
|
h += '<div class="fc-stat unconfirmed"><span class="fc-stat-num">' + (stats.unconfirmed + stats.developing) + '</span><span class="fc-stat-label">Offen</span></div>';
|
|
if (stats.contradicted + stats.disputed > 0)
|
|
h += '<div class="fc-stat contradicted"><span class="fc-stat-num">' + (stats.contradicted + stats.disputed) + '</span><span class="fc-stat-label">Widerlegt</span></div>';
|
|
h += '</div>';
|
|
|
|
// Sortierung: mit History zuerst, dann sources_count
|
|
checks = checks.slice().sort(function(a, b) {
|
|
var aH = (a.status_history || []).length;
|
|
var bH = (b.status_history || []).length;
|
|
if (bH !== aH) return bH - aH;
|
|
return (b.sources_count || 0) - (a.sources_count || 0);
|
|
});
|
|
|
|
for (var i = 0; i < checks.length; i++) {
|
|
var fc = checks[i];
|
|
var status = fc.status || 'developing';
|
|
var hasProg = fc.status_history && fc.status_history.length > 1;
|
|
|
|
h += '<div class="factcheck-card' + (hasProg ? ' highlight' : '') + '" data-status="' + status + '">';
|
|
h += '<div class="factcheck-header">';
|
|
h += '<span class="status-badge ' + status + '">' + this.stLabel(status) + '</span>';
|
|
h += '<span class="factcheck-sources">' + (fc.sources_count || 0) + ' unabhängige Quellen</span>';
|
|
h += '</div>';
|
|
h += '<p class="factcheck-claim">' + this.esc(this.fixUmlauts(fc.claim || '')) + '</p>';
|
|
|
|
// Vollständige Evidenz anzeigen
|
|
if (fc.evidence) {
|
|
var ev = this.fixUmlauts(fc.evidence);
|
|
// URLs in der Evidenz klickbar machen
|
|
ev = this.esc(ev).replace(/(https?:\/\/[^\s,)]+)/g, '<a href="$1" target="_blank" rel="noopener" style="color:#0f72b5;">$1</a>');
|
|
h += '<div class="factcheck-evidence"><strong>Evidenz:</strong> ' + ev + '</div>';
|
|
}
|
|
|
|
// Status-Verlauf
|
|
if (hasProg) {
|
|
h += '<div class="status-progression">';
|
|
h += '<span class="progression-label">Verlauf:</span>';
|
|
for (var j = 0; j < fc.status_history.length; j++) {
|
|
var step = fc.status_history[j];
|
|
if (j > 0) h += '<span class="progression-arrow">→</span>';
|
|
h += '<span class="progression-step">';
|
|
h += '<span class="status-badge ' + step.status + '">' + this.stLabel(step.status) + '</span>';
|
|
if (step.at) h += '<span class="progression-time">' + this.fmtShort(step.at) + '</span>';
|
|
h += '</span>';
|
|
}
|
|
h += '</div>';
|
|
}
|
|
h += '</div>';
|
|
}
|
|
document.getElementById('factchecks-content').innerHTML = h;
|
|
},
|
|
|
|
// ===== TABS =====
|
|
initTabs: function() {
|
|
var btns = document.querySelectorAll('.tab-btn');
|
|
var self = this;
|
|
for (var i = 0; i < btns.length; i++) {
|
|
btns[i].addEventListener('click', function() {
|
|
var tab = this.getAttribute('data-tab');
|
|
for (var j = 0; j < btns.length; j++) btns[j].classList.remove('active');
|
|
this.classList.add('active');
|
|
var panels = document.querySelectorAll('.tab-panel');
|
|
for (var j = 0; j < panels.length; j++) panels[j].classList.remove('active');
|
|
document.getElementById('panel-' + tab).classList.add('active');
|
|
if (tab === 'karte') self.renderMap();
|
|
});
|
|
}
|
|
},
|
|
|
|
initLangToggle: function() {
|
|
var btn = document.querySelector('.lang-toggle');
|
|
if (!btn) return;
|
|
btn.addEventListener('click', function(e) {
|
|
e.preventDefault();
|
|
if (typeof switchLanguage === 'function') {
|
|
var cur = (typeof getCurrentLanguage === 'function') ? getCurrentLanguage() : 'de';
|
|
switchLanguage(cur === 'de' ? 'en' : 'de');
|
|
}
|
|
});
|
|
},
|
|
|
|
// ===== HILFSFUNKTIONEN =====
|
|
fixUmlauts: function(text) {
|
|
if (!text) return text;
|
|
var skip = ['Israel','Israelis','Jazeera','Euronews','Reuters','Februar',
|
|
'Juffair','abgefeuert','Feindseligkeiten','Gegenschlag','neuesten',
|
|
'auszuweiten','befeuert','feuerte','Feuer','feuer','neue','neuen',
|
|
'neuer','neues','Neue','Aero','aero','Manoeuvre','Dauerfeuer'];
|
|
var ph = []; var c = 0;
|
|
for (var i = 0; i < skip.length; i++) {
|
|
var re = new RegExp('\\b' + skip[i] + '\\b', 'g');
|
|
text = text.replace(re, function(m) { ph.push(m); return '##S' + (c++) + '##'; });
|
|
}
|
|
text = text.replace(/ae/g, '\u00e4').replace(/Ae/g, '\u00c4');
|
|
text = text.replace(/oe/g, '\u00f6').replace(/Oe/g, '\u00d6');
|
|
text = text.replace(/ue/g, '\u00fc').replace(/Ue/g, '\u00dc');
|
|
text = text.replace(/##S(\d+)##/g, function(m, idx) { return ph[parseInt(idx)]; });
|
|
return text;
|
|
},
|
|
|
|
stLabel: function(s) {
|
|
return {confirmed:'Bestätigt',unconfirmed:'Unbestätigt',established:'Gesichert',
|
|
unverified:'Nicht verifiziert',contradicted:'Widerlegt',disputed:'Umstritten',
|
|
developing:'In Entwicklung','false':'Falsch'}[s] || s;
|
|
},
|
|
|
|
mdToHtml: function(md) {
|
|
if (!md) return '';
|
|
var lines = md.split('\n'), html = '', inList = false;
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var l = lines[i];
|
|
if (/^### (.+)$/.test(l)) { if(inList){html+='</ul>';inList=false;} html+='<h3>'+l.replace(/^### /,'')+'</h3>'; continue; }
|
|
if (/^## (.+)$/.test(l)) { if(inList){html+='</ul>';inList=false;} html+='<h2>'+l.replace(/^## /,'')+'</h2>'; continue; }
|
|
if (/^[-*] (.+)$/.test(l)) { if(!inList){html+='<ul>';inList=true;} html+='<li>'+l.replace(/^[-*] /,'')+'</li>'; continue; }
|
|
if (inList) { html+='</ul>'; inList=false; }
|
|
if (l.trim()==='') continue;
|
|
html += '<p>' + l + '</p>';
|
|
}
|
|
if (inList) html += '</ul>';
|
|
html = html.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>');
|
|
html = html.replace(/\*(.+?)\*/g, '<em>$1</em>');
|
|
return html;
|
|
},
|
|
|
|
esc: function(s) { if(!s)return''; var d=document.createElement('div'); d.textContent=s; return d.innerHTML; },
|
|
truncUrl: function(u) { try{return new URL(u).hostname;}catch(e){return u;} },
|
|
|
|
// Timestamps aus der DB sind UTC, aber ohne Zeitzone-Suffix.
|
|
// Diese Funktion haengt 'Z' an falls noetig, damit der Browser korrekt nach CET konvertiert.
|
|
toUTC: function(s) {
|
|
if (!s) return s;
|
|
s = String(s).trim();
|
|
// Hat schon Zeitzone (+00:00, Z, +01:00 etc.)? Dann nichts tun.
|
|
if (/[Zz]$/.test(s) || /[+-]\d{2}:?\d{2}$/.test(s)) return s;
|
|
// "2026-03-07 00:42:01" -> "2026-03-07T00:42:01Z"
|
|
return s.replace(' ', 'T') + 'Z';
|
|
},
|
|
|
|
fmtDT: function(iso) {
|
|
if (!iso) return '';
|
|
try {
|
|
var d = new Date(this.toUTC(iso));
|
|
if (isNaN(d.getTime())) return iso;
|
|
var tage = ['Sonntag','Montag','Dienstag','Mittwoch','Donnerstag','Freitag','Samstag'];
|
|
var mon = ['Januar','Februar','M\u00e4rz','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'];
|
|
return tage[d.getDay()] + ', ' + d.getDate() + '. ' + mon[d.getMonth()] + ' ' + d.getFullYear()
|
|
+ ' um ' + ('0'+d.getHours()).slice(-2) + ':' + ('0'+d.getMinutes()).slice(-2) + ' Uhr';
|
|
} catch(e) { return iso; }
|
|
},
|
|
|
|
fmtShort: function(iso) {
|
|
if (!iso) return '';
|
|
try { return new Date(this.toUTC(iso)).toLocaleDateString('de-DE', {day:'numeric',month:'short',hour:'2-digit',minute:'2-digit'}); }
|
|
catch(e) { return iso; }
|
|
},
|
|
|
|
showError: function() {
|
|
document.getElementById('summary-content').innerHTML =
|
|
'<div class="lagebild-error"><p>Das Lagebild konnte nicht geladen werden. Bitte versuchen Sie es sp\u00e4ter erneut.</p></div>';
|
|
}
|
|
};
|
|
|
|
document.addEventListener('DOMContentLoaded', function() { Lagebild.init(); });
|