(() => { 'use strict'; const $ = (sel, root = document) => root.querySelector(sel); const $$ = (sel, root = document) => Array.from(root.querySelectorAll(sel)); const state = { scope: 'production', page: 1, pageSize: 60, total: 0, totalPages: 0, items: [], selected: new Set(), currentKey: null, currentDoc: null, currentMeta: null, dirty: false, activeTab: 'form', lastBulkPreviewSignature: '', }; const els = { brandTitle: $('#brandTitle'), brandSubtitle: $('#brandSubtitle'), healthPill: $('#healthPill'), reloadBtn: $('#reloadBtn'), bulkBtn: $('#bulkBtn'), scopeProduction: $('#scopeProduction'), scopeStaging: $('#scopeStaging'), stagingCountBadge: $('#stagingCountBadge'), searchInput: $('#searchInput'), autoReplyFilter: $('#autoReplyFilter'), languageFilter: $('#languageFilter'), sourceFilter: $('#sourceFilter'), styleFilter: $('#styleFilter'), selectPage: $('#selectPage'), selectionCount: $('#selectionCount'), resultList: $('#resultList'), prevPage: $('#prevPage'), nextPage: $('#nextPage'), pageLabel: $('#pageLabel'), totalLabel: $('#totalLabel'), emptyState: $('#emptyState'), editor: $('#editor'), filePath: $('#filePath'), dirtyBadge: $('#dirtyBadge'), stagingBadge: $('#stagingBadge'), saveBtn: $('#saveBtn'), formatJsonBtn: $('#formatJsonBtn'), deleteStagingBtn: $('#deleteStagingBtn'), promoteStagingBtn: $('#promoteStagingBtn'), formTab: $('#formTab'), rawTab: $('#rawTab'), rawEditor: $('#rawEditor'), rawError: $('#rawError'), bulkDialog: $('#bulkDialog'), bulkTargetText: $('#bulkTargetText'), bulkAllMatching: $('#bulkAllMatching'), allMatchingHint: $('#allMatchingHint'), bulkPreview: $('#bulkPreview'), previewBulkBtn: $('#previewBulkBtn'), applyBulkBtn: $('#applyBulkBtn'), stagingBulkDialog: $('#stagingBulkDialog'), stagingBulkTargetText: $('#stagingBulkTargetText'), stagingBulkResult: $('#stagingBulkResult'), bulkDeleteStagingBtn: $('#bulkDeleteStagingBtn'), bulkPromoteStagingBtn: $('#bulkPromoteStagingBtn'), toastHost: $('#toastHost') }; let searchTimer; async function api(url, options = {}) { const res = await fetch(url, options); const contentType = res.headers.get('content-type') || ''; const body = contentType.includes('application/json') ? await res.json() : await res.text(); if (!res.ok) { const msg = typeof body === 'object' && body?.error ? body.error : `${res.status} ${res.statusText}`; throw new Error(msg); } return body; } function currentQuery(page = state.page) { return { q: els.searchInput.value.trim(), auto_reply: els.autoReplyFilter.value, language: els.languageFilter.value.trim(), communication_style: els.styleFilter.value.trim(), source: els.sourceFilter.value.trim(), page, page_size: state.pageSize, }; } function queryString(q) { const p = new URLSearchParams(); Object.entries(q).forEach(([k, v]) => { if (v !== '' && v !== null && v !== undefined && !(k === 'auto_reply' && v === 'any')) p.set(k, v); }); return p.toString(); } async function loadHealth() { try { const [h, config] = await Promise.all([api('/api/health'), api('/api/config')]); if (config.title) { els.brandTitle.textContent = config.title; document.title = config.title; } if (config.subtitle) els.brandSubtitle.textContent = config.subtitle; const stagingCount = Number(h.staging_count || 0); els.stagingCountBadge.textContent = stagingCount.toLocaleString('de-DE'); els.healthPill.textContent = `${h.count.toLocaleString('de-DE')} produktiv · ${stagingCount.toLocaleString('de-DE')} Staging`; els.healthPill.className = 'pill ok'; els.healthPill.title = `Daten: ${h.data_dir}\nStaging: ${h.staging_dir || '–'}\nBackups: ${h.backup_dir || '–'}`; } catch (err) { els.healthPill.textContent = 'Offline'; els.healthPill.className = 'pill'; toast(err.message, 'error'); } } async function loadList(resetPage = false) { if (resetPage) state.page = 1; els.resultList.innerHTML = '
Lade …
'; try { const endpoint = state.scope === 'staging' ? '/api/staging' : '/api/items'; const data = await api(`${endpoint}?${queryString(currentQuery())}`); state.page = data.page || 1; state.total = data.total; state.totalPages = data.total_pages; state.items = data.items || []; renderList(); } catch (err) { els.resultList.innerHTML = `
${escapeHTML(err.message)}
`; } } function renderList() { els.resultList.innerHTML = ''; if (state.items.length === 0) { els.resultList.innerHTML = '
Keine Treffer.
'; } for (const item of state.items) { const row = document.createElement('div'); row.className = `result-item${item.key === state.currentKey ? ' active' : ''}${state.scope === 'staging' ? ' staging-item' : ''}`; row.dataset.key = item.key; const checked = state.selected.has(item.key) ? 'checked' : ''; const auto = item.auto_reply === true; row.innerHTML = `
${state.scope === 'staging' ? 'STAGING ' : ''}${escapeHTML(item.id || item.rel_path)}
${escapeHTML(item.title || '(ohne Titel)')}
auto ${String(item.auto_reply ?? '–')} score ${item.min_score ?? '–'} ${escapeHTML(item.language || '–')} ${escapeHTML(item.source || '')}
`; const cb = $('.result-check', row); cb.addEventListener('click', (e) => { e.stopPropagation(); toggleSelection(item.key, cb.checked); }); row.addEventListener('click', () => openItem(item.key)); els.resultList.appendChild(row); } els.pageLabel.textContent = state.totalPages ? `Seite ${state.page} / ${state.totalPages}` : 'Seite 0 / 0'; els.totalLabel.textContent = `${state.total.toLocaleString('de-DE')} Treffer`; els.prevPage.disabled = state.page <= 1; els.nextPage.disabled = state.totalPages === 0 || state.page >= state.totalPages; els.selectPage.checked = state.items.length > 0 && state.items.every(i => state.selected.has(i.key)); els.selectPage.indeterminate = !els.selectPage.checked && state.items.some(i => state.selected.has(i.key)); updateSelectionUI(); } function toggleSelection(key, checked) { if (checked) state.selected.add(key); else state.selected.delete(key); renderSelectionOnly(); } function renderSelectionOnly() { els.selectionCount.textContent = `${state.selected.size.toLocaleString('de-DE')} ausgewählt`; els.bulkBtn.disabled = state.scope === 'staging' ? state.selected.size === 0 : (state.selected.size === 0 && state.total === 0); els.bulkBtn.textContent = state.scope === 'staging' ? '✦ Staging-Aktionen' : '✦ Massenbearbeitung'; els.selectPage.checked = state.items.length > 0 && state.items.every(i => state.selected.has(i.key)); els.selectPage.indeterminate = !els.selectPage.checked && state.items.some(i => state.selected.has(i.key)); } function updateSelectionUI() { renderSelectionOnly(); } async function openItem(key) { if (key === state.currentKey) return; if (state.dirty && !confirm('Es gibt ungespeicherte Änderungen. Wirklich einen anderen Eintrag öffnen?')) return; try { const endpoint = state.scope === 'staging' ? '/api/staging' : '/api/items'; const data = await api(`${endpoint}/${encodeURIComponent(key)}`); state.currentKey = key; state.currentDoc = data.document; state.currentMeta = data.meta; state.dirty = false; showEditor(); fillForm(); setTab('form'); renderList(); } catch (err) { toast(err.message, 'error'); } } function showEditor() { els.emptyState.classList.add('hidden'); els.editor.classList.remove('hidden'); els.filePath.textContent = state.currentMeta?.rel_path || '–'; const isStaging = state.scope === 'staging'; els.stagingBadge.classList.toggle('hidden', !isStaging); els.promoteStagingBtn.classList.toggle('hidden', !isStaging); els.deleteStagingBtn.classList.toggle('hidden', !isStaging); setDirty(false); } function fillForm() { $$('[data-field]').forEach(input => { const name = input.dataset.field; const val = state.currentDoc?.[name]; if (input.type === 'checkbox') input.checked = Boolean(val); else input.value = val ?? ''; }); $$('[data-list-field]').forEach(input => { const val = state.currentDoc?.[input.dataset.listField]; input.value = Array.isArray(val) ? val.join('\n') : ''; }); els.rawEditor.value = JSON.stringify(state.currentDoc, null, 2); clearRawError(); } function syncFormToDoc() { if (!state.currentDoc) return; $$('[data-field]').forEach(input => { const name = input.dataset.field; if (input.type === 'checkbox') state.currentDoc[name] = input.checked; else if (input.type === 'number') { if (input.value === '') delete state.currentDoc[name]; else state.currentDoc[name] = Number(input.value); } else state.currentDoc[name] = input.value; }); $$('[data-list-field]').forEach(input => { state.currentDoc[input.dataset.listField] = lines(input.value); }); } function syncRawToDoc() { try { const parsed = JSON.parse(els.rawEditor.value); if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') throw new Error('Die JSON-Wurzel muss ein Objekt sein.'); state.currentDoc = parsed; clearRawError(); return true; } catch (err) { els.rawError.textContent = `JSON-Fehler: ${err.message}`; els.rawError.classList.remove('hidden'); return false; } } function clearRawError() { els.rawError.textContent = ''; els.rawError.classList.add('hidden'); } function setDirty(v = true) { state.dirty = v; els.dirtyBadge.classList.toggle('hidden', !v); } function setTab(tab) { if (tab === state.activeTab) return; if (state.activeTab === 'raw' && tab === 'form') { if (!syncRawToDoc()) return; fillForm(); } else if (state.activeTab === 'form' && tab === 'raw') { syncFormToDoc(); els.rawEditor.value = JSON.stringify(state.currentDoc, null, 2); } state.activeTab = tab; $$('.tab').forEach(b => b.classList.toggle('active', b.dataset.tab === tab)); els.formTab.classList.toggle('active', tab === 'form'); els.rawTab.classList.toggle('active', tab === 'raw'); els.formatJsonBtn.classList.toggle('hidden', tab !== 'raw'); } async function saveCurrent() { if (!state.currentKey || !state.currentDoc) return false; if (state.activeTab === 'raw') { if (!syncRawToDoc()) return false; } else syncFormToDoc(); els.saveBtn.disabled = true; try { const endpoint = state.scope === 'staging' ? '/api/staging' : '/api/items'; const result = await api(`${endpoint}/${encodeURIComponent(state.currentKey)}`, { method: 'PUT', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(state.currentDoc) }); state.currentMeta = result.meta; if (result.document) state.currentDoc = result.document; setDirty(false); if (state.scope === 'staging') toast('Staging-Entwurf gespeichert.', 'success'); else toast(`Gespeichert. Backup: ${shortPath(result.backup)}`, 'success'); await loadList(false); return true; } catch (err) { toast(err.message, 'error'); return false; } finally { els.saveBtn.disabled = false; } } function openBulk() { if (state.scope === 'staging') { if (state.selected.size === 0) return; els.stagingBulkTargetText.textContent = `${state.selected.size.toLocaleString('de-DE')} ausgewählte Staging-Entwürfe`; els.stagingBulkResult.className = 'preview-box hidden'; els.stagingBulkResult.textContent = ''; els.stagingBulkDialog.showModal(); return; } if (state.selected.size === 0 && state.total === 0) return; resetBulkPreview(); els.bulkAllMatching.checked = state.selected.size === 0; updateBulkTargetText(); els.bulkDialog.showModal(); } function updateBulkTargetText() { const all = els.bulkAllMatching.checked; els.bulkTargetText.textContent = all ? `${state.total.toLocaleString('de-DE')} aktuelle Treffer als Ziel` : `${state.selected.size.toLocaleString('de-DE')} explizit ausgewählte Dateien als Ziel`; els.allMatchingHint.textContent = `Aktueller Filter: ${state.total.toLocaleString('de-DE')} Treffer`; } function buildPatch() { const patch = {}; const enabled = id => $(`[data-enable="${id}"]`)?.checked; if (enabled('bulkAutoReply')) patch.set_auto_reply = $('#bulkAutoReply').value === 'true'; if (enabled('bulkMinScore')) patch.set_min_score = Number($('#bulkMinScore').value); if (enabled('bulkLanguage')) patch.set_language = $('#bulkLanguage').value; if (enabled('bulkStyle')) patch.set_communication_style = $('#bulkStyle').value; if (enabled('bulkSource')) patch.set_source = $('#bulkSource').value; if (enabled('bulkSourceUri')) patch.set_source_uri = $('#bulkSourceUri').value; const addK = lines($('#addKeywords').value), rmK = lines($('#removeKeywords').value); const addC = lines($('#addCategories').value), rmC = lines($('#removeCategories').value); if (addK.length) patch.add_keywords = addK; if (rmK.length) patch.remove_keywords = rmK; if (addC.length) patch.add_categories = addC; if (rmC.length) patch.remove_categories = rmC; const find = $('#replaceFind').value; if (find) { patch.find_replace = { fields: $$('input[name="replaceField"]:checked').map(x => x.value), find, replace: $('#replaceWith').value, regex: $('#replaceRegex').checked, case_sensitive: $('#replaceCase').checked, }; } return patch; } function buildBulkRequest(dryRun) { const q = currentQuery(1); q.page = 0; q.page_size = 0; return { keys: Array.from(state.selected), all_matching: els.bulkAllMatching.checked, query: q, patch: buildPatch(), dry_run: dryRun, }; } async function previewBulk() { const req = buildBulkRequest(true); if (!Object.keys(req.patch).length) { toast('Bitte mindestens eine Änderung festlegen.', 'error'); return; } els.previewBulkBtn.disabled = true; try { const result = await api('/api/bulk', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(req) }); state.lastBulkPreviewSignature = signatureFor(req); renderBulkPreview(result); els.applyBulkBtn.disabled = result.changed === 0; } catch (err) { els.bulkPreview.textContent = err.message; els.bulkPreview.className = 'preview-box warn'; els.applyBulkBtn.disabled = true; } finally { els.previewBulkBtn.disabled = false; } } function renderBulkPreview(result) { els.bulkPreview.className = `preview-box ${result.changed > 0 ? 'ok' : 'warn'}`; els.bulkPreview.innerHTML = ` Vorschau: ${result.changed.toLocaleString('de-DE')} von ${result.targeted.toLocaleString('de-DE')} Dateien würden geändert, ${result.skipped.toLocaleString('de-DE')} bleiben unverändert. ${result.sample?.length ? `
${result.sample.map(x => `${escapeHTML(x.id || x.rel_path)} · ${escapeHTML(x.title || '')}`).join('')}
` : ''}`; } async function applyBulk() { const req = buildBulkRequest(false); const sig = signatureFor({...req, dry_run: true}); if (sig !== state.lastBulkPreviewSignature) { toast('Die Massenänderung wurde seit der Vorschau verändert. Bitte erneut Vorschau ausführen.', 'error'); els.applyBulkBtn.disabled = true; return; } if (!confirm('Massenänderung jetzt wirklich auf die Zieldateien anwenden?')) return; els.applyBulkBtn.disabled = true; try { const result = await api('/api/bulk', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(req) }); toast(`${result.changed.toLocaleString('de-DE')} Dateien geändert. Backup: ${shortPath(result.backup)}`, 'success'); els.bulkDialog.close(); state.selected.clear(); state.currentKey = null; state.currentDoc = null; state.currentMeta = null; state.dirty = false; els.editor.classList.add('hidden'); els.emptyState.classList.remove('hidden'); await Promise.all([loadList(true), loadHealth()]); } catch (err) { toast(err.message, 'error'); } } async function promoteCurrentStaging() { if (state.scope !== 'staging' || !state.currentKey) return; if (state.dirty) { if (!confirm('Der Entwurf enthält ungespeicherte Änderungen. Vor der Freigabe speichern?')) return; if (!await saveCurrent()) return; } if (!confirm('Diesen Staging-Entwurf jetzt unverändert in die produktive Wissensbasis freigeben?')) return; els.promoteStagingBtn.disabled = true; try { const result = await api(`/api/staging/${encodeURIComponent(state.currentKey)}/promote`, { method: 'POST', headers: {'Content-Type':'application/json'}, body: '{}' }); const productionKey = result.production?.key; toast(`Freigegeben: ${result.production?.rel_path || result.production?.id || 'Produktivartikel'}`, 'success'); state.currentKey = null; state.currentDoc = null; state.currentMeta = null; state.dirty = false; state.selected.clear(); await loadHealth(); await setScope('production'); if (productionKey) await openItem(productionKey); } catch (err) { toast(err.message, 'error'); } finally { els.promoteStagingBtn.disabled = false; } } async function deleteCurrentStaging() { if (state.scope !== 'staging' || !state.currentKey) return; if (!confirm('Diesen Staging-Entwurf löschen? Er wird zur Sicherheit nach staging/.trash verschoben.')) return; els.deleteStagingBtn.disabled = true; try { await api(`/api/staging/${encodeURIComponent(state.currentKey)}`, {method: 'DELETE'}); toast('Staging-Entwurf gelöscht und in .trash archiviert.', 'success'); state.selected.delete(state.currentKey); state.currentKey = null; state.currentDoc = null; state.currentMeta = null; state.dirty = false; els.editor.classList.add('hidden'); els.emptyState.classList.remove('hidden'); await Promise.all([loadList(true), loadHealth()]); } catch (err) { toast(err.message, 'error'); } finally { els.deleteStagingBtn.disabled = false; } } async function stagingBulkAction(action) { if (state.scope !== 'staging' || state.selected.size === 0) return; const verb = action === 'promote' ? 'freigeben' : 'löschen'; if (!confirm(`${state.selected.size.toLocaleString('de-DE')} Staging-Entwürfe wirklich ${verb}?`)) return; els.bulkPromoteStagingBtn.disabled = true; els.bulkDeleteStagingBtn.disabled = true; try { const result = await api('/api/staging/bulk', { method: 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify({keys: Array.from(state.selected), action}) }); els.stagingBulkResult.className = `preview-box ${result.failed ? 'warn' : 'ok'}`; els.stagingBulkResult.innerHTML = `${result.succeeded.toLocaleString('de-DE')} erfolgreich · ${result.failed.toLocaleString('de-DE')} fehlgeschlagen` + (result.failed ? `
${result.items.filter(x => !x.ok).slice(0,20).map(x => `${escapeHTML(x.key)} · ${escapeHTML(x.error)}`).join('')}
` : ''); state.selected.clear(); state.currentKey = null; state.currentDoc = null; state.currentMeta = null; state.dirty = false; els.editor.classList.add('hidden'); els.emptyState.classList.remove('hidden'); await Promise.all([loadList(true), loadHealth()]); if (!result.failed) setTimeout(() => els.stagingBulkDialog.close(), 650); } catch (err) { els.stagingBulkResult.className = 'preview-box warn'; els.stagingBulkResult.textContent = err.message; } finally { els.bulkPromoteStagingBtn.disabled = false; els.bulkDeleteStagingBtn.disabled = false; } } async function setScope(scope) { if (scope !== 'production' && scope !== 'staging') return; if (scope === state.scope) return; if (state.dirty && !confirm('Ungespeicherte Änderungen verwerfen und Bereich wechseln?')) return; state.scope = scope; state.page = 1; state.selected.clear(); state.currentKey = null; state.currentDoc = null; state.currentMeta = null; state.dirty = false; els.scopeProduction.classList.toggle('active', scope === 'production'); els.scopeStaging.classList.toggle('active', scope === 'staging'); els.editor.classList.add('hidden'); els.emptyState.classList.remove('hidden'); els.emptyState.querySelector('h1').textContent = scope === 'staging' ? 'Staging-Entwürfe prüfen' : 'JSON-Wissensbasis bearbeiten'; els.emptyState.querySelector('p').textContent = scope === 'staging' ? 'KI-generierte Entwürfe prüfen, bearbeiten und anschließend gezielt freigeben oder verwerfen.' : 'Wähle links einen Eintrag aus oder markiere mehrere Dateien für eine Massenänderung.'; renderSelectionOnly(); await loadList(true); } function resetBulkPreview() { state.lastBulkPreviewSignature = ''; els.bulkPreview.className = 'preview-box hidden'; els.bulkPreview.textContent = ''; els.applyBulkBtn.disabled = true; } function signatureFor(obj) { return JSON.stringify(obj); } function lines(s) { return s.split(/\r?\n/).map(x => x.trim()).filter(Boolean); } function shortPath(p) { if (!p) return '–'; const parts = p.split('/'); return parts.slice(-2).join('/'); } function escapeHTML(s) { return String(s ?? '').replace(/[&<>'"]/g, c => ({'&':'&','<':'<','>':'>',"'":''','"':'"'}[c])); } function toast(message, type = '') { const el = document.createElement('div'); el.className = `toast ${type}`; el.textContent = message; els.toastHost.appendChild(el); setTimeout(() => el.remove(), 5000); } function debounceReload() { clearTimeout(searchTimer); searchTimer = setTimeout(() => loadList(true), 240); } els.scopeProduction.addEventListener('click', () => setScope('production')); els.scopeStaging.addEventListener('click', () => setScope('staging')); els.promoteStagingBtn.addEventListener('click', promoteCurrentStaging); els.deleteStagingBtn.addEventListener('click', deleteCurrentStaging); els.bulkPromoteStagingBtn.addEventListener('click', () => stagingBulkAction('promote')); els.bulkDeleteStagingBtn.addEventListener('click', () => stagingBulkAction('delete')); // Filters and navigation. [els.searchInput, els.languageFilter, els.sourceFilter, els.styleFilter].forEach(el => el.addEventListener('input', debounceReload)); els.autoReplyFilter.addEventListener('change', () => loadList(true)); els.prevPage.addEventListener('click', () => { if (state.page > 1) { state.page--; loadList(); } }); els.nextPage.addEventListener('click', () => { if (state.page < state.totalPages) { state.page++; loadList(); } }); els.selectPage.addEventListener('change', () => { for (const item of state.items) { if (els.selectPage.checked) state.selected.add(item.key); else state.selected.delete(item.key); } renderList(); }); // Editor. $$('[data-field], [data-list-field]').forEach(el => el.addEventListener('input', () => setDirty(true))); els.rawEditor.addEventListener('input', () => { setDirty(true); clearRawError(); }); $$('.tab').forEach(btn => btn.addEventListener('click', () => setTab(btn.dataset.tab))); els.saveBtn.addEventListener('click', saveCurrent); els.formatJsonBtn.addEventListener('click', () => { if (syncRawToDoc()) { els.rawEditor.value = JSON.stringify(state.currentDoc, null, 2); setDirty(true); } }); // Bulk modal. els.bulkBtn.addEventListener('click', openBulk); els.bulkAllMatching.addEventListener('change', () => { updateBulkTargetText(); resetBulkPreview(); }); $$('[data-enable]').forEach(toggle => toggle.addEventListener('change', () => { const target = document.getElementById(toggle.dataset.enable); if (target) target.disabled = !toggle.checked; resetBulkPreview(); })); $$('#bulkDialog input, #bulkDialog textarea, #bulkDialog select').forEach(el => { if (el !== els.bulkAllMatching && !el.hasAttribute('data-enable')) el.addEventListener('input', resetBulkPreview); }); els.previewBulkBtn.addEventListener('click', previewBulk); els.applyBulkBtn.addEventListener('click', applyBulk); els.reloadBtn.addEventListener('click', async () => { if (state.dirty && !confirm('Ungespeicherte Änderungen verwerfen und Dateien neu einlesen?')) return; try { const r = await api('/api/reload', {method: 'POST', headers: {'Content-Type':'application/json'}, body:'{}'}); state.currentKey = null; state.currentDoc = null; state.currentMeta = null; state.dirty = false; state.selected.clear(); els.editor.classList.add('hidden'); els.emptyState.classList.remove('hidden'); toast(`${r.count.toLocaleString('de-DE')} Dateien neu eingelesen.`, 'success'); await Promise.all([loadList(true), loadHealth()]); } catch (err) { toast(err.message, 'error'); } }); document.addEventListener('keydown', (e) => { if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's') { e.preventDefault(); saveCurrent(); } if (e.key === '/' && !['INPUT','TEXTAREA','SELECT'].includes(document.activeElement?.tagName)) { e.preventDefault(); els.searchInput.focus(); } }); window.addEventListener('beforeunload', e => { if (state.dirty) { e.preventDefault(); e.returnValue = ''; } }); loadHealth(); loadList(true); })();