(() => { 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'); // Note: prompt is now a label, but accessible via class 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 = 'Upload (Select file first)'; } 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']; // Check extensions as fallback const ext = file.name.split('.').pop().toLowerCase(); const validExts = ['mp4', 'webm']; if (!validTypes.includes(file.type) && !validExts.includes(ext)) { 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'; // Hide input so it doesn't intercept clicks on preview/remove button fileInput.style.display = 'none'; filePreview.style.display = 'flex'; statusDiv.textContent = ''; statusDiv.className = 'upload-status'; // Video Preview const itemPreview = filePreview.querySelector('.item-preview') || document.createElement('div'); itemPreview.className = 'item-preview'; itemPreview.style.marginRight = '15px'; // Clear previous const existingVid = filePreview.querySelector('video'); if (existingVid) existingVid.remove(); const vid = document.createElement('video'); vid.src = URL.createObjectURL(file); vid.controls = true; // User might want to scrub to check if it's the right video vid.autoplay = true; vid.muted = true; vid.loop = true; // Styles handled by CSS now for "Big" preview filePreview.prepend(vid); updateSubmitButton(); }; const preventDefaults = (e) => { e.preventDefault(); e.stopPropagation(); }; // Attach drag events only to dropZone now (Input is hidden) ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, preventDefaults, false); }); ['dragenter', 'dragover'].forEach(eventName => { dropZone.addEventListener(eventName, () => dropZone.classList.add('dragover'), false); }); ['dragleave', 'drop'].forEach(eventName => { dropZone.addEventListener(eventName, () => dropZone.classList.remove('dragover'), false); }); dropZone.addEventListener('drop', (e) => { const dt = e.dataTransfer; const files = dt.files; handleFile(files[0]); }); // Native change listener on hidden input 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'; fileInput.style.display = 'block'; // Restore input visibility filePreview.style.display = 'none'; // Clear preview video const vid = filePreview.querySelector('video'); if (vid) vid.remove(); updateSubmitButton(); }); 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(); }; let currentFocus = -1; const addActive = (x) => { if (!x) return false; removeActive(x); if (currentFocus >= x.length) currentFocus = 0; if (currentFocus < 0) currentFocus = (x.length - 1); x[currentFocus].classList.add("active"); // Scroll to view x[currentFocus].scrollIntoView({ block: 'nearest' }); }; const removeActive = (x) => { for (let i = 0; i < x.length; i++) { x[i].classList.remove("active"); } }; tagInput.addEventListener('keydown', (e) => { const x = tagSuggestions.getElementsByClassName("tag-suggestion"); if (e.key === 'ArrowDown') { currentFocus++; addActive(x); } else if (e.key === 'ArrowUp') { currentFocus--; addActive(x); } else if (e.key === 'Enter') { e.preventDefault(); if (currentFocus > -1) { if (x) x[currentFocus].click(); } else { addTag(tagInput.value); } } else if (e.key === 'Escape') { tagSuggestions.classList.remove('show'); currentFocus = -1; } }); let debounceTimer; tagInput.addEventListener('input', () => { clearTimeout(debounceTimer); const query = tagInput.value.trim(); currentFocus = -1; // Reset focus on new input 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 += '