feat(i18n): Export-Modal + Quellenverwaltung + Chat-Widget + Stats-Bar
- Export-Modal: Titel, Bereiche, Format, alle Checkboxes (Zusammenfassung, Recherchebericht / Lagebild, Faktencheck, Quellen), PDF/DOCX, Abbrechen, Exportieren. - Quellenverwaltung-Modal: Title, 8 Filter-Labels (sr-only) + 8 Alle-* Default-Optionen, Search-Placeholder + Label, + Quelle-Button, Add- Form (URL/Erkennen/Name/Kategorie/Typ/RSS-URL/Domain/Notizen + Placeholder), Speichern/Abbrechen, Loading-State. - Stats-Bar (app.js): RSS-Feeds/Web-Quellen/Ausgeschlossen-Labels. - components.js: source-excluded-badge. - Chat-Widget: Title, alle 5 Buttons mit title+aria, Input-Placeholder. - Chat-Begruessung in chat.js auf T() umgestellt. - 50+ neue i18n-Keys. Cache-Buster components.js + chat.js + app.js auf v=20260514e gebumpt.
Dieser Commit ist enthalten in:
@@ -449,8 +449,8 @@
|
||||
<div class="modal-overlay" id="modal-sources" role="dialog" aria-modal="true" aria-labelledby="modal-sources-title">
|
||||
<div class="modal modal-wide">
|
||||
<div class="modal-header">
|
||||
<div class="modal-title" id="modal-sources-title">Quellenverwaltung</div>
|
||||
<button class="modal-close" onclick="closeModal('modal-sources')" aria-label="Schließen">×</button>
|
||||
<div class="modal-title" id="modal-sources-title" data-i18n="sources_modal.title">Quellenverwaltung</div>
|
||||
<button class="modal-close" onclick="closeModal('modal-sources')" aria-label="Schließen" data-i18n-attr="aria-label:aria.close">×</button>
|
||||
</div>
|
||||
<div class="modal-body sources-modal-body">
|
||||
<!-- Stats-Leiste -->
|
||||
@@ -459,17 +459,17 @@
|
||||
<!-- Toolbar -->
|
||||
<div class="sources-toolbar">
|
||||
<div class="sources-filters">
|
||||
<label for="sources-filter-type" class="sr-only">Quellentyp filtern</label>
|
||||
<label for="sources-filter-type" class="sr-only" data-i18n="sources_modal.filter.type">Quellentyp filtern</label>
|
||||
<select id="sources-filter-type" class="timeline-filter-select" onchange="App.filterSources()">
|
||||
<option value="">Alle Typen</option>
|
||||
<option value="" data-i18n="sources_modal.filter.type_all">Alle Typen</option>
|
||||
<option value="rss_feed">RSS-Feed</option>
|
||||
<option value="web_source">Web-Quelle</option>
|
||||
<option value="telegram_channel">Telegram</option>
|
||||
<option value="excluded">Von mir ausgeschlossen</option>
|
||||
</select>
|
||||
<label for="sources-filter-category" class="sr-only">Kategorie filtern</label>
|
||||
<label for="sources-filter-category" class="sr-only" data-i18n="sources_modal.filter.category">Kategorie filtern</label>
|
||||
<select id="sources-filter-category" class="timeline-filter-select" onchange="App.filterSources()">
|
||||
<option value="">Alle Kategorien</option>
|
||||
<option value="" data-i18n="sources_modal.filter.category_all">Alle Kategorien</option>
|
||||
<option value="nachrichtenagentur">Nachrichtenagentur</option>
|
||||
<option value="oeffentlich-rechtlich">Öffentlich-Rechtlich</option>
|
||||
<option value="qualitaetszeitung">Qualitätszeitung</option>
|
||||
@@ -481,9 +481,9 @@
|
||||
<option value="boulevard">Boulevard</option>
|
||||
<option value="sonstige">Sonstige</option>
|
||||
</select>
|
||||
<label for="sources-filter-political" class="sr-only">Politische Ausrichtung filtern</label>
|
||||
<label for="sources-filter-political" class="sr-only" data-i18n="sources_modal.filter.political">Politische Ausrichtung filtern</label>
|
||||
<select id="sources-filter-political" class="timeline-filter-select" onchange="App.filterSources()">
|
||||
<option value="">Alle Ausrichtungen</option>
|
||||
<option value="" data-i18n="sources_modal.filter.political_all">Alle Ausrichtungen</option>
|
||||
<option value="links_extrem">Links (extrem)</option>
|
||||
<option value="links">Links</option>
|
||||
<option value="mitte_links">Mitte-Links</option>
|
||||
@@ -495,9 +495,9 @@
|
||||
<option value="rechts_extrem">Rechts (extrem)</option>
|
||||
<option value="na">Nicht eingeordnet</option>
|
||||
</select>
|
||||
<label for="sources-filter-mediatype" class="sr-only">Medientyp filtern</label>
|
||||
<label for="sources-filter-mediatype" class="sr-only" data-i18n="sources_modal.filter.mediatype">Medientyp filtern</label>
|
||||
<select id="sources-filter-mediatype" class="timeline-filter-select" onchange="App.filterSources()">
|
||||
<option value="">Alle Medientypen</option>
|
||||
<option value="" data-i18n="sources_modal.filter.mediatype_all">Alle Medientypen</option>
|
||||
<option value="tageszeitung">Tageszeitung</option>
|
||||
<option value="wochenzeitung">Wochenzeitung</option>
|
||||
<option value="magazin">Magazin</option>
|
||||
@@ -519,9 +519,9 @@
|
||||
<option value="fachmedium">Fachmedium</option>
|
||||
<option value="sonstige">Sonstige</option>
|
||||
</select>
|
||||
<label for="sources-filter-reliability" class="sr-only">Glaubwürdigkeit filtern</label>
|
||||
<label for="sources-filter-reliability" class="sr-only" data-i18n="sources_modal.filter.reliability">Glaubwürdigkeit filtern</label>
|
||||
<select id="sources-filter-reliability" class="timeline-filter-select" onchange="App.filterSources()">
|
||||
<option value="">Alle Glaubwürdigkeiten</option>
|
||||
<option value="" data-i18n="sources_modal.filter.reliability_all">Alle Glaubwürdigkeiten</option>
|
||||
<option value="sehr_hoch">Sehr hoch</option>
|
||||
<option value="hoch">Hoch</option>
|
||||
<option value="gemischt">Gemischt</option>
|
||||
@@ -529,15 +529,15 @@
|
||||
<option value="sehr_niedrig">Sehr niedrig</option>
|
||||
<option value="na">Nicht eingeordnet</option>
|
||||
</select>
|
||||
<label for="sources-filter-extern" class="sr-only">Externe Reputation filtern</label>
|
||||
<label for="sources-filter-extern" class="sr-only" data-i18n="sources_modal.filter.extern">Externe Reputation filtern</label>
|
||||
<select id="sources-filter-extern" class="timeline-filter-select" onchange="App.filterSources()">
|
||||
<option value="">Externe Reputation: alle</option>
|
||||
<option value="" data-i18n="sources_modal.filter.extern_all">Externe Reputation: alle</option>
|
||||
<option value="ifcn">IFCN-Faktenchecker</option>
|
||||
<option value="eu_disinfo">EU-Desinfo gelistet</option>
|
||||
</select>
|
||||
<label for="sources-filter-alignment" class="sr-only">Geopolitische Nähe filtern</label>
|
||||
<label for="sources-filter-alignment" class="sr-only" data-i18n="sources_modal.filter.alignment">Geopolitische Nähe filtern</label>
|
||||
<select id="sources-filter-alignment" class="timeline-filter-select" onchange="App.filterSources()">
|
||||
<option value="">Alle Nähen</option>
|
||||
<option value="" data-i18n="sources_modal.filter.alignment_all">Alle Nähen</option>
|
||||
<option value="prorussisch">Prorussisch</option>
|
||||
<option value="proiranisch">Proiranisch</option>
|
||||
<option value="prowestlich">Prowestlich</option>
|
||||
@@ -551,11 +551,11 @@
|
||||
<option value="neutral">Neutral</option>
|
||||
<option value="sonstige">Sonstige</option>
|
||||
</select>
|
||||
<label for="sources-search" class="sr-only">Quellen durchsuchen</label>
|
||||
<input type="text" id="sources-search" class="timeline-filter-input sources-search-input" placeholder="Suche..." oninput="App.filterSources()">
|
||||
<label for="sources-search" class="sr-only" data-i18n="sources_modal.search">Quellen durchsuchen</label>
|
||||
<input type="text" id="sources-search" class="timeline-filter-input sources-search-input" placeholder="Suche..." oninput="App.filterSources()" data-i18n-attr="placeholder:sources_modal.search_placeholder">
|
||||
</div>
|
||||
<div class="sources-toolbar-actions">
|
||||
<button class="btn btn-primary btn-small" onclick="App.toggleSourceForm()">+ Quelle</button>
|
||||
<button class="btn btn-primary btn-small" onclick="App.toggleSourceForm()" data-i18n="sources_modal.add_source">+ Quelle</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -564,10 +564,10 @@
|
||||
<div class="sources-add-form" id="sources-add-form" style="display:none;">
|
||||
<div class="sources-form-row">
|
||||
<div class="form-group flex-1">
|
||||
<label for="src-discover-url">URL oder Domain</label>
|
||||
<input type="text" id="src-discover-url" placeholder="z.B. netzpolitik.org oder t.me/kanalname">
|
||||
<label for="src-discover-url" data-i18n="sources_modal.form.url_label">URL oder Domain</label>
|
||||
<input type="text" id="src-discover-url" placeholder="z.B. netzpolitik.org oder t.me/kanalname" data-i18n-attr="placeholder:sources_modal.form.url_placeholder">
|
||||
</div>
|
||||
<button class="btn btn-secondary btn-small" id="src-discover-btn" onclick="App.discoverSource()">Erkennen</button>
|
||||
<button class="btn btn-secondary btn-small" id="src-discover-btn" onclick="App.discoverSource()" data-i18n="sources_modal.form.discover">Erkennen</button>
|
||||
</div>
|
||||
|
||||
<!-- Ergebnis-Anzeige (nach Discovery) -->
|
||||
@@ -575,10 +575,10 @@
|
||||
<div class="sources-add-form-grid">
|
||||
<div class="form-group">
|
||||
<label for="src-name">Name</label>
|
||||
<input type="text" id="src-name" placeholder="Wird erkannt...">
|
||||
<input type="text" id="src-name" placeholder="Wird erkannt..." data-i18n-attr="placeholder:sources_modal.form.name_placeholder">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="src-category">Kategorie</label>
|
||||
<label for="src-category" data-i18n="sources_modal.form.category">Kategorie</label>
|
||||
<select id="src-category">
|
||||
<option value="nachrichtenagentur">Nachrichtenagentur</option>
|
||||
<option value="oeffentlich-rechtlich">Öffentlich-Rechtlich</option>
|
||||
@@ -597,7 +597,7 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Typ</label>
|
||||
<label data-i18n="sources_modal.form.type">Typ</label>
|
||||
<input type="text" id="src-type-display" class="input-readonly" readonly>
|
||||
<select id="src-type-select" style="display:none">
|
||||
<option value="rss_feed">RSS-Feed</option>
|
||||
@@ -606,28 +606,28 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group" id="src-rss-url-group">
|
||||
<label>RSS-Feed URL</label>
|
||||
<label data-i18n="sources_modal.form.rss_url">RSS-Feed URL</label>
|
||||
<input type="text" id="src-rss-url" class="input-readonly" readonly>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Domain</label>
|
||||
<label data-i18n="sources_modal.form.domain">Domain</label>
|
||||
<input type="text" id="src-domain" class="input-readonly" readonly>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="src-notes">Notizen</label>
|
||||
<input type="text" id="src-notes" placeholder="Optional">
|
||||
<label for="src-notes" data-i18n="sources_modal.form.notes">Notizen</label>
|
||||
<input type="text" id="src-notes" placeholder="Optional" data-i18n-attr="placeholder:sources_modal.form.notes_placeholder">
|
||||
</div>
|
||||
</div>
|
||||
<div class="sources-discovery-actions">
|
||||
<button class="btn btn-primary btn-small" onclick="App.saveSource()">Speichern</button>
|
||||
<button class="btn btn-secondary btn-small" onclick="App.toggleSourceForm(false)">Abbrechen</button>
|
||||
<button class="btn btn-primary btn-small" onclick="App.saveSource()" data-i18n="common.save">Speichern</button>
|
||||
<button class="btn btn-secondary btn-small" onclick="App.toggleSourceForm(false)" data-i18n="common.cancel">Abbrechen</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quellen-Liste (gruppiert) -->
|
||||
<div class="sources-list" id="sources-list">
|
||||
<div class="empty-state-text" style="padding:var(--sp-3xl);text-align:center;">Lade Quellen...</div>
|
||||
<div class="empty-state-text" style="padding:var(--sp-3xl);text-align:center;" data-i18n="sources_modal.list.loading">Lade Quellen...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -683,26 +683,26 @@
|
||||
</div>
|
||||
|
||||
<!-- Chat-Assistent Widget -->
|
||||
<button class="chat-toggle-btn" id="chat-toggle-btn" title="Chat-Assistent" aria-label="Chat-Assistent oeffnen">
|
||||
<button class="chat-toggle-btn" id="chat-toggle-btn" title="Chat-Assistent" aria-label="Chat-Assistent oeffnen" data-i18n-attr="title:chat.toggle_title,aria-label:chat.toggle_aria">
|
||||
<svg viewBox="0 0 24 24"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H5.2L4 17.2V4h16v12z"/></svg>
|
||||
</button>
|
||||
<div class="chat-window" id="chat-window">
|
||||
<div class="chat-header">
|
||||
<span class="chat-header-title">AegisSight Assistent</span>
|
||||
<span class="chat-header-title" data-i18n="chat.title">AegisSight Assistent</span>
|
||||
<div class="chat-header-actions">
|
||||
<button class="chat-header-btn chat-reset-btn" id="chat-reset-btn" title="Neuer Chat" aria-label="Neuen Chat starten" style="display:none">
|
||||
<button class="chat-header-btn chat-reset-btn" id="chat-reset-btn" title="Neuer Chat" aria-label="Neuen Chat starten" style="display:none" data-i18n-attr="title:chat.new_title,aria-label:chat.new_aria">
|
||||
<svg viewBox="0 0 24 24" width="15" height="15"><path d="M17.65 6.35A7.958 7.958 0 0012 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08A5.99 5.99 0 0112 18c-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" fill="currentColor"/></svg>
|
||||
</button>
|
||||
<button class="chat-header-btn" id="chat-fullscreen-btn" title="Vollbild" aria-label="Vollbild umschalten">
|
||||
<button class="chat-header-btn" id="chat-fullscreen-btn" title="Vollbild" aria-label="Vollbild umschalten" data-i18n-attr="title:chat.fullscreen_title,aria-label:chat.fullscreen_aria">
|
||||
<svg viewBox="0 0 24 24" width="15" height="15"><path d="M7 14H5v5h5v-2H7v-3zm-2-4h2V7h3V5H5v5zm12 7h-3v2h5v-5h-2v3zM14 5v2h3v3h2V5h-5z" fill="currentColor"/></svg>
|
||||
</button>
|
||||
<button class="chat-header-btn chat-header-close" id="chat-close-btn" title="Schließen" aria-label="Chat schließen">×</button>
|
||||
<button class="chat-header-btn chat-header-close" id="chat-close-btn" title="Schließen" aria-label="Chat schließen" data-i18n-attr="title:chat.close_title,aria-label:chat.close_aria">×</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chat-messages" id="chat-messages"></div>
|
||||
<form class="chat-input-area" id="chat-form" autocomplete="off">
|
||||
<textarea id="chat-input" rows="1" placeholder="Frage stellen..." maxlength="2000"></textarea>
|
||||
<button type="submit" class="chat-send-btn" title="Senden" aria-label="Nachricht senden">
|
||||
<textarea id="chat-input" rows="1" placeholder="Frage stellen..." maxlength="2000" data-i18n-attr="placeholder:chat.input_placeholder"></textarea>
|
||||
<button type="submit" class="chat-send-btn" title="Senden" aria-label="Nachricht senden" data-i18n-attr="title:chat.send_title,aria-label:chat.send_aria">
|
||||
<svg viewBox="0 0 24 24"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
|
||||
</button>
|
||||
</form>
|
||||
@@ -726,13 +726,13 @@
|
||||
<script src="/static/js/i18n.js?v=20260513a"></script>
|
||||
<script src="/static/js/api.js?v=20260423a"></script>
|
||||
<script src="/static/js/ws.js?v=20260316b"></script>
|
||||
<script src="/static/js/components.js?v=20260513d"></script>
|
||||
<script src="/static/js/components.js?v=20260514e"></script>
|
||||
<script src="/static/js/layout.js?v=20260513f"></script>
|
||||
<script src="/static/js/pipeline.js?v=20260513d"></script>
|
||||
<script src="/static/js/app.js?v=20260514c"></script>
|
||||
<script src="/static/js/app.js?v=20260514e"></script>
|
||||
<script src="/static/js/cluster-data.js?v=20260322f"></script>
|
||||
<script src="/static/js/tutorial.js?v=20260316z"></script>
|
||||
<script src="/static/js/chat.js?v=20260422a"></script>
|
||||
<script src="/static/js/chat.js?v=20260514e"></script>
|
||||
<script>document.addEventListener("DOMContentLoaded",function(){Chat.init();/* Tutorial.init() wird in App.init() nach Sprachwahl aufgerufen, damit es bei englischen Orgs unterdrueckt werden kann */});</script>
|
||||
|
||||
<!-- Map Fullscreen Overlay -->
|
||||
@@ -753,26 +753,26 @@
|
||||
<div class="modal-overlay" id="modal-export" role="dialog" aria-modal="true">
|
||||
<div class="modal" style="max-width:420px;">
|
||||
<div class="modal-header">
|
||||
<h3>Bericht exportieren</h3>
|
||||
<button class="modal-close" onclick="closeModal('modal-export')">×</button>
|
||||
<h3 data-i18n="modal.export.title">Bericht exportieren</h3>
|
||||
<button class="modal-close" onclick="closeModal('modal-export')" aria-label="Schließen" data-i18n-attr="aria-label:aria.close">×</button>
|
||||
</div>
|
||||
<div class="modal-body" style="padding:20px;">
|
||||
<div style="margin-bottom:16px;">
|
||||
<label style="font-size:11px;text-transform:uppercase;letter-spacing:1px;color:var(--text-secondary);display:block;margin-bottom:8px;">Bereiche</label>
|
||||
<label class="export-radio"><input type="checkbox" name="export-section" value="zusammenfassung" checked><span>Zusammenfassung</span></label>
|
||||
<label class="export-radio"><input type="checkbox" name="export-section" value="bericht" checked><span>Recherchebericht / Lagebild</span></label>
|
||||
<label class="export-radio"><input type="checkbox" name="export-section" value="faktencheck" checked><span>Faktencheck</span></label>
|
||||
<label class="export-radio"><input type="checkbox" name="export-section" value="quellen" checked><span>Quellen</span></label>
|
||||
<label style="font-size:11px;text-transform:uppercase;letter-spacing:1px;color:var(--text-secondary);display:block;margin-bottom:8px;" data-i18n="export.sections">Bereiche</label>
|
||||
<label class="export-radio"><input type="checkbox" name="export-section" value="zusammenfassung" checked><span data-i18n="export.section.summary">Zusammenfassung</span></label>
|
||||
<label class="export-radio"><input type="checkbox" name="export-section" value="bericht" checked><span data-i18n="export.section.report">Recherchebericht / Lagebild</span></label>
|
||||
<label class="export-radio"><input type="checkbox" name="export-section" value="faktencheck" checked><span data-i18n="export.section.factcheck">Faktencheck</span></label>
|
||||
<label class="export-radio"><input type="checkbox" name="export-section" value="quellen" checked><span data-i18n="export.section.sources">Quellen</span></label>
|
||||
</div>
|
||||
<div style="margin-bottom:16px;">
|
||||
<label style="font-size:11px;text-transform:uppercase;letter-spacing:1px;color:var(--text-secondary);display:block;margin-bottom:8px;">Format</label>
|
||||
<label style="font-size:11px;text-transform:uppercase;letter-spacing:1px;color:var(--text-secondary);display:block;margin-bottom:8px;" data-i18n="export.format">Format</label>
|
||||
<label class="export-radio"><input type="radio" name="export-format" value="pdf" checked><span>PDF</span></label>
|
||||
<label class="export-radio"><input type="radio" name="export-format" value="docx"><span>Word (DOCX)</span></label>
|
||||
<label class="export-radio"><input type="radio" name="export-format" value="docx"><span data-i18n="export.format.docx">Word (DOCX)</span></label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer" style="padding:12px 20px;display:flex;justify-content:flex-end;gap:8px;border-top:1px solid var(--border);">
|
||||
<button class="btn btn-secondary" onclick="closeModal('modal-export')">Abbrechen</button>
|
||||
<button class="btn btn-primary" id="export-submit-btn" onclick="App.submitExport()">Exportieren</button>
|
||||
<button class="btn btn-secondary" onclick="closeModal('modal-export')" data-i18n="common.cancel">Abbrechen</button>
|
||||
<button class="btn btn-primary" id="export-submit-btn" onclick="App.submitExport()" data-i18n="export.submit">Exportieren</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren