/* =================================================================== Creator-Profil. Welche Felder überhaupt ankommen, entscheidet der Server anhand der Rolle -- `admin_notiz` fehlt in der Antwort an einen Creator komplett. Diese Datei blendet deshalb nur aus, was ohnehin nicht da ist. =================================================================== */ (() => { 'use strict'; /* DogFather und Manager haben dieselben Rechte -- die Unterscheidung macht nur die Personenverwaltung, und die prueft der Server. */ const LEITUNG = new Set(['admin', 'manager']); const OFFEN = ['handles', 'nische', 'live_zeiten', 'technik', 'ziel_live', 'ziel_content', 'ziel_community', 'ziel_technik', 'plan_prio1', 'plan_prio2', 'plan_prio3']; const NUR_ADMIN = ['plan_start', 'naechster_review', 'admin_notiz']; const $ = (id) => document.getElementById(id); let ich = null; let aktuell = null; let darfAlles = false; const melde = (t) => { $('fehler').textContent = t || ''; }; async function hole(pfad, optionen = {}) { const a = await fetch(pfad, { credentials: 'same-origin', ...optionen }); if (a.status === 401) { location.assign('/workspace/'); throw new Error('abgemeldet'); } return a; } function fuelle(profil) { for (const f of [...OFFEN, ...NUR_ADMIN]) { const feld = $(f); if (!feld) continue; /* undefined heißt: Der Server hat das Feld nicht geschickt (fehlende Berechtigung). Dann bleibt es leer und wird auch nicht gesendet. */ feld.value = profil[f] ?? ''; } } function datumHuebsch(iso) { return iso.slice(0, 10).split('-').reverse().join('.'); } async function profilLaden(id) { melde(''); try { const a = await hole('/workspace/api/profil/' + id); if (!a.ok) { melde('Profil konnte nicht geladen werden.'); return; } const daten = await a.json(); aktuell = daten.person.id; darfAlles = daten.darf_alles; $('titel').textContent = LEITUNG.has(ich.rolle) ? 'Profil: ' + daten.person.name : 'Mein Profil'; $('unterzeile').textContent = daten.profil.geaendert ? 'Zuletzt geändert am ' + datumHuebsch(daten.profil.geaendert) : 'Noch nichts eingetragen.'; $('gruppe-intern').hidden = !darfAlles; $('termine').hidden = !darfAlles; fuelle(daten.profil); $('formular').hidden = false; } catch { /* umgeleitet */ } } $('formular').addEventListener('submit', async (e) => { e.preventDefault(); melde(''); const knopf = $('speichern'); const daten = {}; for (const f of darfAlles ? [...OFFEN, ...NUR_ADMIN] : OFFEN) { const feld = $(f); if (feld) daten[f] = feld.value; } knopf.disabled = true; $('stand').textContent = 'Speichere …'; try { const a = await hole('/workspace/api/profil/' + aktuell, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(daten), }); if (!a.ok) { melde((await a.json().catch(() => ({}))).fehler || 'Speichern ging nicht.'); $('stand').textContent = ''; return; } $('stand').textContent = 'Gespeichert ✓'; setTimeout(() => { $('stand').textContent = ''; }, 2600); } catch { /* umgeleitet */ } finally { knopf.disabled = false; } }); (async () => { try { const a = await hole('/workspace/api/ich'); if (!a.ok) { location.assign('/workspace/'); return; } ich = await a.json(); } catch { return; } /* Scouts betreuen ihre zugeteilten Creator wie DogFather -- sie sehen aber nur die management-internen Felder nicht. */ window.werZeigen?.(ich); let liste; try { liste = await (await hole('/workspace/api/profil')).json(); } catch { return; } if (LEITUNG.has(ich.rolle) || ich.rolle === 'scout') { if (!liste.creator.length) { $('unterzeile').textContent = ich.rolle === 'scout' ? 'Dir ist noch kein Creator zugeteilt. DogFather legt fest, wen du betreust.' : 'Es gibt noch keinen Creator. Lege zuerst unter Personen & Zugänge einen an.'; return; } if (ich.rolle === 'scout') { $('unterzeile').textContent = liste.creator.length === 1 ? 'Der Creator, den du betreust.' : `Die ${liste.creator.length} Creator, die du betreust.`; } $('auswahl-block').hidden = false; for (const c of liste.creator) { const o = document.createElement('option'); o.value = String(c.id); o.textContent = c.name + (c.aktiv ? '' : ' (gesperrt)'); $('auswahl').append(o); } $('auswahl').addEventListener('change', () => profilLaden(Number($('auswahl').value))); await profilLaden(liste.creator[0].id); } else { await profilLaden(ich.id); } })(); })(); /* =================================================================== Der eigene Steckbrief. Läuft unabhängig vom Creator-Formular darüber: Den Steckbrief hat JEDER (auch Scouts, Manager und DogFather), die Creator-Akte nur ein Creator. Deshalb ein eigener, abgeschlossener Block -- fällt einer von beiden aus, steht der andere weiterhin. =================================================================== */ (() => { 'use strict'; const $ = (id) => document.getElementById(id); const block = $('steckbrief'); if (!block) return; let stand = null; async function hole(pfad, optionen = {}) { const a = await fetch(pfad, { credentials: 'same-origin', ...optionen }); if (a.status === 401) { location.assign('/workspace/'); throw new Error('abgemeldet'); } return a; } const melde = (t) => { $('sb-fehler').textContent = t || ''; }; const sage = (t) => { $('sb-stand').textContent = t || ''; if (t) setTimeout(() => { if ($('sb-stand').textContent === t) $('sb-stand').textContent = ''; }, 4000); }; function bildZeigen(quelle, name) { const feld = $('sb-bild'); feld.textContent = ''; if (quelle) { const b = document.createElement('img'); b.src = quelle; b.alt = ''; /* Fällt das Bild aus, erscheint der Buchstabe -- nicht das kaputte Bildsymbol des Browsers. */ b.addEventListener('error', () => { b.remove(); feld.textContent = anfang(name); }); feld.append(b); $('sb-bild-weg').hidden = false; } else { feld.textContent = anfang(name); $('sb-bild-weg').hidden = true; } } const anfang = (n) => (n || '?').trim().charAt(0).toUpperCase(); function kanaeleBauen(kanaele, vorhanden) { const ziel = $('sb-kanaele'); ziel.textContent = ''; for (const k of kanaele) { const zeile = document.createElement('div'); zeile.className = 'kanal'; zeile.dataset.art = k.art; const schild = document.createElement('label'); schild.className = 'kanal__name'; schild.setAttribute('for', 'sb-' + k.art); schild.textContent = k.name; const feld = document.createElement('div'); feld.className = 'kanal__feld'; const at = document.createElement('span'); at.className = 'kanal__at'; at.setAttribute('aria-hidden', 'true'); at.textContent = '@'; const eingabe = document.createElement('input'); eingabe.id = 'sb-' + k.art; eingabe.type = 'text'; eingabe.maxLength = 40; eingabe.autocomplete = 'off'; eingabe.placeholder = 'dein Name'; const da = vorhanden.find((v) => v.art === k.art); eingabe.value = da ? da.handle : ''; feld.append(at, eingabe); zeile.append(schild, feld); /* Ein Verweis erscheint erst, wenn wirklich ein Name da ist -- ein Knopf, der ins Leere führt, ist schlimmer als keiner. */ if (da) { const a = document.createElement('a'); a.className = 'kanal__auf'; a.href = da.adresse; a.target = '_blank'; a.rel = 'noopener noreferrer'; a.textContent = 'öffnen'; zeile.append(a); } ziel.append(zeile); } } function zeichne(sb, kanaele) { stand = sb; $('sb-name').textContent = sb.name; $('sb-rolle').textContent = sb.rolle_name; $('sb-rolle').dataset.rolle = sb.rolle; $('sb-ueber').value = sb.ueber_mich || ''; bildZeigen(sb.bild, sb.name); kanaeleBauen(kanaele, sb.kanaele); block.hidden = false; } /* ---------- Bild hochladen --------------------------------------------- Roh im Rumpf statt als multipart -- dasselbe Verfahren wie in der Dateiablage. Kein Parsen von multipart im Server, keine zusätzliche Abhängigkeit. */ $('sb-datei').addEventListener('change', async (e) => { const datei = e.target.files?.[0]; if (!datei) return; melde(''); if (datei.size > 4 * 1024 * 1024) { melde('Das Bild ist größer als 4 MB. Bitte ein kleineres nehmen.'); e.target.value = ''; return; } sage('Bild wird hochgeladen …'); try { const a = await hole('/workspace/api/steckbrief/bild', { method: 'POST', headers: { 'Content-Type': 'application/octet-stream' }, body: datei, }); const antwort = await a.json().catch(() => ({})); if (!a.ok) { melde(antwort.fehler || 'Das Hochladen ging nicht.'); sage(''); return; } bildZeigen(antwort.bild, stand?.name); sage('Bild gespeichert.'); /* Auch oben rechts in der Kopfleiste soll sofort das neue Bild stehen -- sonst müsste man die Seite neu laden, um es zu sehen. */ const oben = document.querySelector('.wer__zeichen'); if (oben) { oben.querySelector('.wer__bild')?.remove(); const b = document.createElement('img'); b.className = 'wer__bild'; b.src = antwort.bild; b.alt = ''; oben.append(b); } } catch { /* umgeleitet */ } finally { e.target.value = ''; } }); $('sb-bild-weg').addEventListener('click', async () => { if (!confirm('Profilbild entfernen?')) return; try { const a = await hole('/workspace/api/steckbrief/bild', { method: 'DELETE' }); if (!a.ok) { melde('Entfernen ging nicht.'); return; } bildZeigen(null, stand?.name); document.querySelector('.wer__bild')?.remove(); sage('Bild entfernt.'); } catch { /* umgeleitet */ } }); /* ---------- Speichern ---------------------------------------------------- */ $('sb-speichern').addEventListener('click', async () => { melde(''); const knopf = $('sb-speichern'); const koerper = { ueber_mich: $('sb-ueber').value }; for (const zeile of document.querySelectorAll('#sb-kanaele .kanal')) { koerper[zeile.dataset.art] = zeile.querySelector('input').value; } knopf.disabled = true; try { const a = await hole('/workspace/api/steckbrief/mein', { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(koerper), }); const antwort = await a.json().catch(() => ({})); if (!a.ok) { melde(antwort.fehler || 'Speichern ging nicht.'); return; } /* Neu zeichnen, damit die "öffnen"-Verweise zu den frisch eingetragenen Namen sofort erscheinen. */ zeichne(antwort.steckbrief, KANAELE); sage('Profil gespeichert.'); } catch { /* umgeleitet */ } finally { knopf.disabled = false; } }); let KANAELE = []; (async () => { try { const a = await hole('/workspace/api/steckbrief/mein'); if (!a.ok) return; const { steckbrief, kanaele } = await a.json(); KANAELE = kanaele; zeichne(steckbrief, kanaele); } catch { /* umgeleitet oder nicht verfügbar -- die Seite bleibt nutzbar */ } })(); })();