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:
@@ -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 (?, ?, ?, ?, ?, ?, ?, ?, ?)""",
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
In neuem Issue referenzieren
Einen Benutzer sperren