Complete authentication and profile features

This commit is contained in:
Olaf
2026-08-24 14:50:05 +02:00
parent 1c827956c4
commit 01702fcadf
17 changed files with 227 additions and 40 deletions
+47 -21
View File
@@ -4,6 +4,26 @@ const userFilter = document.getElementById('user-filter');
const cookieName = 'linklog-feed-preferences';
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('; ')
@@ -25,7 +45,16 @@ function writePreferences(pref) {
document.cookie = `${cookieName}=${value}; path=/; max-age=31536000`;
}
function renderFeed(items) {
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) {
@@ -37,20 +66,6 @@ function renderFeed(items) {
const article = document.createElement('article');
article.className = 'link-item';
const header = document.createElement('div');
header.className = 'link-header';
const avatar = document.createElement('div');
avatar.className = 'avatar';
avatar.textContent = (item.user?.username || 'U').slice(0, 1).toUpperCase();
const userName = document.createElement('div');
userName.className = 'user-name';
userName.textContent = item.user?.username || 'unknown';
header.appendChild(avatar);
header.appendChild(userName);
const title = document.createElement('h2');
const link = document.createElement('a');
link.href = item.url;
@@ -67,7 +82,17 @@ function renderFeed(items) {
meta.className = 'meta';
meta.textContent = item.created_at || 'updated recently';
article.appendChild(header);
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);
@@ -94,7 +119,7 @@ async function loadFeed() {
items = [...items].reverse();
}
renderFeed(items);
renderFeed(items, !routeUser);
}
function syncPreferences() {
@@ -108,12 +133,13 @@ function syncPreferences() {
loadFeed();
});
userFilter.addEventListener('input', (event) => {
const next = { ...readPreferences(), user: event.target.value.trim() };
userFilter.addEventListener('change', (event) => {
const selectedUser = event.target.value.trim();
const next = { ...readPreferences(), user: selectedUser };
writePreferences(next);
loadFeed();
window.location.assign(selectedUser ? `/${encodeURIComponent(selectedUser)}/` : '/');
});
}
syncPreferences();
loadFeed();
loadUsers().then(loadFeed).catch(() => loadFeed());