(() => { const form = document.getElementById('upload-form'); if (!form) return; const fileInput = document.getElementById('file-input'); const dropZone = document.getElementById('drop-zone'); const filePreview = document.getElementById('file-preview'); const dropZonePrompt = dropZone.querySelector('.drop-zone-prompt'); const fileName = document.getElementById('file-name'); const fileSize = document.getElementById('file-size'); const removeFile = document.getElementById('remove-file'); const tagInput = document.getElementById('tag-input'); const tagsList = document.getElementById('tags-list'); const tagsHidden = document.getElementById('tags-hidden'); const tagCount = document.getElementById('tag-count'); const tagSuggestions = document.getElementById('tag-suggestions'); const submitBtn = document.getElementById('submit-btn'); const progressContainer = document.getElementById('upload-progress'); const progressFill = document.getElementById('progress-fill'); const progressText = document.getElementById('progress-text'); const statusDiv = document.getElementById('upload-status'); let tags = []; let selectedFile = null; const formatSize = (bytes) => { const units = ['B', 'KB', 'MB', 'GB']; let i = 0; while (bytes >= 1024 && i < units.length - 1) { bytes /= 1024; i++; } return bytes.toFixed(2) + ' ' + units[i]; }; const updateSubmitButton = () => { const rating = document.querySelector('input[name="rating"]:checked'); const hasFile = selectedFile !== null; const hasRating = rating !== null; const hasTags = tags.length >= 3; submitBtn.disabled = !(hasFile && hasRating && hasTags); if (!hasTags) { submitBtn.querySelector('.btn-text').textContent = (3 - tags.length) + ' more tag' + (3 - tags.length !== 1 ? 's' : '') + ' required'; } else if (!hasFile) { submitBtn.querySelector('.btn-text').textContent = 'Select a file'; } else if (!hasRating) { submitBtn.querySelector('.btn-text').textContent = 'Select SFW or NSFW'; } else { submitBtn.querySelector('.btn-text').textContent = 'Upload'; } tagCount.textContent = '(' + tags.length + '/3 minimum)'; tagCount.classList.toggle('valid', tags.length >= 3); }; const handleFile = (file) => { if (!file) return; const validTypes = ['video/mp4', 'video/webm']; if (!validTypes.includes(file.type)) { statusDiv.textContent = 'Only mp4 and webm files are allowed'; statusDiv.className = 'upload-status error'; return; } selectedFile = file; fileName.textContent = file.name; fileSize.textContent = formatSize(file.size); dropZonePrompt.style.display = 'none'; filePreview.style.display = 'flex'; statusDiv.textContent = ''; statusDiv.className = 'upload-status'; updateSubmitButton(); }; fileInput.addEventListener('change', (e) => handleFile(e.target.files[0])); removeFile.addEventListener('click', (e) => { e.preventDefault(); e.stopPropagation(); selectedFile = null; fileInput.value = ''; dropZonePrompt.style.display = 'block'; filePreview.style.display = 'none'; updateSubmitButton(); }); dropZone.addEventListener('dragover', (e) => { e.preventDefault(); dropZone.classList.add('dragover'); }); dropZone.addEventListener('dragleave', () => { dropZone.classList.remove('dragover'); }); dropZone.addEventListener('drop', (e) => { e.preventDefault(); dropZone.classList.remove('dragover'); handleFile(e.dataTransfer.files[0]); }); const addTag = (tagName) => { tagName = tagName.trim().toLowerCase(); if (!tagName || tags.includes(tagName)) return; if (tagName === 'sfw' || tagName === 'nsfw') return; tags.push(tagName); const chip = document.createElement('span'); chip.className = 'tag-chip'; chip.innerHTML = tagName + ''; chip.querySelector('button').addEventListener('click', () => { tags = tags.filter(t => t !== tagName); chip.remove(); updateSubmitButton(); }); tagsList.appendChild(chip); tagsHidden.value = tags.join(','); tagInput.value = ''; tagSuggestions.innerHTML = ''; tagSuggestions.classList.remove('show'); updateSubmitButton(); }; tagInput.addEventListener('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); addTag(tagInput.value); } }); let debounceTimer; tagInput.addEventListener('input', () => { clearTimeout(debounceTimer); const query = tagInput.value.trim(); if (query.length < 2) { tagSuggestions.classList.remove('show'); return; } debounceTimer = setTimeout(async () => { try { const res = await fetch('/api/v2/admin/tags/suggest?q=' + encodeURIComponent(query)); const data = await res.json(); if (data.success && data.suggestions && data.suggestions.length > 0) { const filtered = data.suggestions.filter(s => !tags.includes(s.tag.toLowerCase())); let html = ''; for (let i = 0; i < Math.min(8, filtered.length); i++) { html += '
' + filtered[i].tag + '
'; } tagSuggestions.innerHTML = html; tagSuggestions.classList.add('show'); tagSuggestions.querySelectorAll('.tag-suggestion').forEach(el => { el.addEventListener('click', () => addTag(el.textContent)); }); } else { tagSuggestions.classList.remove('show'); } } catch (err) { console.error(err); } }, 200); }); document.addEventListener('click', (e) => { if (!tagInput.contains(e.target) && !tagSuggestions.contains(e.target)) { tagSuggestions.classList.remove('show'); } }); document.querySelectorAll('input[name="rating"]').forEach(radio => { radio.addEventListener('change', updateSubmitButton); }); form.addEventListener('submit', async (e) => { e.preventDefault(); if (!selectedFile || tags.length < 3) return; const rating = document.querySelector('input[name="rating"]:checked'); if (!rating) return; submitBtn.disabled = true; submitBtn.querySelector('.btn-text').style.display = 'none'; submitBtn.querySelector('.btn-loading').style.display = 'inline'; progressContainer.style.display = 'flex'; statusDiv.textContent = ''; statusDiv.className = 'upload-status'; const formData = new FormData(); formData.append('file', selectedFile); formData.append('rating', rating.value); formData.append('tags', tags.join(',')); try { const xhr = new XMLHttpRequest(); xhr.upload.addEventListener('progress', (e) => { if (e.lengthComputable) { const percent = Math.round((e.loaded / e.total) * 100); progressFill.style.width = percent + '%'; progressText.textContent = percent + '%'; } }); xhr.onload = () => { const res = JSON.parse(xhr.responseText); if (res.success) { statusDiv.innerHTML = '✓ ' + res.msg; statusDiv.className = 'upload-status success'; form.reset(); tags = []; tagsList.innerHTML = ''; selectedFile = null; dropZonePrompt.style.display = 'block'; filePreview.style.display = 'none'; } else { statusDiv.textContent = '✕ ' + res.msg; statusDiv.className = 'upload-status error'; if (res.repost) { statusDiv.innerHTML += ' View existing'; } } submitBtn.querySelector('.btn-text').style.display = 'inline'; submitBtn.querySelector('.btn-loading').style.display = 'none'; progressContainer.style.display = 'none'; progressFill.style.width = '0%'; updateSubmitButton(); }; xhr.onerror = () => { statusDiv.textContent = '✕ Upload failed. Please try again.'; statusDiv.className = 'upload-status error'; submitBtn.querySelector('.btn-text').style.display = 'inline'; submitBtn.querySelector('.btn-loading').style.display = 'none'; progressContainer.style.display = 'none'; updateSubmitButton(); }; xhr.open('POST', '/api/v2/upload'); xhr.send(formData); } catch (err) { console.error(err); statusDiv.textContent = '✕ Upload failed: ' + err.message; statusDiv.className = 'upload-status error'; submitBtn.querySelector('.btn-text').style.display = 'inline'; submitBtn.querySelector('.btn-loading').style.display = 'none'; updateSubmitButton(); } }); updateSubmitButton(); })();