// /app/static/js/modal-pagination.js document.addEventListener('DOMContentLoaded', () => { const modal = document.getElementById('imageModal'); const modalPrev = document.getElementById('modalPrev'); const modalNext = document.getElementById('modalNext'); 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; let isDragging = false; let startX, startY, scrollLeft, scrollTop; let dragStartX = 0; let dragStartY = 0; let dragMoved = false; const hasNextPageInput = document.getElementById('hasNextPage'); const nextPageUrlInput = document.getElementById('nextPageUrl'); 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} `; 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") { const video = document.createElement("video"); video.src = img.dataset.full; video.controls = true; video.autoplay = true; video.style.maxWidth = "100%"; video.style.maxHeight = "100%"; modalImageWrapper.appendChild(video); } else { const image = document.createElement("img"); image.src = img.dataset.full; image.alt = "Full View"; image.setAttribute("draggable", "false"); image.id = "modalImage"; modalImageWrapper.appendChild(image); } currentIndex = index; resetZoom(); // NEW: load tags for this image into the modal editor setEditorImageId(imageId); loadTags(imageId); } function openModal(index) { modal.classList.add('active'); updateImage(index); history.replaceState({ modalIndex: index }, '', window.location.href); } function closeModal() { modal.classList.remove('active'); modalImageWrapper.innerHTML = ''; currentIndex = -1; resetZoom(); // clear tag UI setEditorImageId(''); renderTags([]); history.replaceState({}, '', window.location.pathname + window.location.search); } function showNext() { if (currentIndex < images.length - 1) { updateImage(currentIndex + 1); } else if (hasNextPageInput?.value === "true") { fetch(nextPageUrlInput.value) .then(res => res.text()) .then(html => { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const newGallery = doc.querySelector('.gallery-grid'); const newImages = doc.querySelectorAll('.img-clickable'); if (newGallery && newImages.length > 0) { const gallery = document.querySelector('.gallery-grid'); gallery.innerHTML = newGallery.innerHTML; images = Array.from(document.querySelectorAll('.img-clickable')); images.forEach((img, i) => { img.addEventListener('click', () => openModal(i)); }); hasNextPageInput.value = doc.getElementById('hasNextPage')?.value || "false"; nextPageUrlInput.value = doc.getElementById('nextPageUrl')?.value || ""; hasPrevPageInput.value = doc.getElementById('hasPrevPage')?.value || "false"; prevPageUrlInput.value = doc.getElementById('prevPageUrl')?.value || ""; history.pushState({}, '', nextPageUrlInput.value); openModal(0); } }); } } function showPrev() { if (currentIndex > 0) { updateImage(currentIndex - 1); } else if (hasPrevPageInput?.value === "true") { fetch(prevPageUrlInput.value) .then(res => res.text()) .then(html => { const parser = new DOMParser(); const doc = parser.parseFromString(html, 'text/html'); const newGallery = doc.querySelector('.gallery-grid'); const newImages = doc.querySelectorAll('.img-clickable'); if (newGallery && newImages.length > 0) { const gallery = document.querySelector('.gallery-grid'); gallery.innerHTML = newGallery.innerHTML; images = Array.from(document.querySelectorAll('.img-clickable')); images.forEach((img, i) => { img.addEventListener('click', () => openModal(i)); }); hasNextPageInput.value = doc.getElementById('hasNextPage')?.value || "false"; nextPageUrlInput.value = doc.getElementById('nextPageUrl')?.value || ""; hasPrevPageInput.value = doc.getElementById('hasPrevPage')?.value || "false"; prevPageUrlInput.value = doc.getElementById('prevPageUrl')?.value || ""; history.pushState({}, '', prevPageUrlInput.value); openModal(images.length - 1); } }); } } function toggleZoom() { const isImage = modalImageWrapper.querySelector('img') !== null; if (!isImage) return; isZoomed = !isZoomed; modalImageWrapper.classList.toggle('zoomed', isZoomed); modalImageWrapper.style.cursor = isZoomed ? 'grab' : ''; if (!isZoomed) { modalImageWrapper.scrollLeft = 0; modalImageWrapper.scrollTop = 0; } } function resetZoom() { isZoomed = false; modalImageWrapper.classList.remove('zoomed'); modalImageWrapper.scrollLeft = 0; modalImageWrapper.scrollTop = 0; modalImageWrapper.style.cursor = ''; } modalImageWrapper.addEventListener('mousedown', (e) => { const isImage = modalImageWrapper.querySelector('img') !== null; if (!isZoomed || !isImage) return; isDragging = true; dragMoved = false; modalImageWrapper.style.cursor = 'grabbing'; startX = e.pageX - modalImageWrapper.offsetLeft; startY = e.pageY - modalImageWrapper.offsetTop; scrollLeft = modalImageWrapper.scrollLeft; scrollTop = modalImageWrapper.scrollTop; dragStartX = e.pageX; dragStartY = e.pageY; }); modalImageWrapper.addEventListener('mouseleave', () => { isDragging = false; const isImage = modalImageWrapper.querySelector('img') !== null; if (isZoomed && isImage) modalImageWrapper.style.cursor = 'grab'; }); modalImageWrapper.addEventListener('mouseup', () => { isDragging = false; const isImage = modalImageWrapper.querySelector('img') !== null; if (isZoomed && isImage) modalImageWrapper.style.cursor = 'grab'; }); modalImageWrapper.addEventListener('mousemove', (e) => { const isImage = modalImageWrapper.querySelector('img') !== null; if (!isDragging || !isZoomed || !isImage) return; e.preventDefault(); dragMoved = true; const x = e.pageX - modalImageWrapper.offsetLeft; const y = e.pageY - modalImageWrapper.offsetTop; const walkX = x - startX; const walkY = y - startY; modalImageWrapper.scrollLeft = scrollLeft - walkX; modalImageWrapper.scrollTop = scrollTop - walkY; }); modalImageWrapper.addEventListener('click', (e) => { if (dragMoved) { dragMoved = false; return; } toggleZoom(); }); modalClose?.addEventListener('click', closeModal); modalPrev?.addEventListener('click', showPrev); modalNext?.addEventListener('click', showNext); modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); }); document.addEventListener('keydown', (e) => { if (!modal.classList.contains('active')) return; if (e.key === 'ArrowRight') showNext(); if (e.key === 'ArrowLeft') showPrev(); if (e.key === 'Escape') closeModal(); }); images.forEach((img, index) => { img.addEventListener('click', () => openModal(index)); }); window.addEventListener('popstate', () => { if (modal.classList.contains('active')) { closeModal(); } else { location.reload(); } }); });