Team-Verwaltung Runde 2: bestehende Mitglieder importieren + Kategorien/Seitentitel editierbar
Nutzer-Wunsch 21.08.2026: "wenn ich eine änderung mache ist es auch für
jeden für die die schon da sind und die neuen" + "ich will ich titel
und namen von kategorien und seiten ändern können in der verwaltung".
Backend (braucht Filipes manuellen Deploy):
- Migration 0008: team_members bekommt intro/bioHtml/extraCta-Spalten
-- volle Feld-Parität mit den von Hand gepflegten Profilen (Diene/
Patrick/Bananenstift/Marina nutzen diese Felder).
- routes/team.js: neue importLegacyMember()-Funktion -- übernimmt ein
bestehendes Profil 1:1 in die Datenbank, OHNE erneut zu übersetzen
(die vorhandenen, von Hand geschriebenen Übersetzungen bleiben
erhalten). Idempotent: mehrfacher Import erzeugt keine Duplikate.
- routes/site-texts.js (neu): admin-editierbare Kategorie-Namen und
Seitentitel, generischer key->{de,...}-Override über app_settings
(wie "Event des Jahres"), fester Schlüssel-Katalog aus
Sicherheitsgründen. Leeres Feld setzt auf den Standardtext zurück.
- 27 Tests in isolierter Testumgebung geprüft (kein better-sqlite3
lokal kompilierbar).
Frontend:
- window.dogiTeamZusammenfuehren() (main.js): admin-Mitglieder
ÜBERSCHREIBEN jetzt gleichnamige statische Einträge (per slug) statt
sie zu duplizieren -- eine Bearbeitung wirkt dadurch für alle.
- window.dogiSiteTexteLaden() + applyTranslations() erweitert um
data-site-text-key -- Kategorie-Seitentitel (team-modis.html/
team-scouts.html/creator.html/manager.html) sind jetzt live editierbar.
- Verwaltung: "📥 Bestehende Mitglieder importieren"-Knopf (holt VanVan/
Diene/Funny/Miss/Marina/Ghost/Patrick/Bananenstift aus den data-*.js-
Dateien, VanVan bewusst ausgenommen -- eigene Sonderkarte + Seite),
erweitertes Formular (Intro/ausführliche Vorstellung/zweiter Button,
eingeklappt unter "Erweitert"), neue Sektion "Kategorien &
Seitentitel" (4 Karten, sofort wirksam auf Website UND Verwaltung).
Beim Testen einen echten UX-Bug gefunden und gefixt: die "Gespeichert"-
Meldung nach dem Speichern eines Mitglieds wurde von der direkt
anschließenden Formular-Zurücksetzung sofort wieder überschrieben und
war nie sichtbar.
Cache-Busting-Version auf 20260821k erhöht.
Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
@@ -18,10 +18,10 @@
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
<meta name="robots" content="noindex" />
|
||||
<title>Seite nicht gefunden — DOGFATHER UNIVERSE</title>
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="site-header"></div>
|
||||
@@ -41,7 +41,7 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/i18n-404.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-404.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+7
-7
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Unterstütze Dogfather dauerhaft mit der DogiCrew 🩵 und sammle exklusive Treueprämien — die Teddy-Kollektion entsteht in Kooperation mit VanVan." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
<style>
|
||||
/* =====================================================================
|
||||
Preis-/Anmelde-Kiste — komplett neu gestaltet (Nutzer-Feedback
|
||||
@@ -454,9 +454,9 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/i18n-abonnieren.js?v=20260821j"></script>
|
||||
<script src="assets/js/supporter.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-abonnieren.js?v=20260821k"></script>
|
||||
<script src="assets/js/supporter.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
<script>
|
||||
(function () {
|
||||
const S = window.DogiSupporter;
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
<meta name="robots" content="noindex" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="site-header"></div>
|
||||
@@ -134,6 +134,6 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+7
-7
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Detailansicht einer Aktion oder eines Projekts von Team Dogi." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="site-header"></div>
|
||||
@@ -51,9 +51,9 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/data-aktionen.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-aktion-detail.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/data-aktionen.js?v=20260821k"></script>
|
||||
<script src="assets/js/i18n-aktion-detail.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const params = new URLSearchParams(location.search);
|
||||
|
||||
@@ -199,6 +199,22 @@
|
||||
if (text) el.setAttribute(attr, text);
|
||||
});
|
||||
});
|
||||
|
||||
// 21.08.2026 (Nutzer-Wunsch: "ich will ich titel und namen von
|
||||
// kategorien und seiten ändern können in der verwaltung"): admin-
|
||||
// editierbare Text-Overrides (siehe window.dogiSiteTexteLaden() weiter
|
||||
// unten). Läuft bewusst NACH den normalen data-i18n-Standardtexten
|
||||
// oben, damit ein gesetzter Override sie überschreiben kann. Fehlt ein
|
||||
// Override (Normalfall, noch nichts in der Verwaltung eingetragen),
|
||||
// bleibt einfach der eingebaute Standardtext stehen -- diese Funktion
|
||||
// hier macht dann schlicht nichts.
|
||||
const siteTexte = window.__siteTexteCache || {};
|
||||
document.querySelectorAll("[data-site-text-key]").forEach((el) => {
|
||||
const eintrag = siteTexte[el.getAttribute("data-site-text-key")];
|
||||
if (!eintrag) return;
|
||||
const text = eintrag[sprache] || eintrag.de;
|
||||
if (text) el.innerHTML = text;
|
||||
});
|
||||
}
|
||||
|
||||
/* ---------- Navigationsstruktur (an einer Stelle pflegen) ---------- */
|
||||
@@ -1052,6 +1068,7 @@
|
||||
|
||||
await skripteUebernehmen(dok);
|
||||
applyTranslations(); // seiteneigene data-i18n-Elemente der neuen Seite übersetzen
|
||||
if (window.dogiSiteTexteLaden) window.dogiSiteTexteLaden(); // Kategorie-/Seitentitel-Overrides der neuen Seite
|
||||
}
|
||||
|
||||
function nahtloseNavigationAktivieren() {
|
||||
@@ -1123,6 +1140,7 @@
|
||||
musikKnopfFarbenAnpassen();
|
||||
nahtloseNavigationAktivieren();
|
||||
applyTranslations(); // seiteneigene data-i18n-Elemente (z.B. Startseite) übersetzen
|
||||
if (window.dogiSiteTexteLaden) window.dogiSiteTexteLaden(); // Kategorie-/Seitentitel-Overrides (siehe Verwaltung)
|
||||
});
|
||||
|
||||
/* Kleine öffentliche Schnittstelle für seiteneigene Inline-Skripte
|
||||
@@ -1189,12 +1207,19 @@
|
||||
role: m.rolle || "",
|
||||
photo: m.foto || "",
|
||||
avatar: m.foto || "",
|
||||
// 21.08.2026 (Runde 2, "wenn ich eine änderung mache ist es auch
|
||||
// für jeden"): volle Feld-Parität mit den von Hand gepflegten
|
||||
// Profilen, damit ein importiertes/bearbeitetes Profil genau so
|
||||
// reichhaltig sein kann wie die schon bestehenden.
|
||||
intro: m.intro || "",
|
||||
bio: m.bio || "",
|
||||
bioHtml: m.bioHtml || "",
|
||||
quote: m.zitat || "",
|
||||
social: m.tiktok ? { tiktok: m.tiktok } : {},
|
||||
status: "aktiv", // /team liefert ohnehin nur aktive Mitglieder
|
||||
rank: m.rang || undefined,
|
||||
rankLabel: m.rangLabel || undefined,
|
||||
extraCta: m.extraCta || undefined,
|
||||
// Markiert admin-gepflegte Einträge (z.B. für profil.html, das
|
||||
// sonst nicht zwischen fest einprogrammierten und dynamischen
|
||||
// Personen unterscheiden könnte).
|
||||
@@ -1205,6 +1230,41 @@
|
||||
}
|
||||
};
|
||||
|
||||
/* 21.08.2026 (Runde 2, Nutzer-Wunsch: "wenn ich eine änderung mache ist
|
||||
es auch für jeden für die die schon da sind und die neuen"): fügt eine
|
||||
per dogiTeamLaden() geladene Liste NICHT einfach hinten an die von Hand
|
||||
gepflegte Liste an, sondern ÜBERSCHREIBT darin jeden Eintrag mit
|
||||
demselben slug. So zeigt die Seite nach einem Import + einer
|
||||
Bearbeitung in der Verwaltung automatisch den aktuellen (DB-)Stand
|
||||
statt der alten, einprogrammierten Version -- ist das Backend mal
|
||||
nicht erreichbar, bleibt wenigstens die statische Version sichtbar
|
||||
(nichts verschwindet einfach). */
|
||||
window.dogiTeamZusammenfuehren = function (statische, neue) {
|
||||
if (!neue || !neue.length) return statische;
|
||||
const neueSlugs = new Set(neue.map((n) => n.slug));
|
||||
return [...statische.filter((s) => !neueSlugs.has(s.slug)), ...neue];
|
||||
};
|
||||
|
||||
/* 21.08.2026 (Nutzer-Wunsch: "ich will ich titel und namen von
|
||||
kategorien und seiten ändern können in der verwaltung"): lädt einmalig
|
||||
alle in der Verwaltung gesetzten Text-Overrides (siehe
|
||||
server-internal/routes/site-texts.js) und ruft applyTranslations()
|
||||
danach erneut auf, damit gesetzte Overrides die eingebauten
|
||||
Standardtexte sofort ersetzen. Ohne Overrides (Normalfall) ändert sich
|
||||
einfach nichts -- die Seite zeigt weiterhin ihren gewohnten Text. */
|
||||
window.dogiSiteTexteLaden = async function () {
|
||||
try {
|
||||
const res = await fetch(`${TEAM_API_BASIS}/site-texts`, { cache: "no-store" });
|
||||
const data = await res.json();
|
||||
if (data && data.ok) {
|
||||
window.__siteTexteCache = data.texte || {};
|
||||
applyTranslations();
|
||||
}
|
||||
} catch {
|
||||
/* kein Override verfügbar -> eingebaute Standardtexte bleiben stehen */
|
||||
}
|
||||
};
|
||||
|
||||
/* ===================================================================
|
||||
PWA: Service-Worker-Registrierung
|
||||
Voraussetzung dafür, dass Chrome/Edge auf dem Desktop "App
|
||||
|
||||
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Du bist Streamer und suchst einen Manager? Bewirb dich als Creator bei Spicy Media." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund bewerben-seite" style="--page-bg:url('/assets/img/bg-bewerben-creator.jpg');--page-bg-mobile:url('/assets/img/bg-bewerben-creator-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
@@ -70,8 +70,8 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/forms.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-bewerben-creator.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/forms.js?v=20260821k"></script>
|
||||
<script src="assets/js/i18n-bewerben-creator.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
<meta name="twitter:description" content="Kooperationsanfrage an Manager DogFather / Spicy Media." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund bewerben-seite" style="--page-bg:url('/assets/img/bg-bewerben.jpg');--page-bg-mobile:url('/assets/img/bg-bewerben-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
@@ -68,8 +68,8 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/forms.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-bewerben-kooperation.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/forms.js?v=20260821k"></script>
|
||||
<script src="assets/js/i18n-bewerben-kooperation.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+7
-7
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Werde Modi bei Team Dogi — DogFathers persönliches Community-Team." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund bewerben-seite" style="--page-bg:url('/assets/img/bg-dogfather.jpg');--page-bg-mobile:url('/assets/img/bg-dogfather-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
@@ -79,8 +79,8 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/forms.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-bewerben-modi.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/forms.js?v=20260821k"></script>
|
||||
<script src="assets/js/i18n-bewerben-modi.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+7
-7
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Werde Scout für DogFather bei der Spicy Media Agentur — finde neue Talente." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund bewerben-seite" style="--page-bg:url('/assets/img/bg-scouts.jpg');--page-bg-mobile:url('/assets/img/bg-scouts-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
@@ -71,8 +71,8 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/forms.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-bewerben-scout.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/forms.js?v=20260821k"></script>
|
||||
<script src="assets/js/i18n-bewerben-scout.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+6
-6
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Werde Teil vom Team: Modi bei Team Dogi, Scout oder Creator bei Spicy Media, oder Kooperation anfragen." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund bewerben-seite" style="--page-bg:url('/assets/img/bg-bewerben-split.jpg');--page-bg-mobile:url('/assets/img/bg-bewerben-split-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
@@ -85,7 +85,7 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/i18n-bewerben.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-bewerben.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+6
-6
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Der Husky an DogFathers Seite — das tierische Gesicht der HasiDog-Welt." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-casper.jpg');--page-bg-mobile:url('/assets/img/bg-casper-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
@@ -85,7 +85,7 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/i18n-casper.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-casper.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
-- =====================================================================
|
||||
-- Team-Mitglieder: volle Feld-Parität mit den von Hand gepflegten
|
||||
-- Profilen (21.08.2026, Nutzer-Wunsch: "wenn ich eine änderung mache
|
||||
-- ist es auch für jeden für die die schon da sind und die neuen").
|
||||
--
|
||||
-- Damit ein bestehendes Profil (Patrick, Bananenstift, Diene, ...) 1:1
|
||||
-- in die Verwaltung importiert werden kann, OHNE Inhalt zu verlieren,
|
||||
-- braucht team_members dieselben optionalen Felder wie
|
||||
-- data-modis.js/data-scouts.js: intro (kurzer Text neben dem Foto),
|
||||
-- bio_html (ausführliche, mehrabsatzige Vorstellung mit Überschriften),
|
||||
-- extra_cta (zweiter Button auf der Profilkarte, aktuell nur bei Marina
|
||||
-- -> manager.html genutzt).
|
||||
-- =====================================================================
|
||||
|
||||
ALTER TABLE team_members ADD COLUMN intro_json TEXT;
|
||||
ALTER TABLE team_members ADD COLUMN bio_html_json TEXT;
|
||||
ALTER TABLE team_members ADD COLUMN extra_cta_href TEXT;
|
||||
ALTER TABLE team_members ADD COLUMN extra_cta_label_json TEXT;
|
||||
+6
-6
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Team Dogi ist Familie — die Community rund um DogFather und HasiDog." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-community.jpg');--page-bg-mobile:url('/assets/img/bg-community-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
@@ -84,7 +84,7 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/i18n-community.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-community.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+12
-9
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Content Creator, die Teil von DogFather / Spicy Media sind." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-creator.jpg');--page-bg-mobile:url('/assets/img/bg-creator-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
@@ -32,7 +32,7 @@
|
||||
<div class="hero-banner"><img src="/assets/img/bg-creator.jpg" alt="" loading="eager" /></div>
|
||||
<div class="container">
|
||||
<span class="eyebrow" data-i18n="crt_eyebrow">Manager DogFather — Team</span>
|
||||
<h1 data-i18n="crt_h1">Creator im Team</h1>
|
||||
<h1 data-i18n="crt_h1" data-site-text-key="seitentitel_creator">Creator im Team</h1>
|
||||
<p class="lead worlds" data-i18n="crt_lead">Content Creator, die bei DogFather / Spicy Media mitmachen.</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -60,9 +60,9 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/data-creator.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-creator.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/data-creator.js?v=20260821k"></script>
|
||||
<script src="assets/js/i18n-creator.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
const grid = document.getElementById("creator-grid");
|
||||
@@ -78,7 +78,10 @@ document.addEventListener("DOMContentLoaded", async () => {
|
||||
// admin-gepflegte Creator NIE geladen worden.
|
||||
if (window.dogiTeamLaden) {
|
||||
const neue = await window.dogiTeamLaden("creator");
|
||||
if (neue.length) list = [...list, ...neue];
|
||||
// Runde 2 (21.08.2026, "wenn ich eine änderung mache ist es auch für
|
||||
// jeden"): überschreibt bereits importierte Creator (gleicher slug)
|
||||
// statt sie zu duplizieren.
|
||||
list = window.dogiTeamZusammenfuehren(list, neue);
|
||||
}
|
||||
|
||||
// Texte fürs JS-generierte Grid/Filter kommen aus den versteckten
|
||||
|
||||
+6
-6
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Wer ist DogFather wirklich? Persönlichkeit, Werte und die Geschichte hinter dem Content." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-dogfather-person.jpg');--page-bg-mobile:url('/assets/img/bg-dogfather-person-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
@@ -158,7 +158,7 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/i18n-dogfather.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-dogfather.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
|
||||
<meta name="apple-mobile-web-app-title" content="DogFather" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<style>
|
||||
/* =====================================================================
|
||||
Zugangsseite — bewusst kein braves Standard-Login-Formular, sondern
|
||||
|
||||
+6
-6
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="HasiDog: Maskottchen, Musikprojekt und die verspielte, familiäre Seite von Team Dogi." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-hasidog.jpg');--page-bg-mobile:url('/assets/img/bg-hasidog-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
@@ -186,7 +186,7 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/i18n-hasidog.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-hasidog.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+8
-8
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="DOGFATHER UNIVERSE: die Heimat von DogFather als Streamer, der HasiDog-Community und Manager DogFather bei Spicy Media." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="start-seite">
|
||||
<div id="site-header"></div>
|
||||
@@ -287,10 +287,10 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/data-modis.js?v=20260821j"></script>
|
||||
<script src="assets/js/data-scouts.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-index.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/data-modis.js?v=20260821k"></script>
|
||||
<script src="assets/js/data-scouts.js?v=20260821k"></script>
|
||||
<script src="assets/js/i18n-index.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
<script>
|
||||
// Gleiche URL wie in assets/js/forms.js / postfach.html — läuft seit
|
||||
// 05.08.2026 auf dem eigenen Server statt auf Cloudflare Workers.
|
||||
|
||||
+6
-6
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Kontakt, Impressum und Datenschutz von DOGFATHER UNIVERSE." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="site-header"></div>
|
||||
@@ -126,7 +126,7 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/i18n-kontakt.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-kontakt.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+6
-6
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Alle Social-Media- und Musik-Links von DogFather an einem Ort — plus QR-Codes." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
<style>
|
||||
/* Rabattcodes: an das Supporter-Abo gekoppelt (Nutzer-Feedback
|
||||
03.08.2026: "verbinde es mit dem Abo... wenn die Leute drauf drücken
|
||||
@@ -140,8 +140,8 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/i18n-links.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-links.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
<script>
|
||||
(function () {
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
|
||||
+11
-9
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="DogFather als Manager bei der Spicy Media Agency: Scouts, Creator und faire Zusammenarbeit." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-manager.jpg');--page-bg-mobile:url('/assets/img/bg-manager-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
@@ -104,7 +104,7 @@
|
||||
flaches Karten-Raster wie team-scouts.html. -->
|
||||
<section class="section-tight" id="weitere-manager-root" hidden>
|
||||
<div class="container">
|
||||
<h2 class="text-center" data-i18n="mgr_weitere_h2">Weitere Manager</h2>
|
||||
<h2 class="text-center" data-i18n="mgr_weitere_h2" data-site-text-key="seitentitel_manager">Weitere Manager</h2>
|
||||
<div id="weitere-manager-grid" class="grid grid-4"></div>
|
||||
</div>
|
||||
</section>
|
||||
@@ -139,9 +139,9 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/data-manager.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-manager.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/data-manager.js?v=20260821k"></script>
|
||||
<script src="assets/js/i18n-manager.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
<script>
|
||||
// "Weitere Manager" (21.08.2026) -- siehe Kommentar bei #weitere-manager-root
|
||||
// oben. Aktuell IMMER window.MANAGERS === [] (siehe data-manager.js), Inhalt
|
||||
@@ -180,7 +180,9 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
if (window.dogiTeamLaden) {
|
||||
window.dogiTeamLaden("manager").then((neue) => {
|
||||
if (!neue.length) return;
|
||||
window.MANAGERS = [...(window.MANAGERS || []), ...neue];
|
||||
// Runde 2 (21.08.2026, "wenn ich eine änderung mache ist es auch für
|
||||
// jeden"): überschreibt bereits importierte Manager statt zu duplizieren.
|
||||
window.MANAGERS = window.dogiTeamZusammenfuehren(window.MANAGERS || [], neue);
|
||||
renderWeitereManager();
|
||||
});
|
||||
}
|
||||
|
||||
+9
-9
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Fotos und Videos rund um Husky Casper an einem Ort." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund" data-welt="casper" style="--page-bg:url('/assets/img/bg-casper.jpg');--page-bg-mobile:url('/assets/img/bg-casper-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
@@ -59,10 +59,10 @@
|
||||
</div>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/data-media.js?v=20260821j"></script>
|
||||
<script src="assets/js/data-videos.js?v=20260821j"></script>
|
||||
<script src="assets/js/medien-welt.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-medien-casper.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/data-media.js?v=20260821k"></script>
|
||||
<script src="assets/js/data-videos.js?v=20260821k"></script>
|
||||
<script src="assets/js/medien-welt.js?v=20260821k"></script>
|
||||
<script src="assets/js/i18n-medien-casper.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+10
-10
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Fotos, Videos und Events aus DogFathers Welt an einem Ort." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund" data-welt="dogfather" style="--page-bg:url('/assets/img/bg-dogfather.jpg');--page-bg-mobile:url('/assets/img/bg-dogfather-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
@@ -65,11 +65,11 @@
|
||||
</div>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/data-media.js?v=20260821j"></script>
|
||||
<script src="assets/js/data-videos.js?v=20260821j"></script>
|
||||
<script src="assets/js/data-aktionen.js?v=20260821j"></script>
|
||||
<script src="assets/js/medien-welt.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-medien-dogfather.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/data-media.js?v=20260821k"></script>
|
||||
<script src="assets/js/data-videos.js?v=20260821k"></script>
|
||||
<script src="assets/js/data-aktionen.js?v=20260821k"></script>
|
||||
<script src="assets/js/medien-welt.js?v=20260821k"></script>
|
||||
<script src="assets/js/i18n-medien-dogfather.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+10
-10
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Fotos, Videos und Events aus der HasiDog-Welt an einem Ort." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund" data-welt="hasidog" style="--page-bg:url('/assets/img/bg-hasidog.jpg');--page-bg-mobile:url('/assets/img/bg-hasidog-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
@@ -65,11 +65,11 @@
|
||||
</div>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/data-media.js?v=20260821j"></script>
|
||||
<script src="assets/js/data-videos.js?v=20260821j"></script>
|
||||
<script src="assets/js/data-aktionen.js?v=20260821j"></script>
|
||||
<script src="assets/js/medien-welt.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-medien-hasidog.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/data-media.js?v=20260821k"></script>
|
||||
<script src="assets/js/data-videos.js?v=20260821k"></script>
|
||||
<script src="assets/js/data-aktionen.js?v=20260821k"></script>
|
||||
<script src="assets/js/medien-welt.js?v=20260821k"></script>
|
||||
<script src="assets/js/i18n-medien-hasidog.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+10
-10
@@ -16,10 +16,10 @@
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="robots" content="noindex" /> <!-- generische URL, echte Team-Seiten verlinken hierher -->
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
<style>
|
||||
.profil-photo { aspect-ratio: 4/5; max-width: 420px; margin: 0 auto; }
|
||||
.profil-photo img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
||||
@@ -130,12 +130,12 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/data-modis.js?v=20260821j"></script>
|
||||
<script src="assets/js/data-scouts.js?v=20260821j"></script>
|
||||
<script src="assets/js/data-creator.js?v=20260821j"></script>
|
||||
<script src="assets/js/data-manager.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-profil.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/data-modis.js?v=20260821k"></script>
|
||||
<script src="assets/js/data-scouts.js?v=20260821k"></script>
|
||||
<script src="assets/js/data-creator.js?v=20260821k"></script>
|
||||
<script src="assets/js/data-manager.js?v=20260821k"></script>
|
||||
<script src="assets/js/i18n-profil.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", async () => {
|
||||
const slug = new URLSearchParams(location.search).get("p") || "";
|
||||
|
||||
+6
-6
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Rabattcodes von Dogis Partnern für Team Dogi — spar bares Geld bei ausgewählten Marken." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
<style>
|
||||
/* Leere-Zustand-Kachel im "Gutschein"-Look: gestrichelter Rand wie ein
|
||||
ausgeschnittener Coupon, damit auch OHNE echte Codes schon klar ist,
|
||||
@@ -131,8 +131,8 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/i18n-rabattcodes.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-rabattcodes.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
<script>
|
||||
(function () {
|
||||
document.addEventListener("DOMContentLoaded", function () {
|
||||
|
||||
@@ -31,6 +31,7 @@ const MIGRATION_FILES = [
|
||||
"0005_supporter_polls.sql",
|
||||
"0006_testimonials.sql",
|
||||
"0007_team_members.sql",
|
||||
"0008_team_members_rich.sql",
|
||||
];
|
||||
|
||||
function alreadyApplied(name) {
|
||||
|
||||
@@ -72,8 +72,9 @@ import {
|
||||
createPoll, listPolls, closePoll, reopenPoll, deletePoll, getActivePollPublic, voteOnPoll,
|
||||
} from "./routes/polls.js";
|
||||
import {
|
||||
getTeamPublic, getTeamAdmin, saveTeamMember, deleteTeamMember, uploadTeamPhoto,
|
||||
getTeamPublic, getTeamAdmin, saveTeamMember, importLegacyMember, deleteTeamMember, uploadTeamPhoto,
|
||||
} from "./routes/team.js";
|
||||
import { getSiteTextsPublic, saveSiteText } from "./routes/site-texts.js";
|
||||
|
||||
initDb();
|
||||
|
||||
@@ -180,6 +181,7 @@ app.get("/special-event", wrap(getSpecialEventPublic));
|
||||
app.post("/testimonials/submit", wrap(submitTestimonial));
|
||||
app.get("/testimonials", wrap(getApprovedTestimonialsPublic));
|
||||
app.get("/team", wrap(getTeamPublic));
|
||||
app.get("/site-texts", wrap(getSiteTextsPublic));
|
||||
|
||||
app.post("/supporter/register", wrap(registerSupporter));
|
||||
app.post("/supporter/verify-email", wrap(verifySupporterEmail));
|
||||
@@ -229,8 +231,10 @@ app.post("/admin/testimonials/delete", requireTeamSession, withSession(deleteTes
|
||||
// Team-Mitglieder (Modis/Scouts/Creator/Manager) — 21.08.2026
|
||||
app.post("/admin/team/list", requireTeamSession, withSession(getTeamAdmin));
|
||||
app.post("/admin/team/save", requireTeamSession, withSession(saveTeamMember));
|
||||
app.post("/admin/team/import", requireTeamSession, withSession(importLegacyMember));
|
||||
app.post("/admin/team/delete", requireTeamSession, withSession(deleteTeamMember));
|
||||
app.post("/admin/team/upload-photo", requireTeamSession, teamBildUpload.single("image"), withSession(uploadTeamPhoto));
|
||||
app.post("/admin/site-texts/save", requireTeamSession, withSession(saveSiteText));
|
||||
|
||||
app.post("/admin/roles/list", requireTeamSession, withSession(listRoles));
|
||||
app.post("/admin/roles/create", requireTeamSession, withSession(createRole));
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
/* =====================================================================
|
||||
routes/site-texts.js — admin-editierbare Kategorie-Namen & Seitentitel
|
||||
(21.08.2026, Nutzer-Wunsch: "ich will ich titel und namen von
|
||||
kategorien und seiten ändern können in der verwaltung").
|
||||
|
||||
Bewusst als generischer key->{de,"de-CH",en,fr,pt}-Override gebaut
|
||||
(wiederverwendet die vorhandene app_settings-Tabelle, key-Präfix
|
||||
"site_text_"), NICHT als eigene Tabelle -- dieselbe Technik wie
|
||||
"Event des Jahres". Ein fester, serverseitiger Katalog erlaubter
|
||||
Keys (ERLAUBTE_KEYS) verhindert, dass beliebige/kaputte Keys
|
||||
gespeichert werden können. Aktuell nur die vier Team-Kategorien +
|
||||
ihre Seitentitel belegt -- das Muster ist aber bewusst so gebaut,
|
||||
dass in einer künftigen Runde weitere Seiten/Überschriften einfach
|
||||
per neuem Key ergänzt werden können (siehe main.js:
|
||||
applyTranslations() prüft data-site-text-key genau wie data-i18n).
|
||||
|
||||
Öffentlich lesbar (jede Seite braucht das ohne Login), Schreiben nur
|
||||
mit TEAM_MANAGE (dieselbe Berechtigung wie die Team-Verwaltung,
|
||||
bewusst keine eigene -- beides gehört zusammen). ===================================================================== */
|
||||
import { db } from "../db.js";
|
||||
import { hasPermission } from "../lib/permissions.js";
|
||||
import { json } from "../lib/http.js";
|
||||
import { logAction } from "../lib/audit.js";
|
||||
import { uebersetzeAlleSprachen } from "../lib/translate.js";
|
||||
|
||||
function can(session, key) {
|
||||
if (!session) return false;
|
||||
if (session.isOwner) return true;
|
||||
return hasPermission(session.permissions, session.overrides, key);
|
||||
}
|
||||
|
||||
function forbidden(res) {
|
||||
return json(res, { ok: false, error: "Keine Berechtigung." }, 403);
|
||||
}
|
||||
|
||||
// key -> { label, maxLen } -- label ist nur für Log-/Fehlermeldungen.
|
||||
const ERLAUBTE_KEYS = {
|
||||
kategorie_modi: { label: "Kategorie-Name: Modi", maxLen: 40 },
|
||||
kategorie_scout: { label: "Kategorie-Name: Scout", maxLen: 40 },
|
||||
kategorie_creator: { label: "Kategorie-Name: Creator", maxLen: 40 },
|
||||
kategorie_manager: { label: "Kategorie-Name: Manager", maxLen: 40 },
|
||||
seitentitel_modi: { label: "Seitentitel: Modi-Team", maxLen: 100 },
|
||||
seitentitel_scout: { label: "Seitentitel: Scout-Team", maxLen: 100 },
|
||||
seitentitel_creator: { label: "Seitentitel: Creator", maxLen: 100 },
|
||||
seitentitel_manager: { label: "Seitentitel: Manager", maxLen: 100 },
|
||||
};
|
||||
|
||||
function settingsKey(key) {
|
||||
return `site_text_${key}`;
|
||||
}
|
||||
|
||||
/** Öffentlich, keine Session: liefert alle aktuell GESETZTEN Overrides
|
||||
als { key: {de,"de-CH",en,fr,pt} }. Keys ohne eigenen Override
|
||||
fehlen einfach im Ergebnis -- die aufrufende Seite behält dann ihren
|
||||
eingebauten Standardtext (siehe main.js). */
|
||||
export async function getSiteTextsPublic(req, res) {
|
||||
const rows = db
|
||||
.prepare(`SELECT key, value FROM app_settings WHERE key LIKE 'site_text_%'`)
|
||||
.all();
|
||||
const texte = {};
|
||||
for (const row of rows) {
|
||||
const kurzKey = row.key.replace(/^site_text_/, "");
|
||||
try {
|
||||
texte[kurzKey] = JSON.parse(row.value);
|
||||
} catch {
|
||||
/* kaputter Eintrag -> einfach überspringen, kein Fehler für alle anderen Keys */
|
||||
}
|
||||
}
|
||||
return json(res, { ok: true, texte });
|
||||
}
|
||||
|
||||
export async function saveSiteText(req, res, session) {
|
||||
if (!can(session, "TEAM_MANAGE")) return forbidden(res);
|
||||
const body = req.body || {};
|
||||
const key = String(body.key || "").trim();
|
||||
const eintrag = ERLAUBTE_KEYS[key];
|
||||
if (!eintrag) return json(res, { ok: false, error: "Unbekannter Text-Schlüssel." }, 400);
|
||||
|
||||
const textDe = String(body.text || "").trim().slice(0, eintrag.maxLen);
|
||||
if (!textDe) {
|
||||
// Leeres Feld = Override entfernen -> Seite fällt zurück auf ihren
|
||||
// eingebauten Standardtext (Dogi kann so jederzeit "zurücksetzen").
|
||||
db.prepare(`DELETE FROM app_settings WHERE key = ?`).run(settingsKey(key));
|
||||
logAction(session.actor, "site_text.reset", key, null);
|
||||
return json(res, { ok: true, text: null });
|
||||
}
|
||||
|
||||
const text = await uebersetzeAlleSprachen(textDe);
|
||||
db.prepare(
|
||||
`INSERT INTO app_settings (key, value, updated_at) VALUES (?, ?, ?)
|
||||
ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`
|
||||
).run(settingsKey(key), JSON.stringify(text), new Date().toISOString());
|
||||
logAction(session.actor, "site_text.saved", key, { textDe });
|
||||
return json(res, { ok: true, text });
|
||||
}
|
||||
+180
-48
@@ -5,12 +5,20 @@
|
||||
automatisch in der Website hinzugefügt werden ... auch mit Fotos und
|
||||
TikTok Link").
|
||||
|
||||
Ergänzt (überschreibt NICHT) die von Hand gepflegten Einträge in
|
||||
assets/js/data-modis.js/data-scouts.js/data-creator.js/data-manager.js.
|
||||
Die Website mischt beide Quellen zu einer gemeinsamen Liste (siehe
|
||||
window.dogiTeamLaden() in main.js) -- ein Fehler hier kann die
|
||||
bestehenden, fest einprogrammierten Profile (VanVan, Patrick, ...) nie
|
||||
verschwinden lassen.
|
||||
Update 21.08.2026 (Runde 2, Nutzer-Wunsch: "wenn ich eine änderung
|
||||
mache ist es auch für jeden für die die schon da sind und die neuen"):
|
||||
volle Feld-Parität mit den von Hand gepflegten Profilen (intro/bioHtml/
|
||||
extraCta) PLUS ein Import-Weg für bestehende Profile (importLegacyMember)
|
||||
-- danach lässt sich JEDES Mitglied (alt oder neu) hier bearbeiten,
|
||||
und die Website zeigt (siehe window.dogiTeamLaden() in main.js) für
|
||||
importierte slugs die DB-Version statt der statischen an.
|
||||
|
||||
WICHTIG: importLegacyMember übersetzt NICHT automatisch -- die
|
||||
bestehenden Profile haben von Hand geschriebene, geprüfte Übersetzungen
|
||||
in allen 5 Sprachen (siehe data-modis.js etc.), die dürfen beim Import
|
||||
nicht durch Maschinenübersetzung ersetzt werden. saveTeamMember (Neu
|
||||
anlegen/Bearbeiten in der Verwaltung) übersetzt weiterhin automatisch,
|
||||
weil dort nur Deutsch eingetippt wird.
|
||||
|
||||
Öffentlicher Lesezugriff (getTeamPublic) ohne Session, nur AKTIVE
|
||||
Mitglieder -- Schreibzugriff nur mit TEAM_MANAGE-Berechtigung.
|
||||
@@ -23,6 +31,7 @@ import { logAction } from "../lib/audit.js";
|
||||
import { uebersetzeAlleSprachen } from "../lib/translate.js";
|
||||
|
||||
const KATEGORIEN = ["modi", "scout", "creator", "manager"];
|
||||
const SPRACHEN = ["de", "de-CH", "en", "fr", "pt"];
|
||||
|
||||
function can(session, key) {
|
||||
if (!session) return false;
|
||||
@@ -43,6 +52,25 @@ function parseJsonFeld(text) {
|
||||
}
|
||||
}
|
||||
|
||||
/** Prüft, ob ein vom Client geschicktes mehrsprachiges Feld wirklich ein
|
||||
Objekt mit (mindestens) einem "de"-Text ist -- genutzt vom Import, der
|
||||
KEINE eigene Übersetzung anstößt und deshalb ein vollständiges Objekt
|
||||
erwartet, kein einzelnes Deutsch-Textfeld. */
|
||||
function istGueltigesSprachFeld(x) {
|
||||
return x && typeof x === "object" && typeof x.de === "string" && x.de.trim().length > 0;
|
||||
}
|
||||
|
||||
/** Baut aus einem client-gesendeten mehrsprachigen Feld ein sauberes,
|
||||
auf die bekannten 5 Sprachen begrenztes Objekt (verhindert, dass
|
||||
beliebige zusätzliche Keys in die Datenbank gelangen). */
|
||||
function saubereSprachen(x, maxLen) {
|
||||
const out = {};
|
||||
for (const sprache of SPRACHEN) {
|
||||
out[sprache] = String(x?.[sprache] ?? x?.de ?? "").slice(0, maxLen);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/** Slug aus dem Namen ableiten (Kleinbuchstaben, Umlaute vereinfacht,
|
||||
alles außer a-z0-9 wird zu "-"), falls die Verwaltung keinen eigenen
|
||||
Slug mitschickt -- selbes Muster wie an anderen Stellen der Seite. */
|
||||
@@ -55,7 +83,24 @@ function slugAusName(name) {
|
||||
.slice(0, 60);
|
||||
}
|
||||
|
||||
function slugFrei(slug, ausgenommeneId) {
|
||||
const row = db.prepare(`SELECT id FROM team_members WHERE slug = ?`).get(slug);
|
||||
return !row || row.id === ausgenommeneId;
|
||||
}
|
||||
|
||||
/** Macht einen Slug bei Kollision automatisch eindeutig ("-2", "-3", ...)
|
||||
statt hart abzulehnen -- Dogi/VanVan sollen nicht wegen eines
|
||||
Namensdopplers (z.B. zwei Personen mit Spitzname "Foxy") hängen
|
||||
bleiben. */
|
||||
function eindeutigerSlug(wunschSlug, ausgenommeneId) {
|
||||
if (slugFrei(wunschSlug, ausgenommeneId)) return wunschSlug;
|
||||
let zaehler = 2;
|
||||
while (!slugFrei(`${wunschSlug}-${zaehler}`, ausgenommeneId)) zaehler++;
|
||||
return `${wunschSlug}-${zaehler}`;
|
||||
}
|
||||
|
||||
function oeffentlichesFeld(row) {
|
||||
const extraCtaLabel = parseJsonFeld(row.extra_cta_label_json);
|
||||
return {
|
||||
id: row.id,
|
||||
kategorie: row.kategorie,
|
||||
@@ -64,10 +109,13 @@ function oeffentlichesFeld(row) {
|
||||
rolle: parseJsonFeld(row.rolle_json),
|
||||
foto: row.foto_url || "",
|
||||
tiktok: row.tiktok_url || "",
|
||||
intro: parseJsonFeld(row.intro_json),
|
||||
bio: parseJsonFeld(row.bio_json),
|
||||
bioHtml: parseJsonFeld(row.bio_html_json),
|
||||
zitat: parseJsonFeld(row.zitat_json),
|
||||
rang: row.rang,
|
||||
rangLabel: parseJsonFeld(row.rang_label_json),
|
||||
extraCta: row.extra_cta_href && extraCtaLabel ? { href: row.extra_cta_href, label: extraCtaLabel } : null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -130,23 +178,9 @@ export async function saveTeamMember(req, res, session) {
|
||||
const rolleDe = String(body.rolle || "").trim().slice(0, 120);
|
||||
if (!rolleDe) return json(res, { ok: false, error: "Rolle ist Pflicht." }, 400);
|
||||
|
||||
let slug = String(body.slug || "").trim().toLowerCase().replace(/[^a-z0-9-]/g, "").slice(0, 60) || slugAusName(name);
|
||||
if (!slug) return json(res, { ok: false, error: "Konnte keinen gültigen Slug ableiten -- bitte Namen prüfen." }, 400);
|
||||
|
||||
// Slug muss GLOBAL eindeutig sein (profil.html sucht kategorieübergreifend).
|
||||
// Kollidiert er mit einem ANDEREN Eintrag (nicht dem gerade bearbeiteten),
|
||||
// wird er automatisch mit "-2", "-3", ... eindeutig gemacht statt hart
|
||||
// abzulehnen -- Dogi/VanVan sollen nicht wegen eines Namensdopplers
|
||||
// (z.B. zwei Personen mit Spitzname "Foxy") hängen bleiben.
|
||||
const slugBelegt = (kandidat) => {
|
||||
const row = db.prepare(`SELECT id FROM team_members WHERE slug = ?`).get(kandidat);
|
||||
return row && row.id !== id;
|
||||
};
|
||||
if (slugBelegt(slug)) {
|
||||
let zaehler = 2;
|
||||
while (slugBelegt(`${slug}-${zaehler}`)) zaehler++;
|
||||
slug = `${slug}-${zaehler}`;
|
||||
}
|
||||
const wunschSlug = String(body.slug || "").trim().toLowerCase().replace(/[^a-z0-9-]/g, "").slice(0, 60) || slugAusName(name);
|
||||
if (!wunschSlug) return json(res, { ok: false, error: "Konnte keinen gültigen Slug ableiten -- bitte Namen prüfen." }, 400);
|
||||
const slug = eindeutigerSlug(wunschSlug, id);
|
||||
|
||||
const tiktokRoh = String(body.tiktok || "").trim().slice(0, 200);
|
||||
let tiktokUrl = null;
|
||||
@@ -160,7 +194,9 @@ export async function saveTeamMember(req, res, session) {
|
||||
}
|
||||
}
|
||||
|
||||
const introDe = String(body.intro || "").trim().slice(0, 400);
|
||||
const bioDe = String(body.bio || "").trim().slice(0, 800);
|
||||
const bioHtmlDe = String(body.bioHtml || "").trim().slice(0, 6000);
|
||||
const zitatDe = String(body.zitat || "").trim().slice(0, 200);
|
||||
const fotoUrl = String(body.foto || "").trim().slice(0, 300) || null;
|
||||
|
||||
@@ -168,54 +204,150 @@ export async function saveTeamMember(req, res, session) {
|
||||
? Math.max(1, Math.min(99, Number(body.rang) || 0)) : null;
|
||||
const rangLabelDe = String(body.rangLabel || "").trim().slice(0, 40);
|
||||
|
||||
const extraCtaHref = String(body.extraCtaHref || "").trim().slice(0, 300) || null;
|
||||
const extraCtaLabelDe = String(body.extraCtaLabel || "").trim().slice(0, 60);
|
||||
if (extraCtaHref && !/^https?:\/\//i.test(extraCtaHref) && !extraCtaHref.startsWith("/") && !extraCtaHref.endsWith(".html")) {
|
||||
return json(res, { ok: false, error: "Zweiter Button: Link muss eine Seite (…html) oder http(s)-Adresse sein." }, 400);
|
||||
}
|
||||
|
||||
const reihenfolge = Number.isFinite(Number(body.reihenfolge)) ? Number(body.reihenfolge) : 0;
|
||||
const status = body.status === "pausiert" ? "pausiert" : "aktiv";
|
||||
|
||||
const [rolle, bio, zitat, rangLabel] = await Promise.all([
|
||||
const [rolle, intro, bio, bioHtml, zitat, rangLabel, extraCtaLabel] = await Promise.all([
|
||||
uebersetzeAlleSprachen(rolleDe),
|
||||
introDe ? uebersetzeAlleSprachen(introDe) : Promise.resolve(null),
|
||||
bioDe ? uebersetzeAlleSprachen(bioDe) : Promise.resolve(null),
|
||||
bioHtmlDe ? uebersetzeAlleSprachen(bioHtmlDe) : Promise.resolve(null),
|
||||
zitatDe ? uebersetzeAlleSprachen(zitatDe) : Promise.resolve(null),
|
||||
rangLabelDe ? uebersetzeAlleSprachen(rangLabelDe) : Promise.resolve(null),
|
||||
extraCtaLabelDe ? uebersetzeAlleSprachen(extraCtaLabelDe) : Promise.resolve(null),
|
||||
]);
|
||||
|
||||
const jetzt = nowIso();
|
||||
const werte = {
|
||||
id: id || generateId(),
|
||||
kategorie, name, slug,
|
||||
rolle_json: JSON.stringify(rolle),
|
||||
foto_url: fotoUrl,
|
||||
tiktok_url: tiktokUrl,
|
||||
intro_json: intro ? JSON.stringify(intro) : null,
|
||||
bio_json: bio ? JSON.stringify(bio) : null,
|
||||
bio_html_json: bioHtml ? JSON.stringify(bioHtml) : null,
|
||||
zitat_json: zitat ? JSON.stringify(zitat) : null,
|
||||
rang, rang_label_json: rangLabel ? JSON.stringify(rangLabel) : null,
|
||||
extra_cta_href: extraCtaHref && extraCtaLabel ? extraCtaHref : null,
|
||||
extra_cta_label_json: extraCtaHref && extraCtaLabel ? JSON.stringify(extraCtaLabel) : null,
|
||||
reihenfolge, status,
|
||||
geaendert_am: jetzt, geaendert_von: session.actor,
|
||||
};
|
||||
|
||||
if (id) {
|
||||
const info = db
|
||||
.prepare(
|
||||
`UPDATE team_members SET
|
||||
kategorie = ?, name = ?, slug = ?, rolle_json = ?, foto_url = ?, tiktok_url = ?,
|
||||
bio_json = ?, zitat_json = ?, rang = ?, rang_label_json = ?, reihenfolge = ?,
|
||||
status = ?, geaendert_am = ?, geaendert_von = ?
|
||||
WHERE id = ?`
|
||||
kategorie=@kategorie, name=@name, slug=@slug, rolle_json=@rolle_json,
|
||||
foto_url=@foto_url, tiktok_url=@tiktok_url, intro_json=@intro_json,
|
||||
bio_json=@bio_json, bio_html_json=@bio_html_json, zitat_json=@zitat_json,
|
||||
rang=@rang, rang_label_json=@rang_label_json,
|
||||
extra_cta_href=@extra_cta_href, extra_cta_label_json=@extra_cta_label_json,
|
||||
reihenfolge=@reihenfolge, status=@status,
|
||||
geaendert_am=@geaendert_am, geaendert_von=@geaendert_von
|
||||
WHERE id=@id`
|
||||
)
|
||||
.run(
|
||||
kategorie, name, slug, JSON.stringify(rolle), fotoUrl, tiktokUrl,
|
||||
bio ? JSON.stringify(bio) : null, zitat ? JSON.stringify(zitat) : null,
|
||||
rang, rangLabel ? JSON.stringify(rangLabel) : null, reihenfolge,
|
||||
status, jetzt, session.actor, id
|
||||
);
|
||||
.run(werte);
|
||||
if (info.changes === 0) return json(res, { ok: false, error: "Nicht gefunden." }, 404);
|
||||
logAction(session.actor, "team.updated", id, { name, kategorie });
|
||||
const row = db.prepare(`SELECT * FROM team_members WHERE id = ?`).get(id);
|
||||
} else {
|
||||
db.prepare(
|
||||
`INSERT INTO team_members
|
||||
(id, kategorie, name, slug, rolle_json, foto_url, tiktok_url, intro_json,
|
||||
bio_json, bio_html_json, zitat_json, rang, rang_label_json,
|
||||
extra_cta_href, extra_cta_label_json, reihenfolge, status,
|
||||
erstellt_am, geaendert_am, erstellt_von, geaendert_von)
|
||||
VALUES
|
||||
(@id, @kategorie, @name, @slug, @rolle_json, @foto_url, @tiktok_url, @intro_json,
|
||||
@bio_json, @bio_html_json, @zitat_json, @rang, @rang_label_json,
|
||||
@extra_cta_href, @extra_cta_label_json, @reihenfolge, @status,
|
||||
@geaendert_am, @geaendert_am, @geaendert_von, @geaendert_von)`
|
||||
).run(werte);
|
||||
logAction(session.actor, "team.created", werte.id, { name, kategorie });
|
||||
}
|
||||
const row = db.prepare(`SELECT * FROM team_members WHERE id = ?`).get(werte.id);
|
||||
return json(res, { ok: true, mitglied: vollesFeld(row) });
|
||||
}
|
||||
|
||||
const neueId = generateId();
|
||||
/** Importiert ein BESTEHENDES, von Hand gepflegtes Profil (aus
|
||||
data-modis.js/data-scouts.js/data-creator.js/data-manager.js) 1:1 in
|
||||
die Datenbank -- danach ist es über die Verwaltung wie jedes andere
|
||||
Mitglied bearbeitbar, und Änderungen gelten für ALLE (siehe
|
||||
window.dogiTeamLaden()-Override-Logik in main.js).
|
||||
|
||||
WICHTIG: übersetzt NICHTS automatisch (anders als saveTeamMember) --
|
||||
erwartet für jedes Textfeld ein bereits vollständiges {de,"de-CH",en,
|
||||
fr,pt}-Objekt, wie es in den data-*.js-Dateien von Hand gepflegt
|
||||
wird. Ist ein Feld kein gültiges Sprachobjekt, wird es einfach
|
||||
weggelassen statt einen Fehler zu werfen -- ein unvollständiges
|
||||
Altprofil soll den Import nicht komplett blockieren.
|
||||
|
||||
Idempotent: ist der slug schon vorhanden, passiert nichts (kein
|
||||
Duplikat, kein Fehler) -- der Button in der Verwaltung kann beliebig
|
||||
oft geklickt werden, ohne Doppel-Einträge zu erzeugen. */
|
||||
export async function importLegacyMember(req, res, session) {
|
||||
if (!can(session, "TEAM_MANAGE")) return forbidden(res);
|
||||
const body = req.body || {};
|
||||
const kategorie = String(body.kategorie || "").trim();
|
||||
if (!KATEGORIEN.includes(kategorie)) return json(res, { ok: false, error: "Ungültige Kategorie." }, 400);
|
||||
const name = String(body.name || "").trim().slice(0, 60);
|
||||
const slug = String(body.slug || "").trim().toLowerCase().replace(/[^a-z0-9-]/g, "").slice(0, 60);
|
||||
if (!name || !slug) return json(res, { ok: false, error: "Name und Slug sind Pflicht." }, 400);
|
||||
if (!istGueltigesSprachFeld(body.rolle)) return json(res, { ok: false, error: "Rolle fehlt oder unvollständig." }, 400);
|
||||
|
||||
if (!slugFrei(slug, null)) {
|
||||
return json(res, { ok: true, uebersprungen: true, grund: "Bereits importiert." });
|
||||
}
|
||||
|
||||
const rolle = saubereSprachen(body.rolle, 120);
|
||||
const intro = istGueltigesSprachFeld(body.intro) ? saubereSprachen(body.intro, 2000) : null;
|
||||
const bio = istGueltigesSprachFeld(body.bio) ? saubereSprachen(body.bio, 800) : null;
|
||||
const bioHtml = istGueltigesSprachFeld(body.bioHtml) ? saubereSprachen(body.bioHtml, 8000) : null;
|
||||
const zitat = istGueltigesSprachFeld(body.zitat) ? saubereSprachen(body.zitat, 200) : null;
|
||||
const rangLabel = istGueltigesSprachFeld(body.rangLabel) ? saubereSprachen(body.rangLabel, 40) : null;
|
||||
const extraCtaLabel = body.extraCta?.href && istGueltigesSprachFeld(body.extraCta?.label) ? saubereSprachen(body.extraCta.label, 60) : null;
|
||||
|
||||
const jetzt = nowIso();
|
||||
const werte = {
|
||||
id: generateId(),
|
||||
kategorie, name, slug,
|
||||
rolle_json: JSON.stringify(rolle),
|
||||
foto_url: String(body.foto || "").trim().slice(0, 300) || null,
|
||||
tiktok_url: body.tiktok?.href || (typeof body.tiktok === "string" ? body.tiktok : null),
|
||||
intro_json: intro ? JSON.stringify(intro) : null,
|
||||
bio_json: bio ? JSON.stringify(bio) : null,
|
||||
bio_html_json: bioHtml ? JSON.stringify(bioHtml) : null,
|
||||
zitat_json: zitat ? JSON.stringify(zitat) : null,
|
||||
rang: Number.isFinite(Number(body.rang)) ? Number(body.rang) : null,
|
||||
rang_label_json: rangLabel ? JSON.stringify(rangLabel) : null,
|
||||
extra_cta_href: extraCtaLabel ? body.extraCta.href : null,
|
||||
extra_cta_label_json: extraCtaLabel ? JSON.stringify(extraCtaLabel) : null,
|
||||
reihenfolge: 0,
|
||||
status: "aktiv",
|
||||
geaendert_am: jetzt, geaendert_von: session.actor,
|
||||
};
|
||||
db.prepare(
|
||||
`INSERT INTO team_members
|
||||
(id, kategorie, name, slug, rolle_json, foto_url, tiktok_url, bio_json, zitat_json,
|
||||
rang, rang_label_json, reihenfolge, status, erstellt_am, geaendert_am, erstellt_von, geaendert_von)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
).run(
|
||||
neueId, kategorie, name, slug, JSON.stringify(rolle), fotoUrl, tiktokUrl,
|
||||
bio ? JSON.stringify(bio) : null, zitat ? JSON.stringify(zitat) : null,
|
||||
rang, rangLabel ? JSON.stringify(rangLabel) : null, reihenfolge,
|
||||
status, jetzt, jetzt, session.actor, session.actor
|
||||
);
|
||||
logAction(session.actor, "team.created", neueId, { name, kategorie });
|
||||
const row = db.prepare(`SELECT * FROM team_members WHERE id = ?`).get(neueId);
|
||||
return json(res, { ok: true, mitglied: vollesFeld(row) });
|
||||
(id, kategorie, name, slug, rolle_json, foto_url, tiktok_url, intro_json,
|
||||
bio_json, bio_html_json, zitat_json, rang, rang_label_json,
|
||||
extra_cta_href, extra_cta_label_json, reihenfolge, status,
|
||||
erstellt_am, geaendert_am, erstellt_von, geaendert_von)
|
||||
VALUES
|
||||
(@id, @kategorie, @name, @slug, @rolle_json, @foto_url, @tiktok_url, @intro_json,
|
||||
@bio_json, @bio_html_json, @zitat_json, @rang, @rang_label_json,
|
||||
@extra_cta_href, @extra_cta_label_json, @reihenfolge, @status,
|
||||
@geaendert_am, @geaendert_am, @geaendert_von, @geaendert_von)`
|
||||
).run(werte);
|
||||
logAction(session.actor, "team.imported", werte.id, { name, kategorie, slug });
|
||||
const row = db.prepare(`SELECT * FROM team_members WHERE id = ?`).get(werte.id);
|
||||
return json(res, { ok: true, uebersprungen: false, mitglied: vollesFeld(row) });
|
||||
}
|
||||
|
||||
export async function deleteTeamMember(req, res, session) {
|
||||
|
||||
+7
-7
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Was die Community und das Team über DogFather sagen." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-stimmen.jpg');--page-bg-mobile:url('/assets/img/bg-stimmen-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
@@ -87,8 +87,8 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/i18n-stimmen.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/stimmen.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-stimmen.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
<script src="assets/js/stimmen.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+6
-6
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Die persönliche Geschichte hinter DogFather — Filipe, Streamer, Manager bei Spicy Media und Hundepapa von Husky Casper." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-streamer.jpg');--page-bg-mobile:url('/assets/img/bg-streamer-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
@@ -217,7 +217,7 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/i18n-streamer.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-streamer.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+9
-9
@@ -19,17 +19,17 @@
|
||||
<meta name="twitter:description" content="Wann geht DogFather live? Streamplan und Livebereich." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-streamplan.jpg?v=20260821j');--page-bg-mobile:url('/assets/img/bg-streamplan-mobile.jpg?v=20260821j');">
|
||||
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-streamplan.jpg?v=20260821k');--page-bg-mobile:url('/assets/img/bg-streamplan-mobile.jpg?v=20260821k');">
|
||||
<div id="site-header"></div>
|
||||
|
||||
<main>
|
||||
<section class="hero page-bg">
|
||||
<div class="hero-banner"><img src="/assets/img/bg-streamplan.jpg?v=20260821j" alt="" loading="eager" /></div>
|
||||
<div class="hero-banner"><img src="/assets/img/bg-streamplan.jpg?v=20260821k" alt="" loading="eager" /></div>
|
||||
<div class="container">
|
||||
<span class="eyebrow" data-i18n="sp_eyebrow">Welt 1 — DogFather</span>
|
||||
<h1 data-i18n="sp_h1">Streamplan & Livebereich</h1>
|
||||
@@ -126,8 +126,8 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/i18n-streamplan.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/streamplan.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-streamplan.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
<script src="assets/js/streamplan.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+7
-7
@@ -9,10 +9,10 @@
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<meta name="theme-color" content="#0b0d10" />
|
||||
<meta name="robots" content="noindex" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
<style>
|
||||
/* =====================================================================
|
||||
Supporter-Bereich — komplett neu gestaltet (Nutzer-Feedback
|
||||
@@ -416,9 +416,9 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/i18n-supporter.js?v=20260821j"></script>
|
||||
<script src="assets/js/supporter.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-supporter.js?v=20260821k"></script>
|
||||
<script src="assets/js/supporter.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
<script>
|
||||
(function () {
|
||||
const S = window.DogiSupporter;
|
||||
|
||||
+14
-13
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="VanVan, Diene, Funny, Miss und Marina — das Modi-/Core-Team von Team Dogi." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-dogfather.jpg');--page-bg-mobile:url('/assets/img/bg-dogfather-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
@@ -32,7 +32,7 @@
|
||||
<div class="hero-banner"><img src="/assets/img/bg-dogfather.jpg" alt="" loading="eager" /></div>
|
||||
<div class="container">
|
||||
<span class="eyebrow" data-i18n="tm_eyebrow">🩵 Team Dogi</span>
|
||||
<h1 data-i18n="tm_h1">Das Modi-Team</h1>
|
||||
<h1 data-i18n="tm_h1" data-site-text-key="seitentitel_modi">Das Modi-Team</h1>
|
||||
<p class="lead worlds" data-i18n="tm_lead">Das Rückgrat von Team Dogi — von VanVan bis Marina.</p>
|
||||
<!-- TikTok-Account der Modi-Gang (Nutzer-Wunsch 20.08.2026, umgezogen 21.08.2026 von
|
||||
streamer.html hierher: "in die modie seite zu den modis hinzufügen"). Passt hier
|
||||
@@ -72,9 +72,9 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/data-modis.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-team-modis.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/data-modis.js?v=20260821k"></script>
|
||||
<script src="assets/js/i18n-team-modis.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const grid = document.getElementById("modis-grid");
|
||||
@@ -133,14 +133,15 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
renderModis();
|
||||
document.addEventListener("dogi-sprache-geaendert", renderModis);
|
||||
|
||||
// 21.08.2026: über die Verwaltung angelegte Modis ergänzen (Nutzer-Wunsch
|
||||
// "überall wo Modis sind ... will ich dass ich die easy über meine
|
||||
// Verwaltungsseite hinzufüge"). Rang/Rang-Bezeichnung kommen mit, damit sie
|
||||
// sich sauber in die bestehende Pyramide einordnen.
|
||||
// 21.08.2026: über die Verwaltung angelegte/importierte Modis einmischen
|
||||
// (+ Runde 2: "wenn ich eine änderung mache ist es auch für jeden").
|
||||
// Rang/Rang-Bezeichnung kommen mit, damit sie sich sauber in die
|
||||
// bestehende Pyramide einordnen. dogiTeamZusammenfuehren() überschreibt
|
||||
// bereits importierte Modis (gleicher slug) mit ihrem aktuellen DB-Stand.
|
||||
if (window.dogiTeamLaden) {
|
||||
window.dogiTeamLaden("modi").then((neue) => {
|
||||
if (!neue.length) return;
|
||||
window.MODIS = [...window.MODIS, ...neue];
|
||||
window.MODIS = window.dogiTeamZusammenfuehren(window.MODIS, neue);
|
||||
renderModis();
|
||||
});
|
||||
}
|
||||
|
||||
+15
-14
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Die Scouts von Team Dogi bei Spicy Media." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-scouts.jpg');--page-bg-mobile:url('/assets/img/bg-scouts-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
@@ -32,7 +32,7 @@
|
||||
<div class="hero-banner"><img src="/assets/img/bg-scouts.jpg" alt="" loading="eager" /></div>
|
||||
<div class="container">
|
||||
<span class="eyebrow" data-i18n="ts_eyebrow">Manager DogFather — Spicy Media</span>
|
||||
<h1 data-i18n="ts_h1">Mein Scout-Team</h1>
|
||||
<h1 data-i18n="ts_h1" data-site-text-key="seitentitel_scout">Mein Scout-Team</h1>
|
||||
<p class="lead worlds" data-i18n="ts_lead">Sie finden und begleiten neue Talente für Dogi.</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -49,9 +49,9 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/data-scouts.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-team-scouts.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/data-scouts.js?v=20260821k"></script>
|
||||
<script src="assets/js/i18n-team-scouts.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
const grid = document.getElementById("scouts-grid");
|
||||
@@ -77,15 +77,16 @@ document.addEventListener("DOMContentLoaded", () => {
|
||||
renderScouts();
|
||||
document.addEventListener("dogi-sprache-geaendert", renderScouts);
|
||||
|
||||
// 21.08.2026: über die Verwaltung angelegte Scouts ergänzen (Nutzer-Wunsch
|
||||
// "überall wo ... Scouts ... sind, will ich dass ich die easy über meine
|
||||
// Verwaltungsseite hinzufüge"). Fügt sich einfach hinten an die von Hand
|
||||
// gepflegte Liste an und rendert danach einmal neu -- die fest
|
||||
// einprogrammierten Scouts (Patrick, Bananenstift) bleiben unverändert.
|
||||
// 21.08.2026: über die Verwaltung angelegte/importierte Scouts einmischen
|
||||
// (Nutzer-Wunsch "überall wo ... Scouts ... sind, will ich dass ich die
|
||||
// easy über meine Verwaltungsseite hinzufüge" + Runde 2: "wenn ich eine
|
||||
// änderung mache ist es auch für jeden"). dogiTeamZusammenfuehren()
|
||||
// überschreibt dabei bereits importierte Scouts (gleicher slug) mit ihrem
|
||||
// aktuellen DB-Stand statt sie zu duplizieren.
|
||||
if (window.dogiTeamLaden) {
|
||||
window.dogiTeamLaden("scout").then((neue) => {
|
||||
if (!neue.length) return;
|
||||
window.SCOUTS = [...window.SCOUTS, ...neue];
|
||||
window.SCOUTS = window.dogiTeamZusammenfuehren(window.SCOUTS, neue);
|
||||
renderScouts();
|
||||
});
|
||||
}
|
||||
|
||||
+6
-6
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Eine persönliche Widmung von Dogi an Tili — seine TikTok-Muddi, die erste und bis heute einzige Streamerin, die ihm all die Zeit treu geblieben ist." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
<style>
|
||||
/* Gleiche Anpassung wie vanvan.html: der site-weit etwas größere
|
||||
Fließtext wirkt bei einem so langen persönlichen Brief zu wuchtig —
|
||||
@@ -153,7 +153,7 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/i18n-tt-muddi.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-tt-muddi.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+6
-6
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="VanVan erzählt ihre Geschichte: wie sie zu Team Dogi kam, wer sie ist und was DogFather und Team Dogi ihr bedeuten." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
<style>
|
||||
/* Auf Wunsch: der site-weit vergrößerte Fließtext wirkte auf VanVans
|
||||
eigener Seite zu groß (viel Text am Stück) — hier gezielt wieder
|
||||
@@ -175,7 +175,7 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/i18n-vanvan.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-vanvan.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+279
-8
@@ -19,10 +19,10 @@
|
||||
<link rel="apple-touch-icon" href="assets/img/apple-touch-icon-admin.png" />
|
||||
<meta name="apple-mobile-web-app-title" content="DogiCrew-Verwaltung" />
|
||||
<meta name="theme-color" content="#0e0a16" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
<style>
|
||||
/* =====================================================================
|
||||
verwaltung.html — Dogis privater Command-Center-Bereich.
|
||||
@@ -880,6 +880,20 @@
|
||||
Speichern automatisch übersetzt.
|
||||
</p>
|
||||
|
||||
<!-- 21.08.2026 (Runde 2, Nutzer-Wunsch: "wenn ich eine änderung mache
|
||||
ist es auch für jeden für die die schon da sind und die neuen"):
|
||||
holt VanVan, Diene, Funny, Miss, Marina, Ghost, Patrick,
|
||||
Bananenstift usw. einmalig aus den bestehenden data-*.js-Dateien
|
||||
in die Datenbank -- danach sind sie hier genauso bearbeitbar wie
|
||||
jedes neu angelegte Mitglied. Übernimmt die vorhandenen, von Hand
|
||||
geschriebenen Übersetzungen 1:1 (übersetzt NICHT erneut
|
||||
maschinell). Mehrfach klickbar, ohne Duplikate zu erzeugen --
|
||||
schon importierte Personen werden dabei einfach übersprungen. -->
|
||||
<div class="text-center" style="margin-bottom:1.6rem;">
|
||||
<button type="button" class="btn btn-outline" id="team-import-btn">📥 Bestehende Mitglieder importieren</button>
|
||||
<p class="small muted" id="team-import-status" style="margin-top:.5rem;"></p>
|
||||
</div>
|
||||
|
||||
<div class="card vw-card vw-shine-card vw-noise" style="max-width:640px;margin:0 auto 2rem;padding:1.5rem;">
|
||||
<h3 id="team-form-titel" style="margin-top:0;">Neues Mitglied</h3>
|
||||
<input type="hidden" id="team-id" value="" />
|
||||
@@ -919,6 +933,40 @@
|
||||
<label for="team-zitat">Zitat/Motto (Deutsch, optional)</label>
|
||||
<input type="text" id="team-zitat" maxlength="200" />
|
||||
</div>
|
||||
|
||||
<!-- 21.08.2026 (Runde 2): dieselbe Feld-Tiefe wie die von Hand
|
||||
gepflegten Profile (siehe data-modis.js: intro/bioHtml/
|
||||
extraCta) -- eingeklappt, weil die meisten Mitglieder das
|
||||
nicht brauchen (nur wenige bestehende Profile wie Diene/
|
||||
Patrick/Bananenstift/Marina nutzen das überhaupt). -->
|
||||
<details style="margin-bottom:1.2rem;">
|
||||
<summary style="cursor:pointer;color:var(--accent);font-weight:600;">Erweitert (optional)</summary>
|
||||
<div style="margin-top:1rem;">
|
||||
<div class="field">
|
||||
<label for="team-intro">Kurzer Intro-Text neben dem Foto (Deutsch, optional, HTML erlaubt)</label>
|
||||
<textarea id="team-intro" maxlength="2000" rows="2" placeholder="Ersetzt/ergänzt die kurze Vorstellung oben direkt neben dem Foto."></textarea>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="team-bio-html">Ausführliche Vorstellung (Deutsch, optional, HTML erlaubt: <p>/<h3>/<strong>/<ul><li>)</label>
|
||||
<textarea id="team-bio-html" maxlength="6000" rows="6" placeholder="Erscheint als breiter Lesebereich unterhalb des Steckbriefs, wie bei Diene/Patrick/Bananenstift."></textarea>
|
||||
</div>
|
||||
<div class="grid grid-2" style="gap:.8rem;">
|
||||
<div class="field">
|
||||
<label for="team-extra-cta-href">Zweiter Button: Ziel (z. B. manager.html)</label>
|
||||
<input type="text" id="team-extra-cta-href" maxlength="300" placeholder="z. B. manager.html" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="team-extra-cta-label">Zweiter Button: Text (Deutsch)</label>
|
||||
<input type="text" id="team-extra-cta-label" maxlength="60" placeholder="z. B. Zum Manager-Profil" />
|
||||
</div>
|
||||
</div>
|
||||
<p class="small muted" style="margin:.3rem 0 0;">
|
||||
Zweiter Button erscheint zusätzlich zu "Profil ansehen" auf der Karte (aktuell nur bei
|
||||
Marina genutzt, Verlinkung zu manager.html).
|
||||
</p>
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<div id="team-rang-felder" style="display:none;">
|
||||
<div class="grid grid-2" style="gap:.8rem;">
|
||||
<div class="field">
|
||||
@@ -961,6 +1009,27 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== "Kategorien & Seitentitel" (21.08.2026, Nutzer-Wunsch: "ich
|
||||
will ich titel und namen von kategorien und seiten ändern können in
|
||||
der verwaltung"). Ändert die Kategorie-Namen (z.B. "Modi") und die
|
||||
Überschrift der jeweiligen öffentlichen Seite -- wirkt sofort
|
||||
überall, wo diese Kategorie auftaucht (siehe data-site-text-key in
|
||||
team-modis.html/team-scouts.html/creator.html/manager.html sowie
|
||||
hier in der Team-Verwaltung selbst). Leeres Feld = zurück zum
|
||||
eingebauten Standardtext. Gleiche TEAM_MANAGE-Berechtigung wie die
|
||||
Team-Verwaltung, weil beides zusammengehört. ===== -->
|
||||
<section class="section-tight" id="site-texts-root" style="display:none;padding-top:0;">
|
||||
<div class="container">
|
||||
<h2 class="text-center" style="margin-bottom:.3rem;">🏷️ Kategorien & Seitentitel</h2>
|
||||
<p class="small muted text-center" style="max-width:64ch;margin:0 auto 1.4rem;">
|
||||
Ändert, wie die vier Team-Kategorien überall auf der Seite (und hier in der Verwaltung)
|
||||
heißen. Deutsch eintippen, der Rest wird automatisch übersetzt. Feld leer lassen und
|
||||
speichern setzt wieder auf den Standardtext zurück.
|
||||
</p>
|
||||
<div class="grid grid-2" id="site-texts-grid" style="gap:1.2rem;max-width:900px;margin:0 auto;"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ===== Supporter-Liste ===== -->
|
||||
<section class="section-tight" id="supp-root" style="display:none;padding-top:0;">
|
||||
<div class="container" id="liste-ansicht">
|
||||
@@ -1135,8 +1204,15 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/admin-auth.js?v=20260821j"></script>
|
||||
<!-- 21.08.2026: für den "Bestehende Mitglieder importieren"-Knopf in der
|
||||
Team-Verwaltung -- liest die von Hand gepflegten Profile aus diesen
|
||||
Dateien aus und bietet an, sie 1:1 in die Datenbank zu übernehmen. -->
|
||||
<script src="assets/js/data-modis.js?v=20260821k"></script>
|
||||
<script src="assets/js/data-scouts.js?v=20260821k"></script>
|
||||
<script src="assets/js/data-creator.js?v=20260821k"></script>
|
||||
<script src="assets/js/data-manager.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
<script src="assets/js/admin-auth.js?v=20260821k"></script>
|
||||
<script>
|
||||
(function () {
|
||||
const API_BASE_URL = "https://postfach.dogfather-universe.com";
|
||||
@@ -1219,6 +1295,8 @@
|
||||
if (window.AdminAuth.sessionHasPermission(session, "TEAM_MANAGE")) {
|
||||
document.getElementById("team-root").style.display = "";
|
||||
await ladeUndRenderTeam();
|
||||
document.getElementById("site-texts-root").style.display = "";
|
||||
await ladeUndRenderSiteTexte();
|
||||
}
|
||||
const pollsBox = document.getElementById("box-abstimmungen");
|
||||
if (window.AdminAuth.sessionHasPermission(session, "POLLS_MANAGE")) {
|
||||
@@ -1240,6 +1318,7 @@
|
||||
document.getElementById("special-event-root").style.display = "none";
|
||||
document.getElementById("testimonials-root").style.display = "none";
|
||||
document.getElementById("team-root").style.display = "none";
|
||||
document.getElementById("site-texts-root").style.display = "none";
|
||||
document.getElementById("box-abstimmungen").style.display = "none";
|
||||
}
|
||||
|
||||
@@ -1729,6 +1808,10 @@
|
||||
document.getElementById("team-tiktok").value = "";
|
||||
document.getElementById("team-bio").value = "";
|
||||
document.getElementById("team-zitat").value = "";
|
||||
document.getElementById("team-intro").value = "";
|
||||
document.getElementById("team-bio-html").value = "";
|
||||
document.getElementById("team-extra-cta-href").value = "";
|
||||
document.getElementById("team-extra-cta-label").value = "";
|
||||
document.getElementById("team-rang").value = "";
|
||||
document.getElementById("team-rang-label").value = "";
|
||||
document.getElementById("team-reihenfolge").value = "0";
|
||||
@@ -1737,6 +1820,7 @@
|
||||
document.getElementById("team-foto-input").value = "";
|
||||
document.getElementById("team-abbrechen").style.display = "none";
|
||||
document.getElementById("team-status").textContent = "";
|
||||
document.getElementById("team-intro").closest("details").open = false;
|
||||
teamHochgeladenesFoto = null;
|
||||
teamRangFelderAnpassen();
|
||||
}
|
||||
@@ -1750,10 +1834,18 @@
|
||||
document.getElementById("team-tiktok").value = m.tiktok || "";
|
||||
document.getElementById("team-bio").value = m.bio?.de || "";
|
||||
document.getElementById("team-zitat").value = m.zitat?.de || "";
|
||||
document.getElementById("team-intro").value = m.intro?.de || "";
|
||||
document.getElementById("team-bio-html").value = m.bioHtml?.de || "";
|
||||
document.getElementById("team-extra-cta-href").value = m.extraCta?.href || "";
|
||||
document.getElementById("team-extra-cta-label").value = m.extraCta?.label?.de || "";
|
||||
document.getElementById("team-rang").value = m.rang || "";
|
||||
document.getElementById("team-rang-label").value = m.rangLabel?.de || "";
|
||||
document.getElementById("team-reihenfolge").value = m.reihenfolge ?? 0;
|
||||
document.getElementById("team-aktiv").checked = m.status !== "pausiert";
|
||||
// "Erweitert" automatisch aufklappen, wenn schon etwas drinsteht --
|
||||
// sonst würde man leicht übersehen, dass z.B. Diene eine lange
|
||||
// bioHtml-Vorstellung hat, weil sie eingeklappt verborgen bliebe.
|
||||
document.getElementById("team-intro").closest("details").open = !!(m.intro?.de || m.bioHtml?.de || m.extraCta?.href);
|
||||
teamHochgeladenesFoto = m.foto || null;
|
||||
const vorschau = document.getElementById("team-foto-vorschau");
|
||||
if (teamHochgeladenesFoto) {
|
||||
@@ -1831,6 +1923,10 @@
|
||||
tiktok: document.getElementById("team-tiktok").value.trim(),
|
||||
bio: document.getElementById("team-bio").value.trim(),
|
||||
zitat: document.getElementById("team-zitat").value.trim(),
|
||||
intro: document.getElementById("team-intro").value.trim(),
|
||||
bioHtml: document.getElementById("team-bio-html").value.trim(),
|
||||
extraCtaHref: document.getElementById("team-extra-cta-href").value.trim(),
|
||||
extraCtaLabel: document.getElementById("team-extra-cta-label").value.trim(),
|
||||
rang: document.getElementById("team-rang").value,
|
||||
rangLabel: document.getElementById("team-rang-label").value.trim(),
|
||||
reihenfolge: document.getElementById("team-reihenfolge").value,
|
||||
@@ -1842,10 +1938,15 @@
|
||||
statusEl.style.color = "#ff6b6b";
|
||||
return;
|
||||
}
|
||||
statusEl.textContent = "✅ Gespeichert.";
|
||||
statusEl.style.color = "#baf7d6";
|
||||
teamFormularZuruecksetzen();
|
||||
await ladeUndRenderTeam();
|
||||
// WICHTIG: erst NACH teamFormularZuruecksetzen() setzen -- die Funktion
|
||||
// leert team-status als Teil des normalen Zurücksetzens, ein Erfolgs-
|
||||
// text davor wäre also sofort wieder verschwunden, bevor er je zu sehen
|
||||
// war (mit Playwright gefunden: der Status blieb nach dem Speichern
|
||||
// immer leer).
|
||||
document.getElementById("team-status").textContent = "✅ Gespeichert.";
|
||||
document.getElementById("team-status").style.color = "#baf7d6";
|
||||
});
|
||||
|
||||
document.getElementById("team-filter-bar").addEventListener("click", (e) => {
|
||||
@@ -1859,6 +1960,176 @@
|
||||
|
||||
teamRangFelderAnpassen();
|
||||
|
||||
/* "Bestehende Mitglieder importieren" (21.08.2026, Runde 2, Nutzer-Wunsch:
|
||||
"wenn ich eine änderung mache ist es auch für jeden für die die schon
|
||||
da sind und die neuen"). Liest VanVan/Diene/Funny/Miss/Marina/Ghost
|
||||
(window.MODIS, VanVan bewusst ausgenommen -- sie hat ihre eigene,
|
||||
abweichend gebaute Hervorhebungs-Karte + eigene Seite vanvan.html,
|
||||
passt nicht ins generische Schema), Patrick/Bananenstift
|
||||
(window.SCOUTS) sowie window.CREATORS/MANAGERS (aktuell leer) aus und
|
||||
schickt jede Person 1:1 an /admin/team/import. Übernimmt die
|
||||
VORHANDENEN, von Hand geschriebenen Übersetzungen -- KEINE erneute
|
||||
Maschinenübersetzung. Mehrfach klickbar (idempotent, siehe
|
||||
routes/team.js): bereits importierte Personen werden serverseitig
|
||||
einfach übersprungen, nie dupliziert. */
|
||||
function zuSprachObjekt(feld) {
|
||||
if (!feld) return null;
|
||||
if (typeof feld === "string") return { de: feld, "de-CH": feld, en: feld, fr: feld, pt: feld };
|
||||
if (typeof feld === "object" && feld.de) return feld;
|
||||
return null;
|
||||
}
|
||||
function slugAusNameClient(name) {
|
||||
return String(name || "")
|
||||
.toLowerCase()
|
||||
.replace(/ä/g, "ae").replace(/ö/g, "oe").replace(/ü/g, "ue").replace(/ß/g, "ss")
|
||||
.replace(/[^a-z0-9]+/g, "-")
|
||||
.replace(/^-+|-+$/g, "");
|
||||
}
|
||||
|
||||
document.getElementById("team-import-btn").addEventListener("click", async () => {
|
||||
const btn = document.getElementById("team-import-btn");
|
||||
const statusEl = document.getElementById("team-import-status");
|
||||
btn.disabled = true;
|
||||
statusEl.textContent = "Importiere …";
|
||||
|
||||
const quellen = [
|
||||
...(window.MODIS || []).filter((p) => p.name !== "VanVan").map((p) => ({ p, kategorie: "modi" })),
|
||||
...(window.SCOUTS || []).map((p) => ({ p, kategorie: "scout" })),
|
||||
...(window.CREATORS || []).map((p) => ({ p, kategorie: "creator" })),
|
||||
...(window.MANAGERS || []).map((p) => ({ p, kategorie: "manager" })),
|
||||
];
|
||||
|
||||
let importiert = 0, uebersprungen = 0, fehler = 0;
|
||||
for (const { p, kategorie } of quellen) {
|
||||
const rolle = zuSprachObjekt(p.role);
|
||||
if (!rolle) { fehler++; continue; } // Pflichtfeld fehlt -- diese Person überspringen, nicht alle abbrechen
|
||||
const payload = {
|
||||
kategorie,
|
||||
name: p.name,
|
||||
slug: p.slug || slugAusNameClient(p.name),
|
||||
rolle,
|
||||
foto: p.photo || p.avatar || "",
|
||||
tiktok: p.social?.tiktok || null,
|
||||
bio: zuSprachObjekt(p.bio),
|
||||
intro: zuSprachObjekt(p.intro),
|
||||
bioHtml: zuSprachObjekt(p.bioHtml),
|
||||
zitat: zuSprachObjekt(p.quote),
|
||||
rang: p.rank || null,
|
||||
rangLabel: zuSprachObjekt(p.rankLabel),
|
||||
extraCta: p.extraCta?.href ? { href: p.extraCta.href, label: zuSprachObjekt(p.extraCta.label) } : null,
|
||||
};
|
||||
try {
|
||||
const res = await window.AdminAuth.apiCall(API_BASE_URL, "/admin/team/import", payload);
|
||||
if (res.ok && res.uebersprungen) uebersprungen++;
|
||||
else if (res.ok) importiert++;
|
||||
else fehler++;
|
||||
} catch {
|
||||
fehler++;
|
||||
}
|
||||
}
|
||||
|
||||
statusEl.textContent = `✅ ${importiert} importiert, ${uebersprungen} schon vorhanden` + (fehler ? `, ${fehler} übersprungen (fehlerhaft)` : "") + ".";
|
||||
btn.disabled = false;
|
||||
await ladeUndRenderTeam();
|
||||
});
|
||||
|
||||
/* ===== "Kategorien & Seitentitel" (21.08.2026) =====
|
||||
Ein Kärtchen pro Kategorie mit zwei Feldern (Kategorie-Name,
|
||||
Seitentitel) -- speichert beide über denselben admin/site-texts/save-
|
||||
Endpunkt wie zwei unabhängige Overrides. Aktualisiert nach dem
|
||||
Speichern sofort die Kategorie-Badges in der Team-Liste, damit man den
|
||||
Effekt nicht erst durch Neuladen sehen muss. */
|
||||
const SITE_TEXT_KATEGORIEN = [
|
||||
{ kat: "modi", label: "Modi", kategorieKey: "kategorie_modi", seitentitelKey: "seitentitel_modi", kategoriePlatzhalter: "Modi", seitentitelPlatzhalter: "Das Modi-Team" },
|
||||
{ kat: "scout", label: "Scout", kategorieKey: "kategorie_scout", seitentitelKey: "seitentitel_scout", kategoriePlatzhalter: "Scouts", seitentitelPlatzhalter: "Mein Scout-Team" },
|
||||
{ kat: "creator", label: "Creator", kategorieKey: "kategorie_creator", seitentitelKey: "seitentitel_creator", kategoriePlatzhalter: "Creator", seitentitelPlatzhalter: "Creator im Team" },
|
||||
{ kat: "manager", label: "Manager", kategorieKey: "kategorie_manager", seitentitelKey: "seitentitel_manager", kategoriePlatzhalter: "Manager", seitentitelPlatzhalter: "Weitere Manager" },
|
||||
];
|
||||
|
||||
function siteTextKarteHtml(eintrag) {
|
||||
return `<div class="card vw-card vw-shine-card vw-noise" style="padding:1.3rem;" data-site-text-kat="${eintrag.kat}">
|
||||
<h3 style="margin-top:0;">${eintrag.label}</h3>
|
||||
<div class="field">
|
||||
<label>Kategorie-Name</label>
|
||||
<input type="text" class="site-text-kategorie-input" maxlength="40" placeholder="${eintrag.kategoriePlatzhalter}" />
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Seitentitel</label>
|
||||
<input type="text" class="site-text-seitentitel-input" maxlength="100" placeholder="${eintrag.seitentitelPlatzhalter}" />
|
||||
</div>
|
||||
<button type="button" class="btn btn-outline site-text-speichern-btn" style="width:100%;">Speichern</button>
|
||||
<p class="small site-text-status" style="margin-top:.5rem;"></p>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
async function teamKategorieLabelsAktualisieren(texteVorhanden) {
|
||||
let texte = texteVorhanden;
|
||||
if (!texte) {
|
||||
try {
|
||||
const res = await fetch(`${API_BASE_URL}/site-texts`, { cache: "no-store" });
|
||||
const data = await res.json();
|
||||
texte = data.ok ? data.texte || {} : {};
|
||||
} catch {
|
||||
texte = {};
|
||||
}
|
||||
}
|
||||
Object.keys(TEAM_KATEGORIE_LABELS).forEach((kat) => {
|
||||
const override = texte[`kategorie_${kat}`]?.de;
|
||||
if (override) TEAM_KATEGORIE_LABELS[kat] = override;
|
||||
});
|
||||
document.querySelectorAll("#team-filter-bar .filter-btn[data-team-filter]").forEach((btn) => {
|
||||
const kat = btn.dataset.teamFilter;
|
||||
if (TEAM_KATEGORIE_LABELS[kat]) btn.textContent = TEAM_KATEGORIE_LABELS[kat];
|
||||
});
|
||||
}
|
||||
|
||||
async function ladeUndRenderSiteTexte() {
|
||||
const grid = document.getElementById("site-texts-grid");
|
||||
grid.innerHTML = SITE_TEXT_KATEGORIEN.map(siteTextKarteHtml).join("");
|
||||
|
||||
let aktuelleTexte = {};
|
||||
try {
|
||||
const res = await fetch(`${API_BASE_URL}/site-texts`, { cache: "no-store" });
|
||||
const data = await res.json();
|
||||
if (data.ok) aktuelleTexte = data.texte || {};
|
||||
} catch {
|
||||
/* Formular bleibt leer (= eingebauter Standardtext), Speichern funktioniert trotzdem. */
|
||||
}
|
||||
|
||||
SITE_TEXT_KATEGORIEN.forEach((eintrag) => {
|
||||
const karte = grid.querySelector(`[data-site-text-kat="${eintrag.kat}"]`);
|
||||
karte.querySelector(".site-text-kategorie-input").value = aktuelleTexte[eintrag.kategorieKey]?.de || "";
|
||||
karte.querySelector(".site-text-seitentitel-input").value = aktuelleTexte[eintrag.seitentitelKey]?.de || "";
|
||||
|
||||
karte.querySelector(".site-text-speichern-btn").addEventListener("click", async () => {
|
||||
const statusEl = karte.querySelector(".site-text-status");
|
||||
const btn = karte.querySelector(".site-text-speichern-btn");
|
||||
btn.disabled = true;
|
||||
statusEl.textContent = "Wird gespeichert …";
|
||||
statusEl.style.color = "";
|
||||
const kategorieText = karte.querySelector(".site-text-kategorie-input").value.trim();
|
||||
const seitentitelText = karte.querySelector(".site-text-seitentitel-input").value.trim();
|
||||
const [r1, r2] = await Promise.all([
|
||||
window.AdminAuth.apiCall(API_BASE_URL, "/admin/site-texts/save", { key: eintrag.kategorieKey, text: kategorieText }),
|
||||
window.AdminAuth.apiCall(API_BASE_URL, "/admin/site-texts/save", { key: eintrag.seitentitelKey, text: seitentitelText }),
|
||||
]);
|
||||
btn.disabled = false;
|
||||
if (!r1.ok || !r2.ok) {
|
||||
statusEl.textContent = r1.error || r2.error || "Speichern fehlgeschlagen.";
|
||||
statusEl.style.color = "#ff6b6b";
|
||||
return;
|
||||
}
|
||||
statusEl.textContent = "✅ Gespeichert — wirkt sofort auf der Website.";
|
||||
statusEl.style.color = "#baf7d6";
|
||||
await teamKategorieLabelsAktualisieren();
|
||||
teamListeZeichnen();
|
||||
});
|
||||
});
|
||||
|
||||
await teamKategorieLabelsAktualisieren(aktuelleTexte);
|
||||
teamListeZeichnen();
|
||||
}
|
||||
|
||||
// Nutzer-Wunsch 04.08.2026 (6. Runde): zwei getrennte Kisten/Formulare
|
||||
// (Dogi + VanVan) statt einem gemeinsamen Feld — beide rufen denselben
|
||||
// Login-Endpunkt auf, die Fehlermeldung erscheint jeweils nur in der
|
||||
|
||||
+6
-6
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Die Werte, nach denen DogFather als Manager bei Spicy Media arbeitet." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-werte.jpg');--page-bg-mobile:url('/assets/img/bg-werte-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
@@ -113,7 +113,7 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/i18n-werte.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-werte.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+6
-6
@@ -19,10 +19,10 @@
|
||||
<meta name="twitter:description" content="Die Geschichte von DogFather in einer interaktiven Zeitleiste." />
|
||||
<meta name="twitter:image" content="https://dogfather-universe.com/assets/img/og-cover.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821j" />
|
||||
<link rel="stylesheet" href="assets/css/main.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-dogfather.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-hasidog.css?v=20260821k" />
|
||||
<link rel="stylesheet" href="assets/css/theme-spicymedia.css?v=20260821k" />
|
||||
</head>
|
||||
<body class="mit-hintergrund" style="--page-bg:url('/assets/img/bg-zeitreise.jpg');--page-bg-mobile:url('/assets/img/bg-zeitreise-mobile.jpg');">
|
||||
<div id="site-header"></div>
|
||||
@@ -237,8 +237,8 @@
|
||||
</main>
|
||||
|
||||
<div id="site-footer"></div>
|
||||
<script src="assets/js/i18n-zeitreise.js?v=20260821j"></script>
|
||||
<script src="assets/js/main.js?v=20260821j"></script>
|
||||
<script src="assets/js/i18n-zeitreise.js?v=20260821k"></script>
|
||||
<script src="assets/js/main.js?v=20260821k"></script>
|
||||
<script>
|
||||
/* Wachstums-Reveal: Äste blenden sich beim Scrollen sanft ein — passt
|
||||
zum "wachsenden Baum"-Konzept dieser Seite. Rein page-lokal, betrifft
|
||||
|
||||
Reference in New Issue
Block a user