Label edit functionality added
Build LinkLog Development Image / development-image (push) Successful in 10s
Build LinkLog Development Image / development-image (push) Successful in 10s
This commit is contained in:
@@ -57,21 +57,85 @@ function renderLabels(labels) {
|
||||
adminLabelList.replaceChildren(...labels.map((label) => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'plugin-row';
|
||||
|
||||
const text = document.createElement('span');
|
||||
text.textContent = `${label.name}${label.creator ? ` (${label.creator})` : ' (default)'}`;
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.className = 'danger-button';
|
||||
button.textContent = 'Delete';
|
||||
button.addEventListener('click', async () => {
|
||||
|
||||
const actions = document.createElement('div');
|
||||
actions.style.display = 'flex';
|
||||
actions.style.gap = '8px';
|
||||
|
||||
const editButton = document.createElement('button');
|
||||
editButton.type = 'button';
|
||||
editButton.textContent = 'Edit';
|
||||
editButton.addEventListener('click', () => {
|
||||
showAdminInlineLabelEdit(row, label);
|
||||
});
|
||||
|
||||
const deleteButton = document.createElement('button');
|
||||
deleteButton.type = 'button';
|
||||
deleteButton.className = 'danger-button';
|
||||
deleteButton.textContent = 'Delete';
|
||||
deleteButton.addEventListener('click', async () => {
|
||||
if (!confirm(`Are you sure you want to delete "${label.name}"?`)) return;
|
||||
const response = await fetch(`/api/admin/labels/${label.id}`, {method: 'DELETE', headers: authHeaders()});
|
||||
if (response.ok) loadLabels();
|
||||
});
|
||||
row.append(text, button);
|
||||
|
||||
actions.append(editButton, deleteButton);
|
||||
row.append(text, actions);
|
||||
return row;
|
||||
}));
|
||||
}
|
||||
|
||||
function showAdminInlineLabelEdit(rowContainer, label) {
|
||||
rowContainer.replaceChildren();
|
||||
|
||||
const editForm = document.createElement('form');
|
||||
editForm.style.display = 'flex';
|
||||
editForm.style.gap = '8px';
|
||||
editForm.style.width = '100%';
|
||||
editForm.style.alignItems = 'center';
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
input.value = label.name;
|
||||
input.required = true;
|
||||
input.style.flex = '1';
|
||||
|
||||
const saveButton = document.createElement('button');
|
||||
saveButton.type = 'submit';
|
||||
saveButton.textContent = 'Save';
|
||||
|
||||
const cancelButton = document.createElement('button');
|
||||
cancelButton.type = 'button';
|
||||
cancelButton.textContent = 'Cancel';
|
||||
cancelButton.addEventListener('click', () => {
|
||||
loadLabels();
|
||||
});
|
||||
|
||||
editForm.append(input, saveButton, cancelButton);
|
||||
|
||||
editForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const newName = input.value.trim();
|
||||
if (!newName) return;
|
||||
const response = await fetch(`/api/admin/labels/${label.id}`, {
|
||||
method: 'PUT',
|
||||
headers: authHeaders(true),
|
||||
body: JSON.stringify({ name: newName }),
|
||||
});
|
||||
if (response.ok) {
|
||||
loadLabels();
|
||||
} else {
|
||||
alert(await responseError(response, 'Could not update label'));
|
||||
}
|
||||
});
|
||||
|
||||
rowContainer.appendChild(editForm);
|
||||
input.focus();
|
||||
}
|
||||
|
||||
async function loadLabels() {
|
||||
const response = await fetch('/api/admin/labels', {headers: authHeaders()});
|
||||
if (!response.ok) throw new Error('Could not load labels');
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
// Copyright © 2026 Olaf Kolkman
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
(() => {
|
||||
const labelAuthNotice = document.querySelector('#label-auth-notice');
|
||||
const labelControls = document.querySelector('#label-controls');
|
||||
const labelForm = document.querySelector('#label-form');
|
||||
const labelNameInput = document.querySelector('#label-name');
|
||||
const labelStatus = document.querySelector('#label-status');
|
||||
const labelList = document.querySelector('#label-list');
|
||||
const accessToken = localStorage.getItem('linklogAccessToken');
|
||||
let currentUserId = null;
|
||||
|
||||
function authHeaders(includeJson = false) {
|
||||
return {
|
||||
...(includeJson ? {'Content-Type': 'application/json'} : {}),
|
||||
...(accessToken ? {Authorization: `Bearer ${accessToken}`} : {}),
|
||||
};
|
||||
}
|
||||
|
||||
function setStatus(message, isError = false) {
|
||||
if (!labelStatus) return;
|
||||
labelStatus.textContent = message;
|
||||
labelStatus.style.color = isError ? '#b91c1c' : '#166534';
|
||||
}
|
||||
|
||||
async function responseError(response, fallback) {
|
||||
try {
|
||||
const result = await response.json();
|
||||
return result.detail || result.message || fallback;
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
function renderLabels(labels) {
|
||||
if (!labelList) return;
|
||||
if (!labels || labels.length === 0) {
|
||||
labelList.innerHTML = '<p>No labels found.</p>';
|
||||
return;
|
||||
}
|
||||
|
||||
labelList.replaceChildren(...labels.map((label) => {
|
||||
const row = document.createElement('div');
|
||||
row.className = 'plugin-row';
|
||||
|
||||
const isOwned = label.created_by === currentUserId;
|
||||
|
||||
const contentContainer = document.createElement('div');
|
||||
contentContainer.style.display = 'flex';
|
||||
contentContainer.style.alignItems = 'center';
|
||||
contentContainer.style.justifySpaceBetween = 'space-between';
|
||||
contentContainer.style.width = '100%';
|
||||
|
||||
const text = document.createElement('span');
|
||||
text.textContent = `${label.name}${isOwned ? '' : (label.creator ? ` (${label.creator})` : ' (default)')}`;
|
||||
|
||||
contentContainer.appendChild(text);
|
||||
|
||||
if (isOwned) {
|
||||
const actions = document.createElement('div');
|
||||
actions.style.display = 'flex';
|
||||
actions.style.gap = '8px';
|
||||
|
||||
const editButton = document.createElement('button');
|
||||
editButton.type = 'button';
|
||||
editButton.textContent = 'Edit';
|
||||
editButton.addEventListener('click', () => {
|
||||
showInlineEdit(row, label);
|
||||
});
|
||||
|
||||
const deleteButton = document.createElement('button');
|
||||
deleteButton.type = 'button';
|
||||
deleteButton.className = 'danger-button';
|
||||
deleteButton.textContent = 'Delete';
|
||||
deleteButton.addEventListener('click', async () => {
|
||||
if (!confirm(`Are you sure you want to delete "${label.name}"?`)) return;
|
||||
setStatus('');
|
||||
const response = await fetch(`/api/user/labels/${label.id}`, {
|
||||
method: 'DELETE',
|
||||
headers: authHeaders(),
|
||||
});
|
||||
if (response.ok) {
|
||||
setStatus(`Deleted label ${label.name}`);
|
||||
loadLabels();
|
||||
} else {
|
||||
setStatus(await responseError(response, 'Could not delete label'), true);
|
||||
}
|
||||
});
|
||||
|
||||
actions.append(editButton, deleteButton);
|
||||
contentContainer.appendChild(actions);
|
||||
}
|
||||
|
||||
row.appendChild(contentContainer);
|
||||
return row;
|
||||
}));
|
||||
}
|
||||
|
||||
function showInlineEdit(rowContainer, label) {
|
||||
rowContainer.replaceChildren();
|
||||
|
||||
const editForm = document.createElement('form');
|
||||
editForm.style.display = 'flex';
|
||||
editForm.style.gap = '8px';
|
||||
editForm.style.width = '100%';
|
||||
editForm.style.alignItems = 'center';
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.type = 'text';
|
||||
input.value = label.name;
|
||||
input.required = true;
|
||||
input.style.flex = '1';
|
||||
|
||||
const saveButton = document.createElement('button');
|
||||
saveButton.type = 'submit';
|
||||
saveButton.textContent = 'Save';
|
||||
|
||||
const cancelButton = document.createElement('button');
|
||||
cancelButton.type = 'button';
|
||||
cancelButton.textContent = 'Cancel';
|
||||
cancelButton.addEventListener('click', () => {
|
||||
loadLabels();
|
||||
});
|
||||
|
||||
editForm.append(input, saveButton, cancelButton);
|
||||
|
||||
editForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const newName = input.value.trim();
|
||||
if (!newName) return;
|
||||
setStatus('');
|
||||
const response = await fetch(`/api/user/labels/${label.id}`, {
|
||||
method: 'PUT',
|
||||
headers: authHeaders(true),
|
||||
body: JSON.stringify({ name: newName }),
|
||||
});
|
||||
if (response.ok) {
|
||||
setStatus(`Updated label to ${newName}`);
|
||||
loadLabels();
|
||||
} else {
|
||||
setStatus(await responseError(response, 'Could not update label'), true);
|
||||
}
|
||||
});
|
||||
|
||||
rowContainer.appendChild(editForm);
|
||||
input.focus();
|
||||
}
|
||||
|
||||
async function loadLabels() {
|
||||
try {
|
||||
const response = await fetch('/api/user/labels', { headers: authHeaders() });
|
||||
if (!response.ok) throw new Error('Could not load labels');
|
||||
const labels = await response.json();
|
||||
renderLabels(labels);
|
||||
} catch (error) {
|
||||
setStatus('Error loading labels', true);
|
||||
}
|
||||
}
|
||||
|
||||
async function init() {
|
||||
if (!accessToken) {
|
||||
if (labelAuthNotice) labelAuthNotice.classList.remove('hidden');
|
||||
if (labelControls) labelControls.classList.add('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
if (labelAuthNotice) labelAuthNotice.classList.add('hidden');
|
||||
if (labelControls) labelControls.classList.remove('hidden');
|
||||
|
||||
try {
|
||||
const meResponse = await fetch('/api/auth/me', { headers: authHeaders() });
|
||||
if (meResponse.ok) {
|
||||
const me = await meResponse.json();
|
||||
currentUserId = me.id;
|
||||
}
|
||||
} catch {
|
||||
// Proceed if me fails
|
||||
}
|
||||
|
||||
await loadLabels();
|
||||
|
||||
if (labelForm) {
|
||||
labelForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const name = labelNameInput.value.trim();
|
||||
if (!name) return;
|
||||
setStatus('');
|
||||
const response = await fetch('/api/user/labels', {
|
||||
method: 'POST',
|
||||
headers: authHeaders(true),
|
||||
body: JSON.stringify({ name }),
|
||||
});
|
||||
if (response.ok) {
|
||||
labelNameInput.value = '';
|
||||
setStatus(`Added label ${name}`);
|
||||
loadLabels();
|
||||
} else {
|
||||
setStatus(await responseError(response, 'Could not add label'), true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', init);
|
||||
if (document.readyState !== 'loading') {
|
||||
init();
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user