switched to tagging and added views to support it, in progress for other tagging functions.

This commit is contained in:
Bryan Van Deusen
2025-08-14 21:36:27 -04:00
parent e9793ba38c
commit 3ff34ec9c2
12 changed files with 943 additions and 349 deletions
+97
View File
@@ -7,6 +7,12 @@ document.addEventListener('DOMContentLoaded', () => {
const modalClose = document.getElementById('modalClose');
const modalImageWrapper = document.querySelector('.modal-image-wrapper');
// NEW: tag editor elements
const tagEditor = document.getElementById('modalTagEditor');
const tagList = document.getElementById('modalTagList');
const tagForm = document.getElementById('modalTagForm');
const tagInput = tagForm ? tagForm.querySelector('input[name="name"]') : null;
let images = Array.from(document.querySelectorAll('.img-clickable'));
let currentIndex = -1;
let isZoomed = false;
@@ -21,9 +27,93 @@ document.addEventListener('DOMContentLoaded', () => {
const hasPrevPageInput = document.getElementById('hasPrevPage');
const prevPageUrlInput = document.getElementById('prevPageUrl');
// ---------------------------
// Tag editor helpers
// ---------------------------
function setEditorImageId(id) {
if (tagEditor) tagEditor.dataset.imageId = id || '';
}
function getEditorImageId() {
return tagEditor ? tagEditor.dataset.imageId : '';
}
function renderTags(tags) {
if (!tagList) return;
tagList.innerHTML = '';
(tags || []).forEach(t => {
const chip = document.createElement('span');
chip.className = 'tag-chip';
const label = (t.kind === 'artist')
? `🎨 ${t.name.split(':', 1)[0] === 'artist' ? t.name.split(':', 2)[1] : t.name}`
: (t.kind === 'archive')
? `🗜️ ${t.name.split(':', 1)[0] === 'archive' ? t.name.split(':', 2)[1] : t.name}`
: `#${t.name}`;
chip.innerHTML = `${label} <button class="x" data-name="${t.name}" title="Remove">×</button>`;
tagList.appendChild(chip);
});
}
async function loadTags(imageId) {
if (!imageId) { renderTags([]); return; }
try {
const r = await fetch(`/image/${imageId}/tags`);
const j = await r.json();
if (j.ok) renderTags(j.tags);
} catch {
// ignore
}
}
async function addTag(imageId, name) {
const fd = new FormData();
fd.append('name', name);
const r = await fetch(`/image/${imageId}/tags/add`, { method: 'POST', body: fd });
const j = await r.json();
if (j.ok) {
await loadTags(imageId);
return true;
}
return false;
}
async function removeTag(imageId, name) {
const fd = new FormData();
fd.append('name', name);
const r = await fetch(`/image/${imageId}/tags/remove`, { method: 'POST', body: fd });
const j = await r.json();
if (j.ok) {
await loadTags(imageId);
return true;
}
return false;
}
if (tagForm) {
tagForm.addEventListener('submit', async (e) => {
e.preventDefault();
const id = getEditorImageId();
const name = (tagInput.value || '').trim();
if (!id || !name) return;
await addTag(id, name);
tagInput.value = '';
});
}
if (tagList) {
tagList.addEventListener('click', async (e) => {
const btn = e.target.closest('button.x');
if (!btn) return;
const id = getEditorImageId();
const name = btn.dataset.name;
if (!id || !name) return;
await removeTag(id, name);
});
}
// ---------------------------
// Existing viewer logic
// ---------------------------
function updateImage(index) {
const img = images[index];
const fileType = img.dataset.type || "image";
const imageId = img.dataset.id || "";
// clear wrapper
modalImageWrapper.innerHTML = "";
if (fileType === "video") {
@@ -45,6 +135,10 @@ document.addEventListener('DOMContentLoaded', () => {
currentIndex = index;
resetZoom();
// NEW: load tags for this image into the modal editor
setEditorImageId(imageId);
loadTags(imageId);
}
function openModal(index) {
@@ -58,6 +152,9 @@ document.addEventListener('DOMContentLoaded', () => {
modalImageWrapper.innerHTML = '';
currentIndex = -1;
resetZoom();
// clear tag UI
setEditorImageId('');
renderTags([]);
history.replaceState({}, '', window.location.pathname + window.location.search);
}