const favoriteIcons = {
star: '',
link: '',
play: '',
note: '',
book: '',
code: '',
mail: '',
calendar: '',
image: '',
tool: ''
};
const iconLabels = {
star: 'Stern',
link: 'Link',
play: 'Video',
note: 'Notiz',
book: 'Buch',
code: 'Code',
mail: 'Mail',
calendar: 'Kalender',
image: 'Bild',
tool: 'Tool'
};
const loginButton = document.querySelector('#favoritesLoginButton');
const logoutButton = document.querySelector('#favoritesLogoutButton');
const loginPanel = document.querySelector('#favoritesLoginPanel');
const panel = document.querySelector('#favoritesPanel');
const message = document.querySelector('#favoritesMessage');
const userBox = document.querySelector('#favoritesUser');
const editor = document.querySelector('#favoriteEditor');
const preview = document.querySelector('#favoritePreview');
const addButton = document.querySelector('#addFavoriteButton');
const saveButton = document.querySelector('#saveFavoritesButton');
const reloadButton = document.querySelector('#reloadFavoritesButton');
let favorites = [];
function escapeHtml(value) {
return String(value || '')
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
function createDefaultFavorite() {
return {
id: crypto.randomUUID(),
name: 'Neuer Link',
url: 'https://',
group: 'Favoriten',
description: '',
color: '#67e8f9',
icon: 'star',
logoUrl: '',
order: favorites.length
};
}
function iconOptions(selected) {
return Object.keys(favoriteIcons).map((icon) => (
``
)).join('');
}
function syncFromInputs() {
editor.querySelectorAll('.favorite-edit-card').forEach((row) => {
const favorite = favorites[Number(row.dataset.index)];
if (!favorite) return;
row.querySelectorAll('[data-field]').forEach((input) => {
favorite[input.dataset.field] = input.value;
});
});
}
function moveFavorite(index, direction) {
const next = index + direction;
if (next < 0 || next >= favorites.length) return;
syncFromInputs();
[favorites[index], favorites[next]] = [favorites[next], favorites[index]];
favorites = favorites.map((favorite, order) => ({ ...favorite, order }));
render();
}
function removeFavorite(index) {
syncFromInputs();
favorites.splice(index, 1);
favorites = favorites.map((favorite, order) => ({ ...favorite, order }));
render();
}
function renderPreviewCard(favorite) {
const color = /^#[0-9a-fA-F]{6}$/.test(favorite.color) ? favorite.color : '#67e8f9';
const iconSvg = favoriteIcons[favorite.icon] || favoriteIcons.star;
let host = '';
try {
host = new URL(favorite.url).hostname;
} catch {
host = 'ungueltige-url';
}
return `
${favorite.logoUrl
? `
})
`
: `
`}
${escapeHtml(favorite.group || 'Favorit')}
${escapeHtml(favorite.name)}
${escapeHtml(favorite.description || 'Eigener Weblink')}
${escapeHtml(host)}
`;
}
function renderPreview() {
preview.innerHTML = favorites.map(renderPreviewCard).join('') || '
Die Vorschau erscheint hier.
';
}
function render() {
editor.innerHTML = favorites.map((favorite, index) => `
${escapeHtml(favorite.name || 'Neuer Link')}
`).join('') || 'Noch keine Favoriten angelegt.
';
renderPreview();
editor.querySelectorAll('[data-field]').forEach((input) => {
input.addEventListener('input', () => {
syncFromInputs();
renderPreview();
});
});
editor.querySelectorAll('[data-action]').forEach((button) => {
button.addEventListener('click', () => {
const index = Number(button.closest('.favorite-edit-card').dataset.index);
if (button.dataset.action === 'up') moveFavorite(index, -1);
if (button.dataset.action === 'down') moveFavorite(index, 1);
if (button.dataset.action === 'remove') removeFavorite(index);
});
});
}
async function loadFavorites() {
const response = await fetch('/api/favorites', {
cache: 'no-store',
headers: MischLabsAuth.authHeader()
});
const payload = await response.json().catch(() => ({}));
if (!response.ok) throw new Error(payload.error || `HTTP ${response.status}`);
favorites = Array.isArray(payload.favorites) ? payload.favorites : [];
userBox.textContent = `Angemeldet als ${payload.user?.name || MischLabsAuth.displayName()}`;
render();
}
async function saveFavorites() {
syncFromInputs();
const response = await fetch('/api/favorites', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
...MischLabsAuth.authHeader()
},
body: JSON.stringify({ favorites })
});
const payload = await response.json().catch(() => ({}));
if (!response.ok) throw new Error(payload.error || `HTTP ${response.status}`);
favorites = payload.favorites || [];
render();
userBox.textContent = 'Favoriten gespeichert.';
}
function updateAuthUi() {
const authenticated = MischLabsAuth.isAuthenticated();
loginButton.hidden = authenticated;
logoutButton.hidden = !authenticated;
loginPanel.hidden = authenticated;
panel.hidden = !authenticated;
}
async function boot() {
try {
await MischLabsAuth.handleCallback();
} catch (error) {
message.textContent = error.message;
}
updateAuthUi();
if (MischLabsAuth.isAuthenticated()) {
await loadFavorites();
}
}
loginButton.addEventListener('click', () => MischLabsAuth.login());
logoutButton.addEventListener('click', () => MischLabsAuth.logout());
addButton.addEventListener('click', () => {
syncFromInputs();
favorites.push(createDefaultFavorite());
render();
});
saveButton.addEventListener('click', () => {
saveFavorites().catch((error) => {
userBox.textContent = `Speichern fehlgeschlagen: ${error.message}`;
});
});
reloadButton.addEventListener('click', () => {
loadFavorites().catch((error) => {
userBox.textContent = `Laden fehlgeschlagen: ${error.message}`;
});
});
boot().catch((error) => {
message.textContent = `Favoritenseite konnte nicht starten: ${error.message}`;
});