183 lines
5.7 KiB
JavaScript
183 lines
5.7 KiB
JavaScript
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 = '<div class="link-item"><p>No links yet.</p></div>';
|
|
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 = `
|
|
<label>Title <input name="title" value=""></label>
|
|
<label>URL <input name="url" type="url" value=""></label>
|
|
<label>Comment <textarea name="comment"></textarea></label>
|
|
<button type="submit">Save changes</button>
|
|
`;
|
|
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());
|