const STECKBRIEF_FIELDS = [ ['spitzname', 'Spitzname'], ['geburtstag', 'Geburtstag'], ['adresse', 'Adresse / Wohnort'], ['lieblingsfarbe', 'Lieblingsfarbe'], ['lieblingsessen', 'Lieblingsessen'], ['lieblingstier', 'Lieblingstier'], ['hobbys', 'Hobbys'], ['lieblingsmusik', 'Lieblingsmusik'], ['lieblingsfilm', 'Lieblingsfilm/-serie'], ['traumberuf', 'Traumberuf'], ['bestefreunde', 'Beste Freunde'], ['motto', 'Motto / Lebensweisheit'], ['sonstiges', 'Sonstiges'], ]; const pathParts = window.location.pathname.split('/').filter(Boolean); // ['friend', ':id'] or ['share', ':id'] const mode = pathParts[0]; // 'friend' | 'share' const friendId = pathParts[1]; let currentFriend = null; let currentFolder = null; let selectedType = 'image'; function escapeHtml(str) { const div = document.createElement('div'); div.textContent = str || ''; return div.innerHTML; } async function init() { const sessionRes = await fetch('/api/session'); const session = await sessionRes.json(); const isAdmin = session.role === 'admin'; const isMatchingFriend = session.role === 'friend' && session.friendId === friendId; document.getElementById('adminBackWrap').style.display = isAdmin ? 'block' : 'none'; if (isAdmin || isMatchingFriend) { loadFriend(); } else if (mode === 'share') { showLoginGate(); } else { // /friend/:id ohne Admin-Session -> zur Startseite window.location.href = '/'; } } function showLoginGate() { document.getElementById('loginGate').style.display = 'block'; document.getElementById('editorRoot').style.display = 'none'; } document.getElementById('gateForm').addEventListener('submit', async (e) => { e.preventDefault(); const password = document.getElementById('gatePassword').value; const errBox = document.getElementById('gateErr'); errBox.innerHTML = ''; const res = await fetch(`/api/share/${friendId}/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password }), }); const data = await res.json(); if (!res.ok) { errBox.innerHTML = `

${data.error}

