220 lines
6.7 KiB
JavaScript
220 lines
6.7 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 mastodonPublishing = document.getElementById('mastodon-publishing');
|
|
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');
|
|
Promise.all([loadTags(), loadMastodonPublishing()]);
|
|
})
|
|
.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);
|
|
}
|
|
}
|
|
|
|
async function loadMastodonPublishing() {
|
|
try {
|
|
const response = await fetch('/api/user/plugins/mastodon', {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
if (!response.ok) return;
|
|
const config = await response.json();
|
|
mastodonEnabledCheckbox.checked = Boolean(config.configured);
|
|
mastodonPublishing.classList.toggle('hidden', !config.configured);
|
|
} catch (error) {
|
|
console.error('Could not load Mastodon configuration:', 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,
|
|
post_to_mastodon: mastodonEnabledCheckbox.checked,
|
|
}),
|
|
});
|
|
|
|
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;
|
|
}
|
|
});
|
|
})();
|