character-fandom tag association system
Link character tags to fandom tags via fandom_id FK with auto-apply behavior. Adding character:Saber (Fate) auto-creates fandom:Fate and applies it to the image. Renaming a fandom cascades to all linked characters. Includes set-fandom endpoint for retroactive association and fandom selector dropdown in the tag editor UI. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+121
-1
@@ -11,7 +11,95 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const nameInput = document.getElementById('editTagName');
|
||||
const kindSelect = document.getElementById('editTagKind');
|
||||
|
||||
// Fandom UI elements
|
||||
const fandomGroup = document.getElementById('fandomGroup');
|
||||
const fandomInput = document.getElementById('editTagFandom');
|
||||
const fandomIdInput = document.getElementById('editTagFandomId');
|
||||
const fandomAutocomplete = document.getElementById('fandomAutocomplete');
|
||||
|
||||
let currentTagCard = null;
|
||||
let fandomDebounce = null;
|
||||
|
||||
// Show/hide fandom selector based on kind
|
||||
function updateFandomVisibility() {
|
||||
if (fandomGroup) {
|
||||
fandomGroup.style.display = kindSelect.value === 'character' ? '' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
kindSelect?.addEventListener('change', updateFandomVisibility);
|
||||
|
||||
// Fandom autocomplete search
|
||||
async function searchFandoms(query) {
|
||||
try {
|
||||
const params = new URLSearchParams({ q: query, kind: 'fandom', limit: 10 });
|
||||
const res = await fetch(`/api/tags/search?${params}`);
|
||||
const data = await res.json();
|
||||
return data.tags || [];
|
||||
} catch (err) {
|
||||
console.error('Failed to search fandoms:', err);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
function renderFandomAutocomplete(tags) {
|
||||
if (!fandomAutocomplete) return;
|
||||
|
||||
if (tags.length === 0) {
|
||||
fandomAutocomplete.innerHTML = '<div class="tag-autocomplete-empty">No matching fandoms</div>';
|
||||
fandomAutocomplete.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
fandomAutocomplete.innerHTML = tags.map((t, i) => {
|
||||
const displayName = t.name.includes(':') ? t.name.split(':')[1] : t.name;
|
||||
return `<div class="tag-autocomplete-item" data-index="${i}" data-id="${t.id}" data-name="${displayName}">
|
||||
<span class="tag-autocomplete-name">🎭 ${displayName}</span>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
fandomAutocomplete.style.display = 'block';
|
||||
}
|
||||
|
||||
// Fandom input events
|
||||
fandomInput?.addEventListener('input', () => {
|
||||
clearTimeout(fandomDebounce);
|
||||
fandomDebounce = setTimeout(async () => {
|
||||
const query = fandomInput.value.trim();
|
||||
if (query.length < 1) {
|
||||
fandomAutocomplete.style.display = 'none';
|
||||
fandomIdInput.value = '';
|
||||
return;
|
||||
}
|
||||
const tags = await searchFandoms(query);
|
||||
renderFandomAutocomplete(tags);
|
||||
// Clear the hidden ID since user is typing a new value
|
||||
fandomIdInput.value = '';
|
||||
}, 200);
|
||||
});
|
||||
|
||||
fandomInput?.addEventListener('focus', async () => {
|
||||
const query = fandomInput.value.trim();
|
||||
const tags = await searchFandoms(query || '');
|
||||
renderFandomAutocomplete(tags);
|
||||
});
|
||||
|
||||
fandomInput?.addEventListener('blur', () => {
|
||||
// Delay to allow click on autocomplete item
|
||||
setTimeout(() => {
|
||||
if (fandomAutocomplete) fandomAutocomplete.style.display = 'none';
|
||||
}, 200);
|
||||
});
|
||||
|
||||
// Click on fandom autocomplete item
|
||||
fandomAutocomplete?.addEventListener('mousedown', (e) => {
|
||||
e.preventDefault();
|
||||
const item = e.target.closest('.tag-autocomplete-item');
|
||||
if (!item) return;
|
||||
fandomInput.value = item.dataset.name;
|
||||
fandomIdInput.value = item.dataset.id;
|
||||
fandomAutocomplete.style.display = 'none';
|
||||
});
|
||||
|
||||
// Open modal when edit button clicked (using event delegation for infinite scroll)
|
||||
document.addEventListener('click', async (e) => {
|
||||
@@ -33,6 +121,17 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
tagIdInput.value = data.tag.id;
|
||||
nameInput.value = data.tag.display_name;
|
||||
kindSelect.value = data.tag.kind || 'user';
|
||||
|
||||
// Set fandom fields
|
||||
if (data.tag.fandom_id && data.tag.fandom_name) {
|
||||
fandomInput.value = data.tag.fandom_name;
|
||||
fandomIdInput.value = data.tag.fandom_id;
|
||||
} else {
|
||||
fandomInput.value = '';
|
||||
fandomIdInput.value = '';
|
||||
}
|
||||
|
||||
updateFandomVisibility();
|
||||
modal.classList.add('active');
|
||||
}
|
||||
} catch (err) {
|
||||
@@ -44,6 +143,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
function closeModal() {
|
||||
modal.classList.remove('active');
|
||||
currentTagCard = null;
|
||||
if (fandomAutocomplete) fandomAutocomplete.style.display = 'none';
|
||||
}
|
||||
|
||||
closeBtn?.addEventListener('click', closeModal);
|
||||
@@ -76,8 +176,28 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const data = await res.json();
|
||||
|
||||
if (data.ok) {
|
||||
// After successful update, handle fandom association for character tags
|
||||
if (kindSelect.value === 'character') {
|
||||
const fandomName = fandomInput.value.trim();
|
||||
const fandomId = fandomIdInput.value;
|
||||
const updateTagId = data.tag.id;
|
||||
|
||||
// Set fandom via the dedicated endpoint
|
||||
const fandomFormData = new FormData();
|
||||
if (fandomId) {
|
||||
fandomFormData.append('fandom_id', fandomId);
|
||||
} else if (fandomName) {
|
||||
fandomFormData.append('fandom_name', fandomName);
|
||||
}
|
||||
// If both empty, this clears the fandom
|
||||
|
||||
await fetch(`/api/tag/${updateTagId}/set-fandom`, {
|
||||
method: 'POST',
|
||||
body: fandomFormData
|
||||
});
|
||||
}
|
||||
|
||||
if (data.merged) {
|
||||
// Tag was merged
|
||||
alert(`Tags merged successfully! ${data.merged_count} images were updated.`);
|
||||
}
|
||||
// Reload page to show updated tag
|
||||
|
||||
Reference in New Issue
Block a user