`; return; } loadFriend(); }); async function loadFriend() { const res = await fetch(`/api/friends/${friendId}`); if (res.status === 401) { showLoginGate(); return; } if (res.status === 404) { document.body.innerHTML = '

Diesen Freund gibt es nicht (mehr).

'; return; } const data = await res.json(); currentFriend = data.friend; currentFolder = currentFriend.folderName; document.getElementById('loginGate').style.display = 'none'; document.getElementById('editorRoot').style.display = 'block'; document.getElementById('pageTitle').textContent = `📖 ${currentFriend.name}`; renderProfileImage(); renderSteckbrief(); renderBlocks(); } function uploadUrl(filename) { return `/uploads/${encodeURIComponent(currentFolder)}/${encodeURIComponent(filename)}`; } function renderProfileImage() { const img = document.getElementById('profileImg'); const placeholder = document.getElementById('profilePlaceholder'); if (currentFriend.profileImage) { img.src = uploadUrl(currentFriend.profileImage) + `?t=${Date.now()}`; img.style.display = 'block'; placeholder.style.display = 'none'; } else { img.style.display = 'none'; placeholder.style.display = 'flex'; } } document.getElementById('profileInput').addEventListener('change', async (e) => { const file = e.target.files[0]; if (!file) return; const fd = new FormData(); fd.append('image', file); const res = await fetch(`/api/friends/${friendId}/profileimage`, { method: 'POST', body: fd }); const data = await res.json(); if (res.ok) { currentFriend = data.friend; renderProfileImage(); } else { alert(data.error || 'Upload fehlgeschlagen.'); } }); function renderSteckbrief() { const form = document.getElementById('steckbriefForm'); form.innerHTML = ''; STECKBRIEF_FIELDS.forEach(([key, label]) => { const wrap = document.createElement('div'); const val = (currentFriend.steckbrief && currentFriend.steckbrief[key]) || ''; wrap.innerHTML = ``; form.appendChild(wrap); }); } document.getElementById('steckbriefForm').addEventListener('submit', async (e) => { e.preventDefault(); const inputs = document.querySelectorAll('#steckbriefForm input'); const payload = {}; inputs.forEach((inp) => { payload[inp.dataset.key] = inp.value; }); const res = await fetch(`/api/friends/${friendId}/steckbrief`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await res.json(); if (res.ok) { currentFriend = data.friend; const savedEl = document.getElementById('sbSaved'); savedEl.textContent = 'Gespeichert ✓'; savedEl.classList.add('show'); setTimeout(() => savedEl.classList.remove('show'), 1800); } else { alert(data.error || 'Speichern fehlgeschlagen.'); } }); // --- Block-Typ-Auswahl --- document.querySelectorAll('.tab-btn').forEach((btn) => { btn.addEventListener('click', () => { selectedType = btn.dataset.type; document.querySelectorAll('.tab-btn').forEach((b) => b.classList.toggle('active', b === btn)); document.getElementById('blockType').value = selectedType; const fileWrap = document.getElementById('blockFileWrap'); const textWrap = document.getElementById('blockTextWrap'); const coverWrap = document.getElementById('blockCoverWrap'); const fileLabel = document.getElementById('blockFileLabel'); const fileInput = document.getElementById('blockFile'); if (selectedType === 'text') { fileWrap.style.display = 'none'; textWrap.style.display = 'block'; coverWrap.style.display = 'none'; } else { fileWrap.style.display = 'block'; textWrap.style.display = 'none'; fileLabel.textContent = selectedType === 'image' ? 'Bild auswählen' : 'MP3 auswählen'; fileInput.accept = selectedType === 'image' ? 'image/*' : 'audio/*'; coverWrap.style.display = selectedType === 'audio' ? 'block' : 'none'; } }); }); document.getElementById('blockForm').addEventListener('submit', async (e) => { e.preventDefault(); const fd = new FormData(); fd.append('type', selectedType); fd.append('caption', document.getElementById('blockCaption').value); if (selectedType === 'text') { fd.append('content', document.getElementById('blockContent').value); } else { const file = document.getElementById('blockFile').files[0]; if (!file) { alert('Bitte eine Datei auswählen.'); return; } fd.append('file', file); if (selectedType === 'audio') { const coverFile = document.getElementById('blockCover').files[0]; if (coverFile) fd.append('cover', coverFile); } } const res = await fetch(`/api/friends/${friendId}/blocks`, { method: 'POST', body: fd }); const data = await res.json(); if (res.ok) { currentFriend = data.friend; document.getElementById('blockForm').reset(); document.getElementById('blockCoverWrap').style.display = selectedType === 'audio' ? 'block' : 'none'; renderBlocks(); } else { alert(data.error || 'Hinzufügen fehlgeschlagen.'); } }); function renderBlocks() { const list = document.getElementById('blocksList'); list.innerHTML = ''; const blocks = [...(currentFriend.blocks || [])].sort((a, b) => a.order - b.order); blocks.forEach((b, idx) => { const el = document.createElement('div'); el.className = 'block-item'; const tag = { image: '🖼️ Bild', text: '📝 Text', audio: '🎙️ Audio' }[b.type] || b.type; let inner = `
${tag}
`; if (b.type === 'image') { inner += ``; } else if (b.type === 'audio') { inner += b.coverFilename ? `` : ''; inner += ``; } else { inner += ``; } inner += ``; inner += `
`; el.innerHTML = inner; list.appendChild(el); }); list.querySelectorAll('[data-save]').forEach((btn) => btn.addEventListener('click', () => saveBlock(btn.dataset.save))); list.querySelectorAll('[data-del]').forEach((btn) => btn.addEventListener('click', () => deleteBlock(btn.dataset.del))); list.querySelectorAll('[data-up]').forEach((btn) => btn.addEventListener('click', () => moveBlock(btn.dataset.up, -1))); list.querySelectorAll('[data-down]').forEach((btn) => btn.addEventListener('click', () => moveBlock(btn.dataset.down, 1))); } async function saveBlock(id) { const captionInput = document.querySelector(`[data-caption-for="${id}"]`); const contentInput = document.querySelector(`[data-content-for="${id}"]`); const payload = { caption: captionInput ? captionInput.value : '' }; if (contentInput) payload.content = contentInput.value; const res = await fetch(`/api/friends/${friendId}/blocks/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }); const data = await res.json(); if (res.ok) { currentFriend = data.friend; renderBlocks(); } } async function deleteBlock(id) { if (!confirm('Diesen Block wirklich löschen?')) return; const res = await fetch(`/api/friends/${friendId}/blocks/${id}`, { method: 'DELETE' }); const data = await res.json(); if (res.ok) { currentFriend = data.friend; renderBlocks(); } } async function moveBlock(id, dir) { const blocks = [...(currentFriend.blocks || [])].sort((a, b) => a.order - b.order); const idx = blocks.findIndex((b) => b.id === id); const swapIdx = idx + dir; if (swapIdx < 0 || swapIdx >= blocks.length) return; [blocks[idx], blocks[swapIdx]] = [blocks[swapIdx], blocks[idx]]; const order = blocks.map((b) => b.id); const res = await fetch(`/api/friends/${friendId}/blocks/reorder`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ order }), }); const data = await res.json(); if (res.ok) { currentFriend = data.friend; renderBlocks(); } } // --- Vorschau: rendert die Seite genauso wie später im Freundesbuch-Viewer --- function randomRotation(seed) { const angles = [-6, -4, -2, 2, 4, 6, -3, 3, 5, -5]; return angles[seed % angles.length]; } function renderPreview() { const f = currentFriend; const container = document.getElementById('previewContent'); const profileHtml = f.profileImage ? `` : `
${(f.name || '?')[0].toUpperCase()}
`; const sbEntries = STECKBRIEF_FIELDS .map(([key, label]) => [label, (f.steckbrief || {})[key]]) .filter(([, v]) => v && String(v).trim()); const sbHtml = sbEntries.length ? `
${sbEntries.map(([label, v]) => `
${escapeHtml(label)}${escapeHtml(v)}
`).join('')}
` : `

Noch nichts eingetragen.

`; const blocks = [...(f.blocks || [])].sort((a, b) => a.order - b.order); const blocksHtml = blocks.length ? blocks.map((b, i) => { const rot = randomRotation(i + f.id.length); if (b.type === 'image') { return `
${b.caption ? `
${escapeHtml(b.caption)}
` : ''}
`; } if (b.type === 'audio') { return `
${b.coverFilename ? `` : ''}
🎙️${b.caption ? ' ' + escapeHtml(b.caption) : ''}
`; } return `

${escapeHtml(b.content).replace(/\n/g, '
')}

${b.caption ? `
${escapeHtml(b.caption)}
` : ''}
`; }).join('') : '

Noch keine Erinnerungen hinzugefügt.

'; container.innerHTML = `
${profileHtml}

${escapeHtml(f.name)}

Steckbrief

${sbHtml}
${blocksHtml}
`; } document.getElementById('previewBtn').addEventListener('click', () => { renderPreview(); document.getElementById('previewModal').style.display = 'flex'; }); document.getElementById('previewCloseBtn').addEventListener('click', () => { document.getElementById('previewModal').style.display = 'none'; }); document.getElementById('previewModal').addEventListener('click', (e) => { if (e.target.id === 'previewModal') document.getElementById('previewModal').style.display = 'none'; }); init();