fix: justify-content: center überall wiederhergestellt + Quellen-Duplikatprüfung

- CSS: 24x fälschliches flex-start zurück auf center (Login, Buttons, Modals, Badges, Map etc.)
- Sources: Domain-Duplikatprüfung bei manuellem Hinzufügen (web_source 1x pro Domain, Domain aus URL extrahieren)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
Claude Dev
2026-03-18 00:13:36 +01:00
Ursprung 17088e588f
Commit 6b4af4cf2a
2 geänderte Dateien mit 47 neuen und 26 gelöschten Zeilen

Datei anzeigen

@@ -415,12 +415,14 @@ async def create_source(
"""Neue Quelle hinzufuegen (org-spezifisch).""" """Neue Quelle hinzufuegen (org-spezifisch)."""
tenant_id = current_user.get("tenant_id") tenant_id = current_user.get("tenant_id")
# Domain normalisieren (Subdomain-Aliase auflösen) # Domain normalisieren (Subdomain-Aliase auflösen, aus URL extrahieren)
domain = data.domain domain = data.domain
if not domain and data.url:
domain = _extract_domain(data.url)
if domain: if domain:
domain = _DOMAIN_ALIASES.get(domain.lower(), domain.lower()) domain = _DOMAIN_ALIASES.get(domain.lower(), domain.lower())
# Duplikat-Prüfung: gleiche URL bereits vorhanden? # Duplikat-Prüfung 1: gleiche URL bereits vorhanden? (tenant-übergreifend)
if data.url: if data.url:
cursor = await db.execute( cursor = await db.execute(
"SELECT id, name FROM sources WHERE url = ? AND status = 'active'", "SELECT id, name FROM sources WHERE url = ? AND status = 'active'",
@@ -433,6 +435,25 @@ async def create_source(
detail=f"Feed-URL bereits vorhanden: {existing['name']} (ID {existing['id']})", detail=f"Feed-URL bereits vorhanden: {existing['name']} (ID {existing['id']})",
) )
# Duplikat-Prüfung 2: Domain bereits vorhanden? (tenant-übergreifend)
if domain:
cursor = await db.execute(
"SELECT id, name, source_type FROM sources WHERE LOWER(domain) = ? AND status = 'active' AND (tenant_id IS NULL OR tenant_id = ?) LIMIT 1",
(domain.lower(), tenant_id),
)
domain_existing = await cursor.fetchone()
if domain_existing:
if data.source_type == "web_source":
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=f"Web-Quelle für '{domain}' bereits vorhanden: {domain_existing['name']}",
)
if not data.url:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail=f"Domain '{domain}' bereits als Quelle vorhanden: {domain_existing['name']}. Für einen neuen RSS-Feed bitte die Feed-URL angeben.",
)
cursor = await db.execute( cursor = await db.execute(
"""INSERT INTO sources (name, url, domain, source_type, category, status, notes, added_by, tenant_id) """INSERT INTO sources (name, url, domain, source_type, category, status, notes, added_by, tenant_id)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""", VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""",

Datei anzeigen

@@ -235,7 +235,7 @@ a:hover {
.login-container { .login-container {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
min-height: 100vh; min-height: 100vh;
padding: var(--sp-3xl); padding: var(--sp-3xl);
background: var(--bg-primary); background: var(--bg-primary);
@@ -356,7 +356,7 @@ a:hover {
.btn { .btn {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
gap: var(--sp-md); gap: var(--sp-md);
border: none; border: none;
border-radius: var(--radius); border-radius: var(--radius);
@@ -1092,7 +1092,7 @@ a:hover {
border-radius: var(--radius); border-radius: var(--radius);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
font-size: 12px; font-size: 12px;
font-weight: 700; font-weight: 700;
margin-top: 1px; margin-top: 1px;
@@ -1547,7 +1547,7 @@ a:hover {
.vt-cluster-count { .vt-cluster-count {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
min-width: 18px; min-width: 18px;
height: 18px; height: 18px;
padding: 0 5px; padding: 0 5px;
@@ -1720,7 +1720,7 @@ a:hover {
backdrop-filter: blur(4px); backdrop-filter: blur(4px);
z-index: 100; z-index: 100;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
} }
.modal-overlay.active { .modal-overlay.active {
@@ -1857,7 +1857,7 @@ a:hover {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
padding: var(--sp-5xl) var(--sp-4xl); padding: var(--sp-5xl) var(--sp-4xl);
text-align: center; text-align: center;
} }
@@ -1900,7 +1900,7 @@ a:hover {
.loading-overlay { .loading-overlay {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
gap: var(--sp-lg); gap: var(--sp-lg);
padding: var(--sp-3xl); padding: var(--sp-3xl);
color: var(--text-secondary); color: var(--text-secondary);
@@ -1978,7 +1978,7 @@ a:hover {
.progress-label-container { .progress-label-container {
display: flex; display: flex;
justify-content: flex-start; justify-content: center;
align-items: center; align-items: center;
gap: var(--sp-md); gap: var(--sp-md);
position: relative; position: relative;
@@ -2642,7 +2642,7 @@ a:hover {
border-radius: var(--radius); border-radius: var(--radius);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
transition: color 0.2s ease, background 0.2s ease; transition: color 0.2s ease, background 0.2s ease;
position: relative; position: relative;
} }
@@ -2667,7 +2667,7 @@ a:hover {
border-radius: 8px; border-radius: 8px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
line-height: 1; line-height: 1;
pointer-events: none; pointer-events: none;
animation: badgePop 0.3s ease; animation: badgePop 0.3s ease;
@@ -2769,7 +2769,7 @@ a:hover {
border-radius: var(--radius); border-radius: var(--radius);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
font-size: 11px; font-size: 11px;
font-weight: 700; font-weight: 700;
flex-shrink: 0; flex-shrink: 0;
@@ -3421,7 +3421,7 @@ a:hover {
.source-feed-count { .source-feed-count {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
padding: 1px 8px; padding: 1px 8px;
border-radius: 9px; border-radius: 9px;
font-size: 11px; font-size: 11px;
@@ -3932,7 +3932,7 @@ select:focus-visible, textarea:focus-visible,
border-radius: var(--radius); border-radius: var(--radius);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
transition: color 0.2s ease, background 0.2s ease; transition: color 0.2s ease, background 0.2s ease;
width: 36px; width: 36px;
height: 36px; height: 36px;
@@ -4348,7 +4348,7 @@ select:focus-visible, textarea:focus-visible,
.map-empty { .map-empty {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
height: 100%; height: 100%;
color: var(--text-tertiary); color: var(--text-tertiary);
font-size: 13px; font-size: 13px;
@@ -4443,7 +4443,7 @@ a.map-popup-article:hover {
border-radius: 50%; border-radius: 50%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
} }
.map-cluster span { .map-cluster span {
font-family: var(--font-body); font-family: var(--font-body);
@@ -4539,7 +4539,7 @@ a.map-popup-article:hover {
padding: 0; padding: 0;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
flex-shrink: 0; flex-shrink: 0;
} }
.map-expand-btn:hover { .map-expand-btn:hover {
@@ -4660,7 +4660,7 @@ a.map-popup-article:hover {
z-index: 9999; z-index: 9999;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
box-shadow: 0 4px 16px rgba(0,0,0,0.3); box-shadow: 0 4px 16px rgba(0,0,0,0.3);
transition: transform 0.2s, background 0.2s; transition: transform 0.2s, background 0.2s;
} }
@@ -4727,7 +4727,7 @@ a.map-popup-article:hover {
border-radius: 4px; border-radius: 4px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
} }
.chat-header-btn:hover { .chat-header-btn:hover {
color: var(--text-primary); color: var(--text-primary);
@@ -4819,7 +4819,7 @@ a.map-popup-article:hover {
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
flex-shrink: 0; flex-shrink: 0;
transition: background 0.15s; transition: background 0.15s;
} }
@@ -4895,7 +4895,7 @@ a.map-popup-article:hover {
.info-icon { .info-icon {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
width: 16px; width: 16px;
height: 16px; height: 16px;
color: var(--text-disabled); color: var(--text-disabled);
@@ -5103,7 +5103,7 @@ a.map-popup-article:hover {
cursor: pointer; cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
border-radius: var(--radius); border-radius: var(--radius);
transition: color 0.15s, background 0.15s; transition: color 0.15s, background 0.15s;
line-height: 1; line-height: 1;
@@ -5117,7 +5117,7 @@ a.map-popup-article:hover {
.tutorial-bubble-dots { .tutorial-bubble-dots {
display: flex; display: flex;
gap: 5px; gap: 5px;
justify-content: flex-start; justify-content: center;
margin-bottom: var(--sp-lg); margin-bottom: var(--sp-lg);
flex-wrap: wrap; flex-wrap: wrap;
} }
@@ -5294,7 +5294,7 @@ body.tutorial-active .tutorial-cursor {
background: rgba(0,0,0,0.6); background: rgba(0,0,0,0.6);
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: center;
backdrop-filter: blur(2px); backdrop-filter: blur(2px);
} }
.tutorial-resume-dialog { .tutorial-resume-dialog {
@@ -5315,7 +5315,7 @@ body.tutorial-active .tutorial-cursor {
.tutorial-resume-actions { .tutorial-resume-actions {
display: flex; display: flex;
gap: 12px; gap: 12px;
justify-content: flex-start; justify-content: center;
} }
.tutorial-resume-actions .tutorial-btn { .tutorial-resume-actions .tutorial-btn {
border: 1px solid var(--accent); border: 1px solid var(--accent);