New Entry functionality
This commit is contained in:
@@ -0,0 +1,203 @@
|
||||
// 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;
|
||||
}
|
||||
});
|
||||
})();
|
||||
Reference in New Issue
Block a user