Promote develop → main (2026-05-01 16:09 UTC) #10
@@ -1704,6 +1704,72 @@ a.dev-source-pill:hover {
|
|||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
background: var(--bg-primary);
|
background: var(--bg-primary);
|
||||||
border: 1px solid var(--border);
|
border: 1px solid var(--border);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.15s ease, background 0.15s ease;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
.source-overview-item:hover {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: var(--bg-elevated);
|
||||||
|
}
|
||||||
|
.source-overview-item:focus-visible {
|
||||||
|
box-shadow: 0 0 0 2px var(--tint-accent-strong);
|
||||||
|
}
|
||||||
|
.source-overview-item.active {
|
||||||
|
border-color: var(--accent);
|
||||||
|
background: var(--tint-accent-subtle);
|
||||||
|
box-shadow: var(--glow-accent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Inline-Aufklapp-Bereich (volle Reihen-Breite, direkt unter dem geklickten Item) */
|
||||||
|
.source-overview-detail {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
padding: var(--sp-md) var(--sp-lg);
|
||||||
|
background: var(--bg-elevated);
|
||||||
|
border: 1px solid var(--accent);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
animation: source-detail-in 0.18s ease;
|
||||||
|
}
|
||||||
|
@keyframes source-detail-in {
|
||||||
|
from { opacity: 0; transform: translateY(-4px); }
|
||||||
|
to { opacity: 1; transform: translateY(0); }
|
||||||
|
}
|
||||||
|
.source-overview-detail-empty {
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
.source-overview-detail-list {
|
||||||
|
list-style: none;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
max-height: 320px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
.source-overview-detail-list::-webkit-scrollbar { width: 6px; }
|
||||||
|
.source-overview-detail-list::-webkit-scrollbar-track { background: var(--bg-primary); border-radius: 3px; }
|
||||||
|
.source-overview-detail-list::-webkit-scrollbar-thumb { background: var(--text-disabled); border-radius: 3px; }
|
||||||
|
.source-overview-detail-list li {
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1.4;
|
||||||
|
padding: 2px 0;
|
||||||
|
border-top: 1px dashed var(--border);
|
||||||
|
}
|
||||||
|
.source-overview-detail-list li:first-child { border-top: none; }
|
||||||
|
.source-overview-detail-list li a {
|
||||||
|
color: var(--text-primary);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
.source-overview-detail-list li a:hover {
|
||||||
|
color: var(--accent);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.source-overview-detail { animation: none; }
|
||||||
|
.source-overview-item { transition: none; }
|
||||||
}
|
}
|
||||||
|
|
||||||
.source-overview-name {
|
.source-overview-name {
|
||||||
|
|||||||
@@ -13,7 +13,7 @@
|
|||||||
<link rel="stylesheet" href="/static/vendor/leaflet.css">
|
<link rel="stylesheet" href="/static/vendor/leaflet.css">
|
||||||
<link rel="stylesheet" href="/static/vendor/MarkerCluster.css">
|
<link rel="stylesheet" href="/static/vendor/MarkerCluster.css">
|
||||||
<link rel="stylesheet" href="/static/vendor/MarkerCluster.Default.css">
|
<link rel="stylesheet" href="/static/vendor/MarkerCluster.Default.css">
|
||||||
<link rel="stylesheet" href="/static/css/style.css?v=20260501e">
|
<link rel="stylesheet" href="/static/css/style.css?v=20260501f">
|
||||||
<style>
|
<style>
|
||||||
/* Export Modal Radio */
|
/* 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; }
|
.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; }
|
||||||
@@ -647,7 +647,7 @@
|
|||||||
<script src="/static/js/components.js?v=20260427a"></script>
|
<script src="/static/js/components.js?v=20260427a"></script>
|
||||||
<script src="/static/js/layout.js?v=20260316b"></script>
|
<script src="/static/js/layout.js?v=20260316b"></script>
|
||||||
<script src="/static/js/pipeline.js?v=20260501a"></script>
|
<script src="/static/js/pipeline.js?v=20260501a"></script>
|
||||||
<script src="/static/js/app.js?v=20260501e"></script>
|
<script src="/static/js/app.js?v=20260501f"></script>
|
||||||
<script src="/static/js/cluster-data.js?v=20260322f"></script>
|
<script src="/static/js/cluster-data.js?v=20260322f"></script>
|
||||||
<script src="/static/js/tutorial.js?v=20260316z"></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=20260422a"></script>
|
||||||
|
|||||||
@@ -866,6 +866,54 @@ const App = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/** Klick auf eine Quellen-Box: Liste der Artikel inline aufklappen (mutual-exclusive). */
|
||||||
|
toggleSourceOverviewDetail(el) {
|
||||||
|
if (!el) return;
|
||||||
|
const grid = el.parentElement;
|
||||||
|
if (!grid) return;
|
||||||
|
const sourceName = el.dataset.source || '';
|
||||||
|
const wasActive = el.classList.contains('active');
|
||||||
|
|
||||||
|
// Alle anderen schliessen + bestehendes Detail entfernen
|
||||||
|
grid.querySelectorAll('.source-overview-item.active').forEach(it => {
|
||||||
|
it.classList.remove('active');
|
||||||
|
it.setAttribute('aria-expanded', 'false');
|
||||||
|
});
|
||||||
|
const existingDetail = grid.querySelector('.source-overview-detail');
|
||||||
|
if (existingDetail) existingDetail.remove();
|
||||||
|
|
||||||
|
// Wenn das geklickte Item bereits aktiv war: nur schliessen
|
||||||
|
if (wasActive) return;
|
||||||
|
|
||||||
|
// Neues Detail einfuegen direkt nach dem geklickten Item
|
||||||
|
el.classList.add('active');
|
||||||
|
el.setAttribute('aria-expanded', 'true');
|
||||||
|
|
||||||
|
const articles = (this._currentArticles || [])
|
||||||
|
.filter(a => (a.source || 'Unbekannt') === sourceName)
|
||||||
|
.sort((a, b) => {
|
||||||
|
const ta = new Date(a.collected_at || a.published_at || 0).getTime();
|
||||||
|
const tb = new Date(b.collected_at || b.published_at || 0).getTime();
|
||||||
|
return tb - ta;
|
||||||
|
});
|
||||||
|
|
||||||
|
const detail = document.createElement('div');
|
||||||
|
detail.className = 'source-overview-detail';
|
||||||
|
if (articles.length === 0) {
|
||||||
|
detail.innerHTML = '<div class="source-overview-detail-empty">Keine Artikel gefunden.</div>';
|
||||||
|
} else {
|
||||||
|
const items = articles.map(a => {
|
||||||
|
const headline = UI.escape(a.headline_de || a.headline || '(ohne Titel)');
|
||||||
|
if (a.source_url) {
|
||||||
|
return `<li><a href="${UI.escape(a.source_url)}" target="_blank" rel="noopener">${headline}</a></li>`;
|
||||||
|
}
|
||||||
|
return `<li>${headline}</li>`;
|
||||||
|
}).join('');
|
||||||
|
detail.innerHTML = `<ul class="source-overview-detail-list">${items}</ul>`;
|
||||||
|
}
|
||||||
|
el.insertAdjacentElement('afterend', detail);
|
||||||
|
},
|
||||||
|
|
||||||
/** Restliche Artikel seitenweise im Hintergrund nachladen und in _currentArticles mergen. */
|
/** Restliche Artikel seitenweise im Hintergrund nachladen und in _currentArticles mergen. */
|
||||||
async _loadRemainingArticlesInBackground(incidentId) {
|
async _loadRemainingArticlesInBackground(incidentId) {
|
||||||
const BATCH = 500;
|
const BATCH = 500;
|
||||||
|
|||||||
@@ -971,8 +971,9 @@ const UI = {
|
|||||||
html += '<div class="source-overview-grid">';
|
html += '<div class="source-overview-grid">';
|
||||||
data.sources.forEach(s => {
|
data.sources.forEach(s => {
|
||||||
const langs = (s.languages || ['de']).map(l => (l || 'de').toUpperCase()).join('/');
|
const langs = (s.languages || ['de']).map(l => (l || 'de').toUpperCase()).join('/');
|
||||||
html += `<div class="source-overview-item">
|
const sourceName = this.escape(s.source || 'Unbekannt');
|
||||||
<span class="source-overview-name">${this.escape(s.source || 'Unbekannt')}</span>
|
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);}">
|
||||||
|
<span class="source-overview-name">${sourceName}</span>
|
||||||
<span class="source-overview-lang">${langs}</span>
|
<span class="source-overview-lang">${langs}</span>
|
||||||
<span class="source-overview-count">${s.article_count}</span>
|
<span class="source-overview-count">${s.article_count}</span>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren