feat(sources): UI fuer Quellen-Klassifikation (Filter, Badges, Edit-Form)
- Quellen-Modal: 4 neue Filter (Politik, Medientyp, Reliability, Alignment). - Edit-Form: Selects fuer political_orientation/media_type/reliability, Multi-Select-Chips fuer alignments, Toggle state_affiliated, Country-Code-Input. - renderSourceGroup: Politik-Badge mit DACH-Farbskala (rot=L, blau=R), Reliability-Punkt (gruen→rot), Alignment-Tags, state-affiliated-Indikator. Tooltip um alle 4 Achsen erweitert. - CSS-Block fuer alle neuen Badge-/Chip-Styles. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -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) {
|
||||
|
||||
@@ -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(`<span class="source-political-badge pol-${this.escape(pol)}" title="${this.escape(label.full)}">${this.escape(label.short)}</span>`);
|
||||
}
|
||||
const rel = feed.reliability;
|
||||
if (rel && rel !== 'na') {
|
||||
parts.push(`<span class="source-reliability-dot rel-${this.escape(rel)}" title="Glaubwürdigkeit: ${this.escape(this._reliabilityLabels[rel] || rel)}" aria-label="Glaubwürdigkeit: ${this.escape(this._reliabilityLabels[rel] || rel)}"></span>`);
|
||||
}
|
||||
if (feed.state_affiliated) {
|
||||
parts.push(`<span class="source-state-badge" title="Staatsnah/-kontrolliert" aria-label="Staatsnah">⚑</span>`);
|
||||
}
|
||||
const aligns = Array.isArray(feed.alignments) ? feed.alignments : [];
|
||||
aligns.forEach(a => {
|
||||
const label = this._alignmentLabels[a] || a;
|
||||
parts.push(`<span class="source-alignment-chip-badge align-${this.escape(a)}">${this.escape(label)}</span>`);
|
||||
});
|
||||
return parts.join('');
|
||||
},
|
||||
|
||||
/**
|
||||
* Domain-Gruppe rendern (aufklappbar mit Feeds).
|
||||
*/
|
||||
@@ -1117,20 +1196,44 @@ const UI = {
|
||||
? `<span class="source-feed-count">${feedCount} Feed${feedCount !== 1 ? 's' : ''}</span>`
|
||||
: '';
|
||||
|
||||
// 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 = ` <span class="info-icon tooltip-below" data-tooltip="${tooltipText}"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg></span>`;
|
||||
}
|
||||
|
||||
const classificationBadges = this._renderClassificationBadges(firstFeed);
|
||||
|
||||
return `<div class="source-group">
|
||||
<div class="source-group-header" ${toggleAttr}>
|
||||
${toggleIcon}
|
||||
@@ -1138,6 +1241,7 @@ const UI = {
|
||||
<span class="source-group-name">${this.escape(displayName)}</span>${infoButtonHtml}
|
||||
</div>
|
||||
<span class="source-category-badge cat-${feeds[0]?.category || 'sonstige'}">${catLabel}</span>
|
||||
${classificationBadges ? `<span class="source-classification-badges">${classificationBadges}</span>` : ''}
|
||||
${feedCountBadge}
|
||||
<div class="source-group-actions" onclick="event.stopPropagation()">
|
||||
${!isGlobal && !hasMultiple && feeds[0]?.id ? `<button class="source-edit-btn" onclick="App.editSource(${feeds[0].id})" title="Bearbeiten" aria-label="Bearbeiten">✎</button>` : ''}
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren