diff --git a/public/s/css/upload.css b/public/s/css/upload.css index f1ef18c..9a5da1f 100644 --- a/public/s/css/upload.css +++ b/public/s/css/upload.css @@ -251,6 +251,7 @@ padding: 0.5rem; display: flex; flex-wrap: wrap; + position: relative; gap: 0.5rem; } @@ -307,13 +308,14 @@ .tag-suggestions { /* (styles for dropdown remain similar, maybe cleaner shadow) */ position: absolute; - top: 100%; + top: auto; + bottom: 100%; left: 0; right: 0; background: #1e1e1e; border: 1px solid rgba(255, 255, 255, 0.1); - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5); - border-radius: 0 0 4px 4px; + box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.5); + border-radius: 4px 4px 0 0; max-height: 200px; overflow-y: auto; display: none; @@ -329,7 +331,8 @@ cursor: pointer; } -.tag-suggestion:hover { +.tag-suggestion:hover, +.tag-suggestion.active { background: rgba(255, 255, 255, 0.1); } diff --git a/public/s/js/upload.js b/public/s/js/upload.js index f8aabb3..df7f245 100644 --- a/public/s/js/upload.js +++ b/public/s/js/upload.js @@ -24,42 +24,7 @@ let tags = []; let selectedFile = null; - // Flash Message Logic - const showFlash = (msg, type = 'success') => { - const existing = document.querySelector('.flash-message'); - if (existing) existing.remove(); - const flash = document.createElement('div'); - flash.className = `flash-message ${type}`; - flash.textContent = msg; - - Object.assign(flash.style, { - position: 'fixed', - top: '20px', - left: '50%', - transform: 'translateX(-50%)', - padding: '15px 30px', - borderRadius: '5px', - color: '#fff', - fontWeight: '600', - zIndex: '9999', - boxShadow: '0 4px 12px rgba(0,0,0,0.3)', - background: type === 'success' ? '#51cf66' : '#ff6b6b', - opacity: '0', - transition: 'opacity 0.3s' - }); - - document.body.appendChild(flash); - - // Fade in - requestAnimationFrame(() => flash.style.opacity = '1'); - - // Remove after 5s - setTimeout(() => { - flash.style.opacity = '0'; - setTimeout(() => flash.remove(), 300); - }, 5000); - }; const formatSize = (bytes) => { const units = ['B', 'KB', 'MB', 'GB']; @@ -207,10 +172,42 @@ 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) => { - if (e.key === 'Enter') { + 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(); - addTag(tagInput.value); + if (currentFocus > -1) { + if (x) x[currentFocus].click(); + } else { + addTag(tagInput.value); + } + } else if (e.key === 'Escape') { + tagSuggestions.classList.remove('show'); + currentFocus = -1; } }); @@ -218,6 +215,7 @@ 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'); @@ -239,7 +237,10 @@ tagSuggestions.classList.add('show'); tagSuggestions.querySelectorAll('.tag-suggestion').forEach(el => { - el.addEventListener('click', () => addTag(el.textContent)); + el.addEventListener('click', () => { + addTag(el.textContent); + tagInput.focus(); + }); }); } else { tagSuggestions.classList.remove('show'); @@ -296,8 +297,7 @@ if (res.success) { statusDiv.innerHTML = '✓ ' + res.msg; statusDiv.className = 'upload-status success'; - // Flash Message - showFlash(res.msg, 'success'); + form.reset(); tags = []; @@ -313,7 +313,7 @@ if (res.repost) { statusDiv.innerHTML += ' View existing'; } - showFlash('Upload failed: ' + res.msg, 'error'); + } submitBtn.querySelector('.btn-text').style.display = 'inline'; @@ -326,7 +326,7 @@ xhr.onerror = () => { statusDiv.textContent = '✕ Upload failed. Please try again.'; statusDiv.className = 'upload-status error'; - showFlash('Upload failed network error', 'error'); + submitBtn.querySelector('.btn-text').style.display = 'inline'; submitBtn.querySelector('.btn-loading').style.display = 'none'; progressContainer.style.display = 'none'; @@ -340,7 +340,7 @@ console.error(err); statusDiv.textContent = '✕ Upload failed: ' + err.message; statusDiv.className = 'upload-status error'; - showFlash('Upload failed: ' + err.message, 'error'); + submitBtn.querySelector('.btn-text').style.display = 'inline'; submitBtn.querySelector('.btn-loading').style.display = 'none'; updateSubmitButton();