feat(sources): Quellenuebersicht der Lage nach Typ filterbar
Die Quellenuebersicht innerhalb einer Lage zeigt jetzt Filter-Chips (Alle / Web / Telegram / X) und blendet die Quellen-Boxen nach Quellentyp ein und aus. Die Chips erscheinen nur, wenn neben Web auch Telegram- oder X-Quellen vorkommen. - sources-summary-Endpoint liefert pro Quelle einen source_type, abgeleitet aus dem source-Praefix (X: / Telegram: / sonst Web) - Filter-Chips und data-type in renderSourceOverviewFromSummary - App.filterSourceOverview blendet die Boxen nach Typ - Chip-Styles in style.css Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -1715,6 +1715,39 @@ a.dev-source-pill:hover {
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.source-type-filter-chips {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--sp-xs);
|
||||
margin: var(--sp-sm) 0 var(--sp-xs);
|
||||
}
|
||||
|
||||
.source-type-filter-chip {
|
||||
font: inherit;
|
||||
font-size: 11px;
|
||||
padding: 3px 10px;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--border);
|
||||
background: var(--bg-secondary);
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.source-type-filter-chip:hover {
|
||||
border-color: var(--accent);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.source-type-filter-chip.active {
|
||||
background: var(--accent);
|
||||
border-color: var(--accent);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.source-type-filter-chip.active strong {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.source-overview-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<link rel="stylesheet" href="/static/vendor/leaflet.css">
|
||||
<link rel="stylesheet" href="/static/vendor/MarkerCluster.css">
|
||||
<link rel="stylesheet" href="/static/vendor/MarkerCluster.Default.css">
|
||||
<link rel="stylesheet" href="/static/css/style.css?v=20260501h">
|
||||
<link rel="stylesheet" href="/static/css/style.css?v=20260522c">
|
||||
<style>
|
||||
/* Export Modal Radio */
|
||||
.export-radio { display:flex; align-items:center; gap:10px; padding:8px 12px; cursor:pointer; border-radius:var(--radius-sm); transition:background 0.15s; border:1px solid transparent; margin-bottom:4px; }
|
||||
@@ -807,10 +807,10 @@
|
||||
<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=20260522b"></script>
|
||||
<script src="/static/js/components.js?v=20260522c"></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=20260522a"></script>
|
||||
<script src="/static/js/app.js?v=20260522c"></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=20260514e"></script>
|
||||
|
||||
@@ -909,6 +909,26 @@ const App = {
|
||||
}
|
||||
},
|
||||
|
||||
/** Quellenuebersicht der Lage nach Quellentyp filtern (Web/Telegram/X). */
|
||||
filterSourceOverview(type, chipEl) {
|
||||
const content = document.getElementById('source-overview-content');
|
||||
if (!content) return;
|
||||
content.querySelectorAll('.source-type-filter-chip').forEach(c => c.classList.remove('active'));
|
||||
if (chipEl) chipEl.classList.add('active');
|
||||
// ein offenes Detail-Panel schliessen
|
||||
const det = content.querySelector('.source-overview-detail');
|
||||
if (det) det.remove();
|
||||
content.querySelectorAll('.source-overview-item.active').forEach(it => {
|
||||
it.classList.remove('active');
|
||||
it.setAttribute('aria-expanded', 'false');
|
||||
});
|
||||
// Quellen-Boxen nach Typ ein-/ausblenden
|
||||
content.querySelectorAll('.source-overview-item').forEach(it => {
|
||||
const t = it.dataset.type || 'web';
|
||||
it.style.display = (!type || t === type) ? '' : 'none';
|
||||
});
|
||||
},
|
||||
|
||||
/** Klick auf eine Quellen-Box: Liste der Artikel inline aufklappen (mutual-exclusive). */
|
||||
toggleSourceOverviewDetail(el) {
|
||||
if (!el) return;
|
||||
|
||||
@@ -1034,11 +1034,32 @@ const UI = {
|
||||
html += `<div class="source-lang-chips">${langChips}</div>`;
|
||||
html += `</div>`;
|
||||
|
||||
// Typ-Filter-Chips (nur zeigen, wenn neben Web auch Telegram/X vorkommt)
|
||||
const typeCounts = { web: 0, telegram: 0, x: 0 };
|
||||
data.sources.forEach(s => {
|
||||
const t = s.source_type || 'web';
|
||||
typeCounts[t] = (typeCounts[t] || 0) + 1;
|
||||
});
|
||||
if (typeCounts.telegram > 0 || typeCounts.x > 0) {
|
||||
const typeMeta = [
|
||||
{ key: '', label: 'Alle', count: data.sources.length },
|
||||
{ key: 'web', label: 'Web', count: typeCounts.web },
|
||||
{ key: 'telegram', label: 'Telegram', count: typeCounts.telegram },
|
||||
{ key: 'x', label: 'X', count: typeCounts.x },
|
||||
];
|
||||
const chips = typeMeta
|
||||
.filter(t => t.key === '' || t.count > 0)
|
||||
.map(t => `<button type="button" class="source-type-filter-chip${t.key === '' ? ' active' : ''}" data-type="${t.key}" onclick="App.filterSourceOverview('${t.key}', this)">${t.label} <strong>${t.count}</strong></button>`)
|
||||
.join('');
|
||||
html += `<div class="source-type-filter-chips">${chips}</div>`;
|
||||
}
|
||||
|
||||
html += '<div class="source-overview-grid">';
|
||||
data.sources.forEach(s => {
|
||||
const langs = (s.languages || ['de']).map(l => (l || 'de').toUpperCase()).join('/');
|
||||
const sourceName = this.escape(s.source || 'Unbekannt');
|
||||
html += `<div class="source-overview-item" data-source="${sourceName}" tabindex="0" role="button" aria-expanded="false" onclick="App.toggleSourceOverviewDetail(this)" onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();App.toggleSourceOverviewDetail(this);}">
|
||||
const sType = s.source_type || 'web';
|
||||
html += `<div class="source-overview-item" data-source="${sourceName}" data-type="${sType}" tabindex="0" role="button" aria-expanded="false" onclick="App.toggleSourceOverviewDetail(this)" onkeydown="if(event.key==='Enter'||event.key===' '){event.preventDefault();App.toggleSourceOverviewDetail(this);}">
|
||||
<span class="source-overview-name">${sourceName}</span>
|
||||
<span class="source-overview-lang">${langs}</span>
|
||||
<span class="source-overview-count">${s.article_count}</span>
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren