// Console-based authentication window.login = async (password) => { if (!password) { console.error('Usage: login("your-password")'); return; } try { const res = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ password }) }); const data = await res.json(); if (res.ok) { console.log(data.firstTime ? 'Password set! Reloading...' : 'Logged in! Reloading...'); setTimeout(() => location.reload(), 500); } else { console.error(data.error || 'Login failed'); } } catch (err) { console.error('Login error:', err); } }; window.logout = async () => { try { await fetch('/api/auth/logout', { method: 'POST' }); console.log('Logged out! Reloading...'); setTimeout(() => location.reload(), 500); } catch (err) { console.error('Logout error:', err); } }; // Modal functions function showAddModal() { document.getElementById('add-modal').classList.add('active'); } function hideAddModal() { document.getElementById('add-modal').classList.remove('active'); } function showEditModal() { document.getElementById('edit-modal').classList.add('active'); } function hideEditModal() { document.getElementById('edit-modal').classList.remove('active'); } // Tab switching document.addEventListener('DOMContentLoaded', () => { const tabs = document.querySelectorAll('.modal-tabs .tab'); tabs.forEach(tab => { tab.addEventListener('click', () => { const tabId = tab.dataset.tab; // Update tab buttons tabs.forEach(t => t.classList.remove('active')); tab.classList.add('active'); // Update tab content document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active')); document.getElementById('tab-' + tabId).classList.add('active'); }); }); // URL preview on input const urlInput = document.querySelector('#link-form input[name="url"]'); if (urlInput) { let debounceTimer; urlInput.addEventListener('input', (e) => { clearTimeout(debounceTimer); debounceTimer = setTimeout(() => fetchPreview(e.target.value), 500); }); } }); // Fetch URL preview async function fetchPreview(url) { const preview = document.getElementById('link-preview'); if (!url) { preview.classList.remove('active'); preview.innerHTML = ''; return; } try { const res = await fetch('/api/preview', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url }) }); if (!res.ok) { const data = await res.json(); preview.innerHTML = `
${data.error || 'Failed to fetch preview'}
`; preview.classList.add('active'); return; } const data = await res.json(); preview.dataset.preview = JSON.stringify(data); let html = ''; if (data.imageUrl) { html += `Preview`; } if (data.title) { html += `
${escapeHtml(data.title)}
`; } if (data.description) { html += `
${escapeHtml(data.description)}
`; } if (data.isEmbed) { html += `
${escapeHtml(data.provider.toUpperCase())} VIDEO
`; } preview.innerHTML = html || '
No preview available
'; preview.classList.add('active'); // Auto-fill title if empty const titleInput = document.querySelector('#link-form input[name="title"]'); if (titleInput && !titleInput.value && data.title) { titleInput.value = data.title; } } catch (err) { console.error('Preview error:', err); preview.innerHTML = '
Failed to fetch preview
'; preview.classList.add('active'); } } // Submit link form async function submitLink(event) { event.preventDefault(); const form = event.target; const url = form.url.value; const tags = form.tags.value ? form.tags.value.split(',').map(t => t.trim()).filter(Boolean) : []; // Get preview data const preview = document.getElementById('link-preview'); const previewData = preview.dataset.preview ? JSON.parse(preview.dataset.preview) : {}; // Use form values, falling back to preview data const title = form.title.value || previewData.title || null; const description = form.description.value || previewData.description || null; try { const body = { url, title, description, tags, imageUrl: previewData.imageUrl || null, }; if (previewData.isEmbed) { body.provider = previewData.provider; body.videoId = previewData.videoId; body.embedHtml = previewData.embedHtml; } const res = await fetch('/api/items/from-link', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body) }); if (res.ok) { location.reload(); } else { const data = await res.json(); alert(data.error || 'Failed to add item'); } } catch (err) { console.error('Submit error:', err); alert('Failed to add item'); } return false; } // Submit upload form async function submitUpload(event) { event.preventDefault(); const form = event.target; const formData = new FormData(form); try { const res = await fetch('/api/items/upload', { method: 'POST', body: formData }); if (res.ok) { location.reload(); } else { const data = await res.json(); alert(data.error || 'Failed to upload'); } } catch (err) { console.error('Upload error:', err); alert('Failed to upload'); } return false; } // Submit quote form async function submitQuote(event) { event.preventDefault(); const form = event.target; const text = form.text.value; const source = form.source.value || null; const sourceUrl = form.sourceUrl.value || null; const tags = form.tags.value ? form.tags.value.split(',').map(t => t.trim()).filter(Boolean) : []; try { const res = await fetch('/api/items/quote', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ text, source, sourceUrl, tags }) }); if (res.ok) { location.reload(); } else { const data = await res.json(); alert(data.error || 'Failed to add quote'); } } catch (err) { console.error('Submit error:', err); alert('Failed to add quote'); } return false; } // Edit item function editItem(id) { showEditModal(); } // Submit edit form async function submitEdit(event) { event.preventDefault(); const form = event.target; const id = form.id.value; const title = form.title.value || null; const description = form.description.value || null; const linkUrl = form.linkUrl.value || null; const tags = form.tags.value ? form.tags.value.split(',').map(t => t.trim()).filter(Boolean) : []; try { const res = await fetch(`/api/items/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title, description, linkUrl, tags }) }); if (res.ok) { location.reload(); } else { const data = await res.json(); alert(data.error || 'Failed to update'); } } catch (err) { console.error('Update error:', err); alert('Failed to update'); } return false; } // Refresh metadata async function refreshMetadata(id) { try { // Fetch current item data const itemRes = await fetch(`/api/items/${id}`); if (!itemRes.ok) { alert('Failed to fetch item'); return; } const item = await itemRes.json(); if (!item.linkUrl) { alert('Item has no link URL'); return; } // Fetch fresh metadata const previewRes = await fetch('/api/preview', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: item.linkUrl }) }); if (!previewRes.ok) { const data = await previewRes.json(); alert(data.error || 'Failed to fetch metadata'); return; } const preview = await previewRes.json(); // Check if user has made manual edits const titleChanged = item.title && preview.title && item.title !== preview.title; const descChanged = item.description && preview.description && item.description !== preview.description; const imageChanged = item.thumbnailSourceUrl && preview.imageUrl && item.thumbnailSourceUrl !== preview.imageUrl; if (titleChanged || descChanged || imageChanged) { let msg = 'This will overwrite your changes:\n'; if (titleChanged) msg += `\nTitle: "${item.title}" → "${preview.title}"`; if (descChanged) msg += `\nDescription will be replaced`; if (imageChanged) msg += `\nImage will be replaced`; msg += '\n\nContinue?'; if (!confirm(msg)) return; } // Proceed with refresh const res = await fetch(`/api/items/${id}/refresh`, { method: 'POST' }); if (res.ok) { location.reload(); } else { const data = await res.json(); alert(data.error || 'Failed to refresh'); } } catch (err) { console.error('Refresh error:', err); alert('Failed to refresh'); } } // Replace media async function submitReplaceMedia(event, id) { event.preventDefault(); const form = event.target; const formData = new FormData(form); try { const res = await fetch(`/api/items/${id}/media`, { method: 'POST', body: formData }); if (res.ok) { location.reload(); } else { const data = await res.json(); alert(data.error || 'Failed to replace media'); } } catch (err) { console.error('Replace media error:', err); alert('Failed to replace media'); } return false; } // Delete item async function deleteItem(id) { if (!confirm('Delete this item?')) return; try { const res = await fetch(`/api/items/${id}`, { method: 'DELETE' }); if (res.ok) { location.href = '/'; } else { const data = await res.json(); alert(data.error || 'Failed to delete'); } } catch (err) { console.error('Delete error:', err); alert('Failed to delete'); } } // Play video (initial click to start) function playVideo(overlay) { const container = overlay.parentElement; const video = container.querySelector('video'); if (video) { video.play(); overlay.remove(); // Show controls on hover video.addEventListener('mouseenter', () => video.controls = true); video.addEventListener('mouseleave', () => video.controls = false); } } // Utility function escapeHtml(str) { const div = document.createElement('div'); div.textContent = str; return div.innerHTML; }