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 @@
+
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 `
${catLabel}
+ ${classificationBadges ? `
${classificationBadges}` : ''}
${feedCountBadge}
${!isGlobal && !hasMultiple && feeds[0]?.id ? `` : ''}