Neue Kategorie russische-opposition in Verwaltungs-UI + 10 neue Telegram-Kanaele (Wave 3)
Dieser Commit ist enthalten in:
@@ -4,7 +4,7 @@
|
||||
let globalSourcesCache = [];
|
||||
let tenantSourcesCache = [];
|
||||
let editingSourceId = null;
|
||||
let globalSortField = "name";
|
||||
let globalSortField = "category";
|
||||
let globalSortAsc = true;
|
||||
|
||||
const CATEGORY_LABELS = {
|
||||
@@ -18,11 +18,21 @@ const CATEGORY_LABELS = {
|
||||
regional: "Regional",
|
||||
boulevard: "Boulevard",
|
||||
sonstige: "Sonstige",
|
||||
"cybercrime": "Cybercrime / Hacktivismus",
|
||||
"cybercrime-leaks": "Cybercrime / Leaks",
|
||||
"ukraine-russland-krieg": "Ukraine-Russland-Krieg",
|
||||
"irankonflikt": "Irankonflikt",
|
||||
"osint-international": "OSINT International",
|
||||
"extremismus-deutschland": "Extremismus Deutschland",
|
||||
"russische-staatspropaganda": "Russische Staatspropaganda",
|
||||
"russische-opposition": "Russische Opposition / Exilmedien",
|
||||
"syrien-nahost": "Syrien / Nahost",
|
||||
};
|
||||
|
||||
const TYPE_LABELS = {
|
||||
rss_feed: "RSS-Feed",
|
||||
web_source: "Webquelle",
|
||||
telegram_channel: "Telegram-Kanal",
|
||||
excluded: "Ausgeschlossen",
|
||||
};
|
||||
|
||||
@@ -65,25 +75,49 @@ async function loadGlobalSources() {
|
||||
|
||||
function renderGlobalSources(sources) {
|
||||
const tbody = document.getElementById("globalSourceTable");
|
||||
const cols = 7;
|
||||
if (sources.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="8" class="text-muted">Keine Grundquellen</td></tr>';
|
||||
tbody.innerHTML = `<tr><td colspan="${cols}" class="text-muted">Keine Grundquellen</td></tr>`;
|
||||
return;
|
||||
}
|
||||
tbody.innerHTML = sources.map((s) => `
|
||||
<tr>
|
||||
<td>${esc(s.name)}</td>
|
||||
<td class="text-secondary" style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${esc(s.url || '')}">${esc(s.url || "-")}</td>
|
||||
<td>${esc(s.domain || "-")}</td>
|
||||
<td>${TYPE_LABELS[s.source_type] || s.source_type}</td>
|
||||
<td>${CATEGORY_LABELS[s.category] || s.category}</td>
|
||||
<td class="text-right">${s.article_count || 0}</td>
|
||||
<td><span class="badge badge-${s.status === "active" ? "active" : "inactive"}">${s.status === "active" ? "Aktiv" : "Inaktiv"}</span></td>
|
||||
<td>
|
||||
<button class="btn btn-secondary btn-small" onclick="editGlobalSource(${s.id})">Bearbeiten</button>
|
||||
<button class="btn btn-danger btn-small" onclick="confirmDeleteGlobalSource(${s.id}, '${esc(s.name)}')">Löschen</button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join("");
|
||||
|
||||
// Nach Kategorie gruppieren (Reihenfolge beibehalten)
|
||||
const grouped = {};
|
||||
const order = [];
|
||||
sources.forEach((s) => {
|
||||
const cat = s.category || "sonstige";
|
||||
if (!grouped[cat]) { grouped[cat] = []; order.push(cat); }
|
||||
grouped[cat].push(s);
|
||||
});
|
||||
|
||||
let html = "";
|
||||
order.forEach((cat) => {
|
||||
const label = CATEGORY_LABELS[cat] || cat;
|
||||
const count = grouped[cat].length;
|
||||
html += `<tr class="cat-header-row"><td colspan="${cols}"><span class="cat-header-label">${esc(label)}</span><span class="cat-header-count">${count}</span></td></tr>`;
|
||||
grouped[cat].forEach((s) => {
|
||||
const hasNotes = s.notes && s.notes.trim();
|
||||
const infoBtn = hasNotes
|
||||
? `<span class="src-info-toggle" onclick="toggleSourceInfo(${s.id})" title="Info einblenden">ⓘ</span>`
|
||||
: '';
|
||||
const notesRow = hasNotes
|
||||
? `<tr class="src-notes-row" id="notes-${s.id}" style="display:none;"><td colspan="${cols}" class="src-notes-cell">${esc(s.notes)}</td></tr>`
|
||||
: '';
|
||||
html += `<tr>
|
||||
<td>${infoBtn} ${esc(s.name)}</td>
|
||||
<td class="text-secondary" style="max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;" title="${esc(s.url || '')}">${esc(s.url || "-")}</td>
|
||||
<td>${esc(s.domain || "-")}</td>
|
||||
<td>${TYPE_LABELS[s.source_type] || s.source_type}</td>
|
||||
<td class="text-right">${s.article_count || 0}</td>
|
||||
<td><span class="badge badge-${s.status === "active" ? "active" : "inactive"}">${s.status === "active" ? "Aktiv" : "Inaktiv"}</span></td>
|
||||
<td>
|
||||
<button class="btn btn-secondary btn-small" onclick="editGlobalSource(${s.id})">Bearbeiten</button>
|
||||
<button class="btn btn-danger btn-small" onclick="confirmDeleteGlobalSource(${s.id}, '${esc(s.name)}')">Löschen</button>
|
||||
</td>
|
||||
</tr>${notesRow}`;
|
||||
});
|
||||
});
|
||||
tbody.innerHTML = html;
|
||||
|
||||
document.getElementById("globalSourceCount").textContent = `${sources.length} Grundquellen`;
|
||||
|
||||
@@ -384,3 +418,15 @@ async function addDiscoveredFeeds() {
|
||||
btn.textContent = "Ausgewählte hinzufügen";
|
||||
}
|
||||
}
|
||||
|
||||
function toggleSourceInfo(id) {
|
||||
const row = document.getElementById("notes-" + id);
|
||||
if (!row) return;
|
||||
const isVisible = row.style.display !== "none";
|
||||
row.style.display = isVisible ? "none" : "table-row";
|
||||
const mainRow = row.previousElementSibling;
|
||||
if (mainRow) {
|
||||
const btn = mainRow.querySelector(".src-info-toggle");
|
||||
if (btn) btn.classList.toggle("active", !isVisible);
|
||||
}
|
||||
}
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren