263 lines
9.7 KiB
JavaScript
263 lines
9.7 KiB
JavaScript
(() => {
|
|
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 + '<button type="button">×</button>';
|
|
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 += '<div class="tag-suggestion">' + filtered[i].tag + '</div>';
|
|
}
|
|
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 += ' <a href="/' + res.repost + '">View existing</a>';
|
|
}
|
|
}
|
|
|
|
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();
|
|
})();
|