diff --git a/src/static/css/style.css b/src/static/css/style.css index f232fac..3bc671c 100644 --- a/src/static/css/style.css +++ b/src/static/css/style.css @@ -3503,6 +3503,117 @@ a.dev-source-pill:hover { color: var(--info); } +/* Klassifikations-Badges (politisch / reliability / alignments / state) */ +.source-classification-badges { + display: inline-flex; + align-items: center; + gap: 4px; + flex-wrap: wrap; +} + +.source-political-badge { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 22px; + padding: 2px 6px; + border-radius: var(--radius); + font-size: 10px; + font-weight: 700; + letter-spacing: 0.4px; + color: #fff; + background: #9e9e9e; +} +.source-political-badge.pol-links_extrem { background: #b71c1c; } +.source-political-badge.pol-links { background: #e53935; } +.source-political-badge.pol-mitte_links { background: #ef9a9a; color: #4a0d0d; } +.source-political-badge.pol-liberal { background: #fdd835; color: #4a3700; } +.source-political-badge.pol-mitte { background: #9e9e9e; } +.source-political-badge.pol-konservativ { background: #90caf9; color: #0d2740; } +.source-political-badge.pol-mitte_rechts { background: #5c6bc0; } +.source-political-badge.pol-rechts { background: #1976d2; } +.source-political-badge.pol-rechts_extrem { background: #0d47a1; } + +.source-reliability-dot { + display: inline-block; + width: 10px; + height: 10px; + border-radius: 50%; + background: #9e9e9e; + border: 1px solid rgba(0, 0, 0, 0.15); +} +.source-reliability-dot.rel-sehr_hoch { background: #2e7d32; } +.source-reliability-dot.rel-hoch { background: #66bb6a; } +.source-reliability-dot.rel-gemischt { background: #fbc02d; } +.source-reliability-dot.rel-niedrig { background: #ef6c00; } +.source-reliability-dot.rel-sehr_niedrig { background: #c62828; } + +.source-state-badge { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + border-radius: 50%; + background: #4a148c; + color: #fff; + font-size: 11px; + line-height: 1; +} + +.source-alignment-chip-badge { + display: inline-flex; + align-items: center; + padding: 1px 6px; + border-radius: 999px; + font-size: 10px; + font-weight: 500; + background: var(--cat-sonstige-bg, #eef); + color: var(--text-secondary, #555); + border: 1px solid rgba(0, 0, 0, 0.08); +} + +/* Edit-Form: Klassifikations-Sektion */ +.sources-classification-section { + margin-top: 12px; + padding-top: 12px; + border-top: 1px solid var(--border-color, rgba(0,0,0,0.08)); +} +.sources-classification-header { + font-size: 12px; + font-weight: 600; + color: var(--text-secondary, #555); + margin-bottom: 8px; + letter-spacing: 0.3px; + text-transform: uppercase; +} +.alignment-chips { + display: flex; + flex-wrap: wrap; + gap: 6px; +} +.alignment-chip { + display: inline-flex; + align-items: center; + padding: 4px 10px; + border-radius: 999px; + font-size: 11px; + font-weight: 500; + background: transparent; + color: var(--text-secondary, #555); + border: 1px solid var(--border-color, rgba(0,0,0,0.15)); + cursor: pointer; + transition: all 0.12s ease; +} +.alignment-chip:hover { + background: var(--cat-sonstige-bg, #eef); +} +.alignment-chip.active { + background: var(--primary, #2a81cb); + color: #fff; + border-color: var(--primary, #2a81cb); +} + /* Typ-Badges */ .source-type-badge { display: inline-flex; diff --git a/src/static/dashboard.html b/src/static/dashboard.html index 4737350..43a81dd 100644 --- a/src/static/dashboard.html +++ b/src/static/dashboard.html @@ -481,6 +481,70 @@ + + + + + + + + @@ -548,6 +612,89 @@ +
+
Einordnung
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+ +
+ + + + + + + + + + + + +
+
+
diff --git a/src/static/js/app.js b/src/static/js/app.js index 0e65c4f..1aff794 100644 --- a/src/static/js/app.js +++ b/src/static/js/app.js @@ -2750,6 +2750,10 @@ async handleRefresh() { // Filter anwenden const typeFilter = document.getElementById('sources-filter-type')?.value || ''; const catFilter = document.getElementById('sources-filter-category')?.value || ''; + const politicalFilter = document.getElementById('sources-filter-political')?.value || ''; + const mediaTypeFilter = document.getElementById('sources-filter-mediatype')?.value || ''; + const reliabilityFilter = document.getElementById('sources-filter-reliability')?.value || ''; + const alignmentFilter = document.getElementById('sources-filter-alignment')?.value || ''; const search = (document.getElementById('sources-search')?.value || '').toLowerCase(); // Alle Quellen nach Domain gruppieren @@ -2800,6 +2804,20 @@ async handleRefresh() { if (!hasMatchingCat) continue; } + // Klassifikations-Filter + if (politicalFilter) { + if (!feeds.some(f => (f.political_orientation || 'na') === politicalFilter)) continue; + } + if (mediaTypeFilter) { + if (!feeds.some(f => (f.media_type || 'sonstige') === mediaTypeFilter)) continue; + } + if (reliabilityFilter) { + if (!feeds.some(f => (f.reliability || 'na') === reliabilityFilter)) continue; + } + if (alignmentFilter) { + if (!feeds.some(f => Array.isArray(f.alignments) && f.alignments.includes(alignmentFilter))) continue; + } + // Suche if (search) { const groupText = feeds.map(f => @@ -3054,6 +3072,13 @@ async handleRefresh() { document.getElementById('src-discover-btn').disabled = false; document.getElementById('src-discover-btn').textContent = 'Erkennen'; document.getElementById('src-type-select').value = 'rss_feed'; + // Klassifikations-Felder auf Default zurücksetzen + const polEl = document.getElementById('src-political'); if (polEl) polEl.value = 'na'; + const mtEl = document.getElementById('src-mediatype'); if (mtEl) mtEl.value = 'sonstige'; + const relEl = document.getElementById('src-reliability'); if (relEl) relEl.value = 'na'; + const ccEl = document.getElementById('src-country'); if (ccEl) ccEl.value = ''; + const saEl = document.getElementById('src-state-affiliated'); if (saEl) saEl.checked = false; + this._setAlignmentChips([]); // Save-Button Text zurücksetzen const saveBtn = document.querySelector('#src-discovery-result .sources-discovery-actions .btn-primary'); if (saveBtn) saveBtn.textContent = 'Speichern'; @@ -3235,6 +3260,19 @@ async handleRefresh() { rss_url: source.url, }; + // Klassifikations-Felder setzen + const polEl = document.getElementById('src-political'); + if (polEl) polEl.value = source.political_orientation || 'na'; + const mtEl = document.getElementById('src-mediatype'); + if (mtEl) mtEl.value = source.media_type || 'sonstige'; + const relEl = document.getElementById('src-reliability'); + if (relEl) relEl.value = source.reliability || 'na'; + const ccEl = document.getElementById('src-country'); + if (ccEl) ccEl.value = source.country_code || ''; + const saEl = document.getElementById('src-state-affiliated'); + if (saEl) saEl.checked = !!source.state_affiliated; + this._setAlignmentChips(source.alignments || []); + // Submit-Button-Text ändern const saveBtn = document.querySelector('#src-discovery-result .sources-discovery-actions .btn-primary'); if (saveBtn) saveBtn.textContent = 'Quelle speichern'; @@ -3243,6 +3281,27 @@ async handleRefresh() { if (form) form.scrollIntoView({ behavior: 'smooth', block: 'start' }); }, + _setAlignmentChips(active) { + const chips = document.querySelectorAll('#src-alignments-chips .alignment-chip'); + const set = new Set((active || []).map(a => (a || '').toLowerCase())); + chips.forEach(chip => { + if (set.has(chip.dataset.alignment)) chip.classList.add('active'); + else chip.classList.remove('active'); + }); + }, + + _getAlignmentChips() { + return Array.from(document.querySelectorAll('#src-alignments-chips .alignment-chip.active')) + .map(chip => chip.dataset.alignment); + }, + + handleAlignmentChipClick(e) { + const chip = e.target.closest('.alignment-chip'); + if (!chip) return; + e.preventDefault(); + chip.classList.toggle('active'); + }, + async saveSource() { const name = document.getElementById('src-name').value.trim(); if (!name) { @@ -3258,6 +3317,12 @@ async handleRefresh() { url: discovered.rss_url || (discovered.source_type === 'telegram_channel' ? (document.getElementById('src-domain').value || null) : null), domain: document.getElementById('src-domain').value.trim() || discovered.domain || null, notes: document.getElementById('src-notes').value.trim() || null, + political_orientation: document.getElementById('src-political')?.value || 'na', + media_type: document.getElementById('src-mediatype')?.value || 'sonstige', + reliability: document.getElementById('src-reliability')?.value || 'na', + country_code: (document.getElementById('src-country')?.value || '').trim().toUpperCase() || null, + state_affiliated: !!document.getElementById('src-state-affiliated')?.checked, + alignments: this._getAlignmentChips(), }; if (!data.domain && discovered.domain) { diff --git a/src/static/js/components.js b/src/static/js/components.js index b32dce0..d0a2cd8 100644 --- a/src/static/js/components.js +++ b/src/static/js/components.js @@ -1062,6 +1062,85 @@ const UI = { 'sonstige': 'Sonstige', }, + _politicalLabels: { + links_extrem: { short: 'L+', full: 'Links (extrem)' }, + links: { short: 'L', full: 'Links' }, + mitte_links: { short: 'ML', full: 'Mitte-Links' }, + liberal: { short: 'LIB', full: 'Liberal' }, + mitte: { short: 'M', full: 'Mitte' }, + konservativ: { short: 'KON', full: 'Konservativ' }, + mitte_rechts: { short: 'MR', full: 'Mitte-Rechts' }, + rechts: { short: 'R', full: 'Rechts' }, + rechts_extrem: { short: 'R+', full: 'Rechts (extrem)' }, + na: { short: '?', full: 'Nicht eingeordnet' }, + }, + _reliabilityLabels: { + sehr_hoch: 'Sehr hoch', + hoch: 'Hoch', + gemischt: 'Gemischt', + niedrig: 'Niedrig', + sehr_niedrig: 'Sehr niedrig', + na: 'Nicht eingeordnet', + }, + _mediaTypeLabels: { + tageszeitung: 'Tageszeitung', + wochenzeitung: 'Wochenzeitung', + magazin: 'Magazin', + tv_sender: 'TV-Sender', + radio: 'Radio', + oeffentlich_rechtlich: 'Öffentlich-Rechtlich', + nachrichtenagentur: 'Nachrichtenagentur', + online_only: 'Online-only', + blog: 'Blog', + telegram_kanal: 'Telegram-Kanal', + telegram_bot: 'Telegram-Bot', + podcast: 'Podcast', + social_media: 'Social Media', + imageboard: 'Imageboard', + think_tank: 'Think Tank', + ngo: 'NGO', + behoerde: 'Behörde', + staatsmedium: 'Staatsmedium', + fachmedium: 'Fachmedium', + sonstige: 'Sonstige', + }, + _alignmentLabels: { + prorussisch: 'prorussisch', + proiranisch: 'proiranisch', + prowestlich: 'prowestlich', + proukrainisch: 'proukrainisch', + prochinesisch: 'prochinesisch', + projapanisch: 'projapanisch', + proisraelisch: 'proisraelisch', + propalaestinensisch: 'propalästinensisch', + protuerkisch: 'protürkisch', + panarabisch: 'panarabisch', + neutral: 'neutral', + sonstige: 'sonstige', + }, + + _renderClassificationBadges(feed) { + const parts = []; + const pol = feed.political_orientation; + if (pol && pol !== 'na') { + const label = this._politicalLabels[pol] || { short: pol, full: pol }; + parts.push(`${this.escape(label.short)}`); + } + const rel = feed.reliability; + if (rel && rel !== 'na') { + parts.push(``); + } + if (feed.state_affiliated) { + parts.push(``); + } + const aligns = Array.isArray(feed.alignments) ? feed.alignments : []; + aligns.forEach(a => { + const label = this._alignmentLabels[a] || a; + parts.push(`${this.escape(label)}`); + }); + return parts.join(''); + }, + /** * Domain-Gruppe rendern (aufklappbar mit Feeds). */ @@ -1117,20 +1196,44 @@ const UI = { ? `${feedCount} Feed${feedCount !== 1 ? 's' : ''}` : ''; - // Info-Button mit Tooltip (Typ, Sprache, Ausrichtung) + // Info-Button mit Tooltip (Typ, Sprache, Ausrichtung, Klassifikation) let infoButtonHtml = ''; const firstFeed = feeds[0] || {}; - const hasInfo = firstFeed.language || firstFeed.bias; + const hasInfo = firstFeed.language || firstFeed.bias + || (firstFeed.political_orientation && firstFeed.political_orientation !== 'na') + || (firstFeed.media_type && firstFeed.media_type !== 'sonstige') + || (firstFeed.reliability && firstFeed.reliability !== 'na') + || firstFeed.state_affiliated + || firstFeed.country_code + || (Array.isArray(firstFeed.alignments) && firstFeed.alignments.length > 0); if (hasInfo) { - const typeMap = { rss_feed: 'RSS-Feed', web_source: 'Web-Quelle', telegram_channel: 'Telegram-Kanal' }; + const typeMap = { rss_feed: 'RSS-Feed', web_source: 'Web-Quelle', telegram_channel: 'Telegram-Kanal', podcast_feed: 'Podcast' }; const lines = []; lines.push('Typ: ' + (typeMap[firstFeed.source_type] || firstFeed.source_type || 'Unbekannt')); if (firstFeed.language) lines.push('Sprache: ' + firstFeed.language); - if (firstFeed.bias) lines.push('Ausrichtung: ' + firstFeed.bias); + if (firstFeed.country_code) lines.push('Land: ' + firstFeed.country_code); + if (firstFeed.media_type && firstFeed.media_type !== 'sonstige') { + lines.push('Medientyp: ' + (this._mediaTypeLabels[firstFeed.media_type] || firstFeed.media_type)); + } + if (firstFeed.political_orientation && firstFeed.political_orientation !== 'na') { + const pl = this._politicalLabels[firstFeed.political_orientation]; + lines.push('Politisch: ' + (pl ? pl.full : firstFeed.political_orientation)); + } + if (firstFeed.reliability && firstFeed.reliability !== 'na') { + lines.push('Glaubwürdigkeit: ' + (this._reliabilityLabels[firstFeed.reliability] || firstFeed.reliability)); + } + if (firstFeed.state_affiliated) lines.push('Staatsnah: ja'); + if (Array.isArray(firstFeed.alignments) && firstFeed.alignments.length > 0) { + const labels = firstFeed.alignments.map(a => this._alignmentLabels[a] || a); + lines.push('Geopolitische Nähe: ' + labels.join(', ')); + } + if (firstFeed.bias) lines.push('Notiz: ' + firstFeed.bias); const tooltipText = this.escape(lines.join('\n')); infoButtonHtml = ` `; } + const classificationBadges = this._renderClassificationBadges(firstFeed); + return `
${toggleIcon} @@ -1138,6 +1241,7 @@ const UI = { ${this.escape(displayName)}${infoButtonHtml}
${catLabel} + ${classificationBadges ? `${classificationBadges}` : ''} ${feedCountBadge}
${!isGlobal && !hasMultiple && feeds[0]?.id ? `` : ''}