Snapshots: Liste ohne Volltext, Lazy-Load + serverseitige Suche
Backend:
- GET /{id}/snapshots liefert nur noch schlanke Shape (Metadaten +
SUBSTR(summary,1,300) AS summary_preview), kein Volltext, kein sources_json.
- Neuer Endpunkt GET /{id}/snapshots/{snapshot_id} fuer Volltext-Lazy-Load.
- Neuer Endpunkt GET /{id}/snapshots/search?q=... fuer serverseitige
Volltextsuche ueber alle Snapshots einer Lage.
Frontend:
- api.js: getSnapshot() und searchSnapshots() ergaenzt.
- app.js: _snapshotFullCache, Volltext wird beim Aufklappen eines
Snapshot-Eintrags per lazyLoadSnapshotDetail() nachgeladen und gecacht.
- Suche ueber Snapshots filtert weiterhin clientseitig ueber summary_preview.
Hintergrund: Bei grossen Lagen (Iran-Lage: 347 Snapshots) fiel die
Snapshots-Listenantwort mit Volltext-Summaries auf ~54 MB. Die Liste
faellt damit auf ~150 KB; Volltexte werden nur on-demand geladen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -420,6 +420,8 @@ const App = {
|
||||
_refreshingIncidents: new Set(),
|
||||
_editingIncidentId: null,
|
||||
_currentArticles: [],
|
||||
_currentSnapshots: [],
|
||||
_snapshotFullCache: new Map(),
|
||||
_currentIncidentType: 'adhoc',
|
||||
_sidebarFilter: 'all',
|
||||
_currentUsername: '',
|
||||
@@ -954,6 +956,7 @@ const App = {
|
||||
// Timeline - Artikel + Snapshots zwischenspeichern und rendern
|
||||
this._currentArticles = articles;
|
||||
this._currentSnapshots = snapshots || [];
|
||||
this._snapshotFullCache = new Map();
|
||||
this._currentIncidentType = incident.type;
|
||||
|
||||
// Tab-Auswahl: gemerkt pro Lage (localStorage), Default = erster Tab
|
||||
@@ -1001,7 +1004,9 @@ const App = {
|
||||
if (filterType === 'all' || filterType === 'snapshots') {
|
||||
let snapshots = this._currentSnapshots || [];
|
||||
if (searchTerm) {
|
||||
snapshots = snapshots.filter(s => (s.summary || '').toLowerCase().includes(searchTerm));
|
||||
// Suche erfolgt clientseitig ueber Preview (Snapshots-Liste enthaelt keinen Volltext mehr).
|
||||
// Die asynchrone Volltext-Server-Suche wird separat ausgeloest (rerenderTimeline).
|
||||
snapshots = snapshots.filter(s => (s.summary_preview || s.summary || '').toLowerCase().includes(searchTerm));
|
||||
}
|
||||
snapshots.forEach(s => entries.push({ kind: 'snapshot', data: s, timestamp: s.created_at || '' }));
|
||||
}
|
||||
@@ -1503,6 +1508,7 @@ const App = {
|
||||
|
||||
/**
|
||||
* Snapshot/Lagebericht-Eintrag für den Zeitstrahl rendern.
|
||||
* Volltext + sources_json werden erst beim Aufklappen lazy nachgeladen.
|
||||
*/
|
||||
_renderSnapshotEntry(snapshot) {
|
||||
const time = snapshot.created_at
|
||||
@@ -1514,24 +1520,58 @@ const App = {
|
||||
if (snapshot.fact_check_count) stats.push(`${snapshot.fact_check_count} Fakten`);
|
||||
const statsText = stats.join(', ');
|
||||
|
||||
// Vorschau: erste 200 Zeichen der Zusammenfassung
|
||||
const summaryText = snapshot.summary || '';
|
||||
const preview = summaryText.length > 200 ? summaryText.substring(0, 200) + '...' : summaryText;
|
||||
// Vorschau: erste 200 Zeichen aus summary_preview (vom Server gekuerzt) oder Fallback summary
|
||||
const previewText = snapshot.summary_preview || snapshot.summary || '';
|
||||
const preview = previewText.length > 200 ? previewText.substring(0, 200) + '...' : previewText;
|
||||
|
||||
// Vollständige Zusammenfassung via UI.renderSummary
|
||||
const fullSummary = UI.renderSummary(snapshot.summary, snapshot.sources_json, this._currentIncidentType);
|
||||
// Volltext aus Cache (falls bereits geladen), sonst Platzhalter fuer Lazy-Load
|
||||
const cached = this._snapshotFullCache && this._snapshotFullCache.get(snapshot.id);
|
||||
const detailHtml = cached
|
||||
? UI.renderSummary(cached.summary, cached.sources_json, this._currentIncidentType)
|
||||
: '<div class="vt-snapshot-loading">Lagebericht wird geladen…</div>';
|
||||
const loadedAttr = cached ? ' data-loaded="yes"' : '';
|
||||
|
||||
return `<div class="vt-entry vt-snapshot expandable" onclick="App.toggleTimelineEntry(this)">
|
||||
return `<div class="vt-entry vt-snapshot expandable" data-snapshot-id="${snapshot.id}"${loadedAttr} onclick="App.toggleTimelineEntry(this)">
|
||||
<div class="vt-snapshot-header">
|
||||
<span class="vt-snapshot-badge">Lagebericht</span>
|
||||
<span class="vt-snapshot-time">${time}</span>
|
||||
<span class="vt-snapshot-stats">${UI.escape(statsText)}</span>
|
||||
</div>
|
||||
<div class="vt-snapshot-preview">${UI.escape(preview)}</div>
|
||||
<div class="vt-snapshot-detail">${fullSummary}</div>
|
||||
<div class="vt-snapshot-detail">${detailHtml}</div>
|
||||
</div>`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Volltext eines Snapshots bei Bedarf nachladen und in das DOM einsetzen.
|
||||
* Ergebnis wird in _snapshotFullCache gecacht.
|
||||
*/
|
||||
async lazyLoadSnapshotDetail(el) {
|
||||
if (!el || el.dataset.loaded === 'yes' || el.dataset.loaded === 'loading') return;
|
||||
const snapId = parseInt(el.dataset.snapshotId || '0', 10);
|
||||
if (!snapId || !this.currentIncidentId) return;
|
||||
el.dataset.loaded = 'loading';
|
||||
try {
|
||||
let snap = this._snapshotFullCache.get(snapId);
|
||||
if (!snap) {
|
||||
snap = await API.getSnapshot(this.currentIncidentId, snapId);
|
||||
this._snapshotFullCache.set(snapId, snap);
|
||||
}
|
||||
const detailEl = el.querySelector('.vt-snapshot-detail');
|
||||
if (detailEl) {
|
||||
detailEl.innerHTML = UI.renderSummary(snap.summary, snap.sources_json, this._currentIncidentType);
|
||||
}
|
||||
el.dataset.loaded = 'yes';
|
||||
// Nach dem Laden die Timeline-Kachel an neue Hoehe anpassen
|
||||
if (el.classList.contains('expanded')) this._resizeTimelineTile();
|
||||
} catch (err) {
|
||||
console.error('Snapshot-Volltext laden fehlgeschlagen:', err);
|
||||
el.dataset.loaded = '';
|
||||
const detailEl = el.querySelector('.vt-snapshot-detail');
|
||||
if (detailEl) detailEl.innerHTML = '<div class="vt-snapshot-error">Fehler beim Laden des Lageberichts.</div>';
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Timeline-Eintrag auf-/zuklappen (mutual-exclusive pro Zeitgruppe).
|
||||
*/
|
||||
@@ -1544,6 +1584,10 @@ const App = {
|
||||
}
|
||||
el.classList.toggle('expanded');
|
||||
if (el.classList.contains('expanded')) {
|
||||
// Snapshots: Volltext lazy nachladen (nur wenn noch nicht geladen)
|
||||
if (el.classList.contains('vt-snapshot') && el.dataset.snapshotId) {
|
||||
this.lazyLoadSnapshotDetail(el);
|
||||
}
|
||||
requestAnimationFrame(() => {
|
||||
var scrollParent = el.closest('.ht-detail-content');
|
||||
if (scrollParent && el.classList.contains('vt-snapshot')) {
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren