Per-User Domain-Ausschlüsse + Grundquellen-Schutz
- Neue Tabelle user_excluded_domains für benutzerspezifische Ausschlüsse - Domain-Ausschlüsse wirken nur für den jeweiligen User, nicht org-weit - user_id wird durch die gesamte Pipeline geschleust (Orchestrator → Researcher → RSS-Parser) - Grundquellen (is_global) können nicht mehr bearbeitet/gelöscht werden im Frontend - Grundquelle-Badge bei globalen Quellen statt Edit/Delete-Buttons - Filter Von mir ausgeschlossen im Quellen-Modal Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Dieser Commit ist enthalten in:
@@ -425,7 +425,7 @@ const App = {
|
||||
_currentUsername: '',
|
||||
_allSources: [],
|
||||
_sourcesOnly: [],
|
||||
_blacklistOnly: [],
|
||||
_myExclusions: [], // [{domain, notes, created_at}]
|
||||
_expandedGroups: new Set(),
|
||||
_editingSourceId: null,
|
||||
_timelineFilter: 'all',
|
||||
@@ -2173,13 +2173,14 @@ const App = {
|
||||
|
||||
async loadSources() {
|
||||
try {
|
||||
const [sources, stats] = await Promise.all([
|
||||
const [sources, stats, myExclusions] = await Promise.all([
|
||||
API.listSources(),
|
||||
API.getSourceStats(),
|
||||
API.getMyExclusions(),
|
||||
]);
|
||||
this._allSources = sources;
|
||||
this._sourcesOnly = sources.filter(s => s.source_type !== 'excluded');
|
||||
this._blacklistOnly = sources.filter(s => s.source_type === 'excluded');
|
||||
this._myExclusions = myExclusions || [];
|
||||
|
||||
this.renderSourceStats(stats);
|
||||
this.renderSourceList();
|
||||
@@ -2194,7 +2195,7 @@ const App = {
|
||||
|
||||
const rss = stats.by_type.rss_feed || { count: 0, articles: 0 };
|
||||
const web = stats.by_type.web_source || { count: 0, articles: 0 };
|
||||
const excluded = this._blacklistOnly.length;
|
||||
const excluded = this._myExclusions.length;
|
||||
|
||||
bar.innerHTML = `
|
||||
<span class="sources-stat-item"><span class="sources-stat-value">${rss.count}</span> RSS-Feeds</span>
|
||||
@@ -2221,12 +2222,12 @@ const App = {
|
||||
const excludedDomains = new Set();
|
||||
const excludedNotes = {};
|
||||
|
||||
// Blacklist-Domains sammeln
|
||||
this._blacklistOnly.forEach(s => {
|
||||
const domain = (s.domain || s.name || '').toLowerCase();
|
||||
// User-Ausschlüsse sammeln
|
||||
this._myExclusions.forEach(e => {
|
||||
const domain = (e.domain || '').toLowerCase();
|
||||
if (domain) {
|
||||
excludedDomains.add(domain);
|
||||
excludedNotes[domain] = s.notes || '';
|
||||
excludedNotes[domain] = e.notes || '';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2238,10 +2239,10 @@ const App = {
|
||||
});
|
||||
|
||||
// Ausgeschlossene Domains die keine Feeds haben auch als Gruppe
|
||||
this._blacklistOnly.forEach(s => {
|
||||
const domain = (s.domain || s.name || '').toLowerCase();
|
||||
this._myExclusions.forEach(e => {
|
||||
const domain = (e.domain || '').toLowerCase();
|
||||
if (domain && !groups.has(domain)) {
|
||||
groups.set(domain, [s]);
|
||||
groups.set(domain, []);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -2249,6 +2250,7 @@ const App = {
|
||||
let filteredGroups = [];
|
||||
for (const [domain, feeds] of groups) {
|
||||
const isExcluded = excludedDomains.has(domain);
|
||||
const isGlobal = feeds.some(f => f.is_global);
|
||||
|
||||
// Typ-Filter
|
||||
if (typeFilter === 'excluded' && !isExcluded) continue;
|
||||
@@ -2271,7 +2273,7 @@ const App = {
|
||||
if (!groupText.includes(search)) continue;
|
||||
}
|
||||
|
||||
filteredGroups.push({ domain, feeds, isExcluded });
|
||||
filteredGroups.push({ domain, feeds, isExcluded, isGlobal });
|
||||
}
|
||||
|
||||
if (filteredGroups.length === 0) {
|
||||
@@ -2286,7 +2288,7 @@ const App = {
|
||||
});
|
||||
|
||||
list.innerHTML = filteredGroups.map(g =>
|
||||
UI.renderSourceGroup(g.domain, g.feeds, g.isExcluded, excludedNotes[g.domain] || '')
|
||||
UI.renderSourceGroup(g.domain, g.feeds, g.isExcluded, excludedNotes[g.domain] || '', g.isGlobal)
|
||||
).join('');
|
||||
|
||||
// Erweiterte Gruppen wiederherstellen
|
||||
@@ -2440,11 +2442,11 @@ const App = {
|
||||
* Domain direkt ausschließen (aus der Gruppenliste).
|
||||
*/
|
||||
async blockDomainDirect(domain) {
|
||||
if (!await confirmDialog(`"${domain}" wirklich ausschließen? Alle Feeds dieser Domain werden deaktiviert.`)) return;
|
||||
if (!await confirmDialog(`"${domain}" wirklich ausschließen? Artikel dieser Domain werden bei deinen Recherchen ignoriert.`)) return;
|
||||
|
||||
try {
|
||||
await API.blockDomain(domain);
|
||||
UI.showToast(`${domain} gesperrt.`, 'success');
|
||||
UI.showToast(`${domain} ausgeschlossen.`, 'success');
|
||||
await this.loadSources();
|
||||
this.updateSidebarStats();
|
||||
} catch (err) {
|
||||
@@ -2560,7 +2562,7 @@ const App = {
|
||||
|
||||
// Prüfen ob Domain ausgeschlossen ist
|
||||
const inputDomain = url.replace(/^https?:\/\//, '').replace(/^www\./, '').split('/')[0].toLowerCase();
|
||||
const isBlocked = inputDomain && this._blacklistOnly.some(s => (s.domain || '').toLowerCase() === inputDomain);
|
||||
const isBlocked = inputDomain && this._myExclusions.some(e => (e.domain || '').toLowerCase() === inputDomain);
|
||||
|
||||
if (isBlocked) {
|
||||
if (!await confirmDialog(`"${inputDomain}" ist ausgeschlossen. Trotzdem hinzufügen? Der Ausschluss wird dabei aufgehoben.`)) return;
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren