Files
Link-Log/frontend/static/new-entry.js
T
2026-08-27 17:33:43 +02:00

204 lines
6.0 KiB
JavaScript

// 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;
}
});
})();