feat: Add folder upload and enhance dropzone UI on invite page

Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
2026-01-20 22:23:37 -07:00
parent e163e4dd45
commit a3881b8e03

View File

@@ -58,14 +58,29 @@
<div id="dropHint" class="mx-auto -mt-6 h-12 w-12 opacity-70 hidden md:block"> <div id="dropHint" class="mx-auto -mt-6 h-12 w-12 opacity-70 hidden md:block">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 16V8m0 0l-3 3m3-3 3 3M4 16a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4v-1a1 1 0 1 0-2 0v1a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2v-1a1 1 0 1 0-2 0v1z"/></svg> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 16V8m0 0l-3 3m3-3 3 3M4 16a4 4 0 0 0 4 4h8a4 4 0 0 0 4-4v-1a1 1 0 1 0-2 0v1a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2v-1a1 1 0 1 0-2 0v1z"/></svg>
</div> </div>
<p class="mt-3 font-medium hidden md:block">Drop files here</p> <p class="mt-3 font-medium hidden md:block">Drop multiple files or folders here</p>
<p class="mb-2 text-sm text-gray-600 dark:text-gray-400 hidden md:block">...or</p> <p class="mb-5 text-sm text-gray-600 dark:text-gray-400 hidden md:block">...or</p>
<div class="mt-3 relative inline-block"> <!-- Mobile-safe choose controls -->
<label class="rounded-2xl bg-black text-white dark:bg-white dark:text-black px-5 py-3 hover:opacity-90 cursor-pointer select-none" aria-label="Choose files"> <div class="flex flex-col sm:flex-row items-center justify-center gap-y-4 sm:gap-y-0 sm:gap-x-4">
<div class="relative inline-block">
<label class="rounded-2xl bg-black text-white dark:bg-white dark:text-black px-5 py-3 hover:opacity-90 cursor-pointer select-none transition-colors" aria-label="Choose files">
Choose files Choose files
<input id="fileInput" type="file" multiple class="absolute inset-0 opacity-0 cursor-pointer" /> <input id="fileInput"
type="file"
multiple
class="absolute inset-0 opacity-0 cursor-pointer" />
</label> </label>
</div> </div>
<div class="relative inline-block">
<label class="rounded-2xl bg-black text-white dark:bg-white dark:text-black px-5 py-3 hover:opacity-90 cursor-pointer select-none transition-colors" aria-label="Choose a folder">
Choose a folder
<input id="folderInput"
type="file"
webkitdirectory
class="absolute inset-0 opacity-0 cursor-pointer" />
</label>
</div>
</div>
<div class="mt-4 text-sm text-gray-500 dark:text-gray-400"> <div class="mt-4 text-sm text-gray-500 dark:text-gray-400">
Files will be uploaded to the selected album for this invite. Files will be uploaded to the selected album for this invite.
</div> </div>
@@ -116,6 +131,7 @@
document.getElementById('liStatus').textContent = j.active ? 'Active' : ('Inactive' + (j.inactiveReason ? (' ('+j.inactiveReason+')') : '')); document.getElementById('liStatus').textContent = j.active ? 'Active' : ('Inactive' + (j.inactiveReason ? (' ('+j.inactiveReason+')') : ''));
const dz = document.getElementById('dropzone'); const dz = document.getElementById('dropzone');
const fi = document.getElementById('fileInput'); const fi = document.getElementById('fileInput');
const foi = document.getElementById('folderInput');
const itemsEl = document.getElementById('items'); const itemsEl = document.getElementById('items');
const pwGate = document.getElementById('pwGate'); const pwGate = document.getElementById('pwGate');
if (j.passwordRequired && !j.authorized) { if (j.passwordRequired && !j.authorized) {
@@ -123,6 +139,7 @@
pwGate.classList.remove('hidden'); pwGate.classList.remove('hidden');
dz.classList.add('opacity-50'); dz.classList.add('opacity-50');
if (fi) fi.disabled = true; if (fi) fi.disabled = true;
if (foi) foi.disabled = true;
itemsEl.innerHTML = '<div class="text-sm text-gray-500">Enter the password above to enable uploads.</div>'; itemsEl.innerHTML = '<div class="text-sm text-gray-500">Enter the password above to enable uploads.</div>';
// Wire unlock button // Wire unlock button
const pwInput = document.getElementById('pwInput'); const pwInput = document.getElementById('pwInput');
@@ -139,6 +156,7 @@
pwGate.classList.add('hidden'); pwGate.classList.add('hidden');
dz.classList.remove('opacity-50'); dz.classList.remove('opacity-50');
if (fi) fi.disabled = false; if (fi) fi.disabled = false;
if (foi) foi.disabled = false;
itemsEl.innerHTML = ''; itemsEl.innerHTML = '';
try { showBanner('Password accepted. You can upload now.', 'ok'); } catch {} try { showBanner('Password accepted. You can upload now.', 'ok'); } catch {}
} catch (e) { } catch (e) {
@@ -153,6 +171,7 @@
// Disable dropzone // Disable dropzone
dz.classList.add('opacity-50'); dz.classList.add('opacity-50');
fi.disabled = true; fi.disabled = true;
if (foi) foi.disabled = true;
itemsEl.innerHTML = `<div class="text-sm text-gray-500">This link is not active${j.inactiveReason?` (${j.inactiveReason})`:''}.</div>`; itemsEl.innerHTML = `<div class="text-sm text-gray-500">This link is not active${j.inactiveReason?` (${j.inactiveReason})`:''}.</div>`;
} }
} catch {} } catch {}