diff --git a/app/static/js/view-modal.js b/app/static/js/view-modal.js index f293371..5a4ceff 100644 --- a/app/static/js/view-modal.js +++ b/app/static/js/view-modal.js @@ -838,11 +838,67 @@ document.addEventListener('DOMContentLoaded', () => { if (j.ok) { await loadTags(imageId); showFeedback(tagActionFeedback, 'Tag removed'); + showBlocklistOffer(name); return true; } return false; } + // After a pill removal, offer an inline "Blocklist this everywhere" button. + // Fades after 8s; click escalates to blocklist (same endpoint + Celery sweep + // as the suggestion chip's ⊘ button). Covers the case where you remove a + // WD14 composition descriptor (e.g. '1girl', 'breasts') from this image and + // realize you also don't want it anywhere else. + function showBlocklistOffer(name) { + const offer = document.getElementById('blocklistOffer'); + if (!offer) return; + const label = offer.querySelector('.blocklist-offer-label'); + const btn = offer.querySelector('.blocklist-offer-btn'); + const dismiss = offer.querySelector('.blocklist-offer-dismiss'); + if (!label || !btn) return; + + label.textContent = `Removed '${name}' — also blocklist?`; + offer.dataset.name = name; + offer.classList.add('visible'); + offer.setAttribute('aria-hidden', 'false'); + btn.disabled = false; + + clearTimeout(offer._fadeTimeout); + offer._fadeTimeout = setTimeout(() => hideBlocklistOffer(), 8000); + + btn.onclick = async () => { + btn.disabled = true; + try { + const r = await fetch('/api/suggestions/blocklist', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ name }), + }); + const j = await r.json(); + if (j.ok) { + label.textContent = `Blocklisted '${name}'. Cleanup task enqueued.`; + setTimeout(() => hideBlocklistOffer(), 2500); + } else { + label.textContent = `Couldn't blocklist: ${j.error || 'error'}`; + btn.disabled = false; + } + } catch (e) { + label.textContent = `Couldn't blocklist: ${e}`; + btn.disabled = false; + } + }; + + if (dismiss) dismiss.onclick = () => hideBlocklistOffer(); + } + + function hideBlocklistOffer() { + const offer = document.getElementById('blocklistOffer'); + if (!offer) return; + offer.classList.remove('visible'); + offer.setAttribute('aria-hidden', 'true'); + clearTimeout(offer._fadeTimeout); + } + // --------------------------- // Fandom picker helpers // --------------------------- @@ -1155,6 +1211,7 @@ document.addEventListener('DOMContentLoaded', () => { setEditorImageId(imageId); if (tagInput) tagInput.value = ''; clearFandomPicker(); + hideBlocklistOffer(); loadTags(imageId); loadSeriesInfo(imageId); // Prefill the reserved autocomplete area with top tags so it's never empty. @@ -1175,6 +1232,7 @@ document.addEventListener('DOMContentLoaded', () => { function closeModal() { clearFandomPicker(); + hideBlocklistOffer(); // Get the image ID before clearing so we can refresh its tags const imageId = getEditorImageId(); diff --git a/app/static/style.css b/app/static/style.css index a93a5d9..c33cf8a 100644 --- a/app/static/style.css +++ b/app/static/style.css @@ -1020,6 +1020,44 @@ header { border-color: rgba(255, 180, 0, 0.6); } +/* Inline blocklist offer shown after a pill-× removal. Fades after 8s or + on image/modal change; click escalates to blocklist + sweep. */ +.blocklist-offer { + display: none; + align-items: center; + gap: 0.5rem; + flex-wrap: wrap; + margin-top: 0.5rem; + padding: 0.4rem 0.6rem; + background: rgba(255, 180, 0, 0.08); + border: 1px solid rgba(255, 180, 0, 0.3); + border-radius: 6px; + font-size: 0.85rem; + color: var(--text); +} +.blocklist-offer.visible { + display: flex; +} +.blocklist-offer-label { + flex: 1; + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + color: #ffb400; +} +.blocklist-offer-btn { + color: #ffb400; + border-color: rgba(255, 180, 0, 0.5); +} +.blocklist-offer-btn:hover { + background: rgba(255, 180, 0, 0.15); +} +.blocklist-offer-dismiss { + color: var(--text-muted); + padding: 0 0.4rem; +} + /* Auto-accepted suggestion block (top of suggestions section) */ .suggestions-auto-accepted { margin-bottom: 0.75rem; diff --git a/app/templates/_gallery_modal.html b/app/templates/_gallery_modal.html index 4815a75..b7a3f0b 100644 --- a/app/templates/_gallery_modal.html +++ b/app/templates/_gallery_modal.html @@ -73,6 +73,11 @@
+