const feedEl = document.getElementById('feed'); const sortSelect = document.getElementById('sort-select'); const userFilter = document.getElementById('user-filter'); const cookieName = 'linklog-feed-preferences'; const accessToken = localStorage.getItem('linklogAccessToken'); function createAvatar(user) { const avatar = document.createElement('div'); avatar.className = 'avatar'; const initial = (user?.username || 'U').slice(0, 1).toUpperCase(); avatar.textContent = initial; if (user?.avatar_url) { const image = document.createElement('img'); image.src = user.avatar_url; image.alt = `${user.username || 'User'} avatar`; image.addEventListener('error', () => { image.remove(); avatar.textContent = initial; }); avatar.textContent = ''; avatar.appendChild(image); } return avatar; } function readPreferences() { const cookie = document.cookie .split('; ') .find((row) => row.startsWith(`${cookieName}=`)); if (!cookie) { return { sort: 'newest', user: '' }; } try { return JSON.parse(decodeURIComponent(cookie.split('=')[1])); } catch (error) { return { sort: 'newest', user: '' }; } } function writePreferences(pref) { const value = encodeURIComponent(JSON.stringify(pref)); document.cookie = `${cookieName}=${value}; path=/; max-age=31536000`; } async function loadUsers() { const response = await fetch('/api/public/users'); if (!response.ok) throw new Error('Could not load users'); const users = await response.json(); const selectedUser = readPreferences().user; userFilter.replaceChildren(new Option('All users', ''), ...users.map((username) => new Option(username, username))); userFilter.value = users.includes(selectedUser) ? selectedUser : ''; } function renderFeed(items, showIdentity = true) { feedEl.innerHTML = ''; if (!items.length) { feedEl.innerHTML = ''; return; } items.forEach((item) => { const article = document.createElement('article'); article.className = 'link-item'; const title = document.createElement('h2'); const link = document.createElement('a'); link.href = item.url; link.target = '_blank'; link.rel = 'noopener noreferrer'; link.textContent = item.title || item.url; title.appendChild(link); const comment = document.createElement('div'); comment.className = 'comment'; comment.textContent = item.comment || 'No comment provided'; const meta = document.createElement('div'); meta.className = 'meta'; meta.textContent = item.created_at || 'updated recently'; if (item.is_owner) { const editButton = document.createElement('button'); editButton.type = 'button'; editButton.className = 'edit-button'; editButton.textContent = 'Edit'; editButton.addEventListener('click', () => showEditForm(article, item)); article.appendChild(editButton); } if (showIdentity) { const header = document.createElement('div'); header.className = 'link-header'; header.appendChild(createAvatar(item.user)); const userName = document.createElement('div'); userName.className = 'user-name'; userName.textContent = item.user?.username || 'unknown'; header.appendChild(userName); article.appendChild(header); } article.appendChild(title); article.appendChild(comment); article.appendChild(meta); feedEl.appendChild(article); }); } function showEditForm(article, item) { if (article.querySelector('.edit-form')) return; const form = document.createElement('form'); form.className = 'edit-form'; form.innerHTML = ` `; form.elements.title.value = item.title || ''; form.elements.url.value = item.url || ''; form.elements.comment.value = item.comment || ''; form.addEventListener('submit', async (event) => { event.preventDefault(); const response = await fetch(`/api/links/${encodeURIComponent(item.id)}`, { method: 'PUT', headers: {'Content-Type': 'application/json', Authorization: `Bearer ${accessToken}`}, body: JSON.stringify(Object.fromEntries(new FormData(form))), }); if (response.ok) loadFeed(); }); article.appendChild(form); } async function loadFeed() { const routeUser = document.body.dataset.userFilter; const endpoint = routeUser ? `/api/public/feed/${encodeURIComponent(routeUser)}` : '/api/public/feed'; const response = await fetch(endpoint, { headers: accessToken ? {Authorization: `Bearer ${accessToken}`} : {}, }); const data = await response.json(); let items = data || []; const pref = readPreferences(); if (pref.user && !routeUser) { items = items.filter((item) => (item.user?.username || '').toLowerCase() === pref.user.toLowerCase()); } if (pref.sort === 'oldest') { items = [...items].reverse(); } renderFeed(items, !routeUser); } function syncPreferences() { const pref = readPreferences(); sortSelect.value = pref.sort; userFilter.value = pref.user; sortSelect.addEventListener('change', (event) => { const next = { ...readPreferences(), sort: event.target.value }; writePreferences(next); loadFeed(); }); userFilter.addEventListener('change', (event) => { const selectedUser = event.target.value.trim(); const next = { ...readPreferences(), user: selectedUser }; writePreferences(next); window.location.assign(selectedUser ? `/${encodeURIComponent(selectedUser)}/` : '/'); }); } syncPreferences(); loadUsers().then(loadFeed).catch(() => loadFeed());