This commit is contained in:
2026-09-07 13:00:59 +02:00
parent cbe4726c07
commit e0326285d3
12 changed files with 478 additions and 143 deletions

View File

@@ -10,31 +10,15 @@ async function checkSession() {
}
}
// Einstellungen werden im Hintergrund geladen (für den Freigabe-Link),
// bearbeitet werden sie im separaten "Einstellungen"-Tab.
async function loadSettings() {
const res = await fetch('/api/settings');
if (!res.ok) return;
const data = await res.json();
settings = data.settings || { publicBaseUrl: '' };
document.getElementById('publicBaseUrl').value = settings.publicBaseUrl || '';
settings = data.settings || { publicBaseUrl: '', bookTitle: '' };
}
document.getElementById('saveSettingsBtn').addEventListener('click', async () => {
const publicBaseUrl = document.getElementById('publicBaseUrl').value.trim();
const res = await fetch('/api/settings', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ publicBaseUrl }),
});
const data = await res.json();
if (res.ok) {
settings = data.settings;
const msg = document.getElementById('settingsSaved');
msg.textContent = 'Gespeichert ✓';
msg.classList.add('show');
setTimeout(() => msg.classList.remove('show'), 1800);
}
});
function baseUrl() {
return settings.publicBaseUrl && settings.publicBaseUrl.length
? settings.publicBaseUrl
@@ -55,7 +39,7 @@ function renderFriends() {
grid.innerHTML = '';
emptyMsg.style.display = friends.length ? 'none' : 'block';
friends.forEach((f) => {
friends.forEach((f, idx) => {
const el = document.createElement('div');
el.className = 'friend-card';
const thumbHtml = f.profileImage
@@ -63,6 +47,11 @@ function renderFriends() {
: `<div class="friend-thumb placeholder">${(f.name || '?')[0].toUpperCase()}</div>`;
el.innerHTML = `
<div class="tape"></div>
<div class="order-controls">
<button type="button" class="ghost" data-moveup="${f.id}" ${idx === 0 ? 'disabled' : ''} title="Nach vorn">↑</button>
<span class="order-num">${idx + 1}</span>
<button type="button" class="ghost" data-movedown="${f.id}" ${idx === friends.length - 1 ? 'disabled' : ''} title="Nach hinten">↓</button>
</div>
${thumbHtml}
<h3>${escapeHtml(f.name)}</h3>
<span class="badge ${f.shareEnabled ? 'on' : 'off'}">${f.shareEnabled ? 'Freigegeben' : 'Nicht freigegeben'}</span>
@@ -85,6 +74,27 @@ function renderFriends() {
grid.querySelectorAll('[data-export]').forEach((btn) => {
btn.addEventListener('click', () => window.location.href = `/api/friends/${btn.dataset.export}/export`);
});
grid.querySelectorAll('[data-moveup]').forEach((btn) => {
btn.addEventListener('click', () => moveFriend(btn.dataset.moveup, -1));
});
grid.querySelectorAll('[data-movedown]').forEach((btn) => {
btn.addEventListener('click', () => moveFriend(btn.dataset.movedown, 1));
});
}
async function moveFriend(id, dir) {
const idx = friends.findIndex((f) => f.id === id);
const swapIdx = idx + dir;
if (idx === -1 || swapIdx < 0 || swapIdx >= friends.length) return;
const newOrder = [...friends];
[newOrder[idx], newOrder[swapIdx]] = [newOrder[swapIdx], newOrder[idx]];
const order = newOrder.map((f) => f.id);
const res = await fetch('/api/friends/reorder', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ order }),
});
if (res.ok) loadFriends();
}
// wir brauchen den vollen Ordnernamen für Bild-URLs; da wir ihn nicht separat liefern,
@@ -190,56 +200,6 @@ document.getElementById('logoutBtn').addEventListener('click', async () => {
window.location.href = '/';
});
// --- Sichern & Wiederherstellen ---
function showTransferMsg(text, isError) {
const box = document.getElementById('transferMsg');
box.innerHTML = isError ? `<p class="error-msg">${text}</p>` : `<p class="saved-msg2 show">${text}</p>`;
}
document.getElementById('bookExportBtn').addEventListener('click', () => {
window.location.href = '/api/book/export';
});
document.getElementById('bookImportBtn').addEventListener('click', async () => {
const fileInput = document.getElementById('bookImportFile');
const file = fileInput.files[0];
if (!file) { showTransferMsg('Bitte zuerst eine ZIP-Datei auswählen.', true); return; }
const replaceAll = document.getElementById('bookImportReplace').checked;
if (replaceAll && !confirm('Wirklich ALLE bestehenden Freunde vor dem Import löschen?')) return;
const fd = new FormData();
fd.append('file', file);
fd.append('replaceAll', replaceAll ? 'true' : 'false');
showTransferMsg('Importiere...', false);
const res = await fetch('/api/book/import', { method: 'POST', body: fd });
const data = await res.json();
if (res.ok) {
showTransferMsg(`${data.imported.length} Freund(e) importiert: ${data.imported.join(', ') || ''}`, false);
fileInput.value = '';
loadFriends();
} else {
showTransferMsg(data.error || 'Import fehlgeschlagen.', true);
}
});
document.getElementById('friendImportBtn').addEventListener('click', async () => {
const fileInput = document.getElementById('friendImportFile');
const file = fileInput.files[0];
if (!file) { showTransferMsg('Bitte zuerst eine ZIP-Datei auswählen.', true); return; }
const fd = new FormData();
fd.append('file', file);
showTransferMsg('Importiere...', false);
const res = await fetch('/api/friends/import', { method: 'POST', body: fd });
const data = await res.json();
if (res.ok) {
showTransferMsg(`"${data.friend.name}" wurde importiert.`, false);
fileInput.value = '';
loadFriends();
} else {
showTransferMsg(data.error || 'Import fehlgeschlagen.', true);
}
});
checkSession();
loadSettings();
loadFriends();