// Copyright © 2026 Olaf Kolkman // SPDX-License-Identifier: GPL-3.0-or-later (() => { const entryForm = document.getElementById('entry-form'); const authRequired = document.getElementById('auth-required'); const urlInput = document.getElementById('url-input'); const titleInput = document.getElementById('title-input'); const commentInput = document.getElementById('comment-input'); const newTagsInput = document.getElementById('new-tags-input'); const existingTagsEl = document.getElementById('existing-tags'); const mastodonEnabledCheckbox = document.getElementById('mastodon-enabled'); const scrapeButton = document.getElementById('scrape-button'); const scrapeStatus = document.getElementById('scrape-status'); const submitButton = document.getElementById('submit-button'); const submitStatus = document.getElementById('submit-status'); const token = localStorage.getItem('linklogAccessToken'); let availableTags = []; let selectedTags = new Set(); let currentUser = null; // Utility function to add status messages function setStatus(statusEl, message, isError = false) { statusEl.textContent = message; statusEl.className = `status ${isError ? 'error' : 'success'}`; statusEl.classList.remove('hidden'); if (!isError) { setTimeout(() => statusEl.classList.add('hidden'), 4000); } } // Check authentication if (!token) { authRequired.classList.remove('hidden'); entryForm.classList.add('hidden'); return; } // Verify token is still valid fetch('/api/auth/me', { headers: { Authorization: `Bearer ${token}` } }) .then((response) => { if (!response.ok) throw new Error('Not authenticated'); return response.json(); }) .then((user) => { currentUser = user; entryForm.classList.remove('hidden'); loadTags(); }) .catch(() => { localStorage.removeItem('linklogAccessToken'); authRequired.classList.remove('hidden'); entryForm.classList.add('hidden'); }); // Load available tags async function loadTags() { try { const response = await fetch('/api/tags'); if (!response.ok) throw new Error('Could not load tags'); availableTags = await response.json(); renderTags(); } catch (error) { console.error('Error loading tags:', error); } } // Render tag checkboxes function renderTags() { existingTagsEl.innerHTML = ''; availableTags.forEach((tag) => { const label = document.createElement('label'); label.className = 'tag-checkbox'; const checkbox = document.createElement('input'); checkbox.type = 'checkbox'; checkbox.value = tag; checkbox.checked = selectedTags.has(tag); checkbox.addEventListener('change', () => { if (checkbox.checked) { selectedTags.add(tag); } else { selectedTags.delete(tag); } }); label.appendChild(checkbox); label.append(` ${tag}`); existingTagsEl.appendChild(label); }); } // Scrape URL for title scrapeButton.addEventListener('click', async (e) => { e.preventDefault(); const url = urlInput.value.trim(); if (!url) { setStatus(scrapeStatus, 'Please enter a URL', true); return; } scrapeButton.disabled = true; setStatus(scrapeStatus, 'Scraping...', false); try { const response = await fetch(`/api/scrape?url=${encodeURIComponent(url)}`, { headers: { Authorization: `Bearer ${token}` }, }); if (!response.ok) { if (response.status === 401) { localStorage.removeItem('linklogAccessToken'); location.reload(); return; } throw new Error(`HTTP ${response.status}`); } const data = await response.json(); if (data.title) { titleInput.value = data.title; setStatus(scrapeStatus, 'Title loaded!', false); } else { setStatus(scrapeStatus, 'No title found', true); } } catch (error) { console.error('Scrape error:', error); setStatus(scrapeStatus, `Error: ${error.message}`, true); } finally { scrapeButton.disabled = false; } }); // Handle form submission entryForm.addEventListener('submit', async (e) => { e.preventDefault(); const url = urlInput.value.trim(); const title = titleInput.value.trim(); const comment = commentInput.value.trim(); const newTags = newTagsInput.value.trim(); if (!url || !title) { setStatus(submitStatus, 'URL and title are required', true); return; } // Combine selected tags and new tags const tags = Array.from(selectedTags); if (newTags) { const newTagsList = newTags .split(',') .map((t) => t.trim()) .filter((t) => t); tags.push(...newTagsList); } submitButton.disabled = true; setStatus(submitStatus, 'Saving...', false); try { const response = await fetch('/api/links', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}`, }, body: JSON.stringify({ title, url, comment, tags, }), }); if (!response.ok) { if (response.status === 401) { localStorage.removeItem('linklogAccessToken'); location.reload(); return; } const error = await response.json().catch(() => ({})); throw new Error(error.detail || `HTTP ${response.status}`); } const data = await response.json(); setStatus(submitStatus, `Link saved to LinkLog${data.duplicate ? ' (updated)' : ''}!`, false); // Redirect to user page after 1 second if (currentUser) { setTimeout(() => { window.location.href = `/${encodeURIComponent(currentUser.username)}/`; }, 1000); } } catch (error) { console.error('Submit error:', error); setStatus(submitStatus, `Error: ${error.message}`, true); } finally { submitButton.disabled = false; } }); })();