commit 568b6feb5b4cd538844c8b8b865bc0607f960185
parent 50b0789ab73ad286a70d56fb048812fda6520b4b
Author: Amit Dutta <mail@amit.is-a.dev>
Date: Tue, 21 Jul 2026 09:53:13 +0530
Remove custom dropdown for link expiry options
Removed custom dropdown functionality and related styles from the upload interface.
Diffstat:
| M | index.html | | | 205 | ++++++++++++++++++++++++++++++------------------------------------------------- |
1 file changed, 78 insertions(+), 127 deletions(-)
diff --git a/index.html b/index.html
@@ -4,12 +4,10 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Upload Hub</title>
-
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Lexend:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,1,0" rel="stylesheet" />
-
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
<script>
@@ -40,7 +38,6 @@
}
}
</script>
-
<style>
body { background-color: #FDFCFB; margin: 0; -webkit-font-smoothing: antialiased; }
@media (prefers-color-scheme: dark) { body { background-color: #1A1C19; } }
@@ -51,21 +48,7 @@
.hide { display: none !important; }
.tab-active { border-bottom: 2px solid #386A20; color: #386A20; }
@media (prefers-color-scheme: dark) { .tab-active { border-bottom-color: #9CD67D; color: #9CD67D; } }
- /* Custom dropdown */
- .dd { position: relative; }
- .dd-trigger { cursor: pointer; user-select: none; display: flex; align-items: center; justify-content: space-between; }
- .dd-trigger .dd-chevron { transition: transform 0.15s ease; }
- .dd.open .dd-trigger .dd-chevron { transform: rotate(180deg); }
- .dd-panel {
- position: absolute; top: calc(100% + 6px); left: 0; right: 0; z-index: 30;
- overflow: hidden; opacity: 0; transform: translateY(-6px) scale(0.98);
- pointer-events: none; transition: opacity 0.12s ease, transform 0.12s ease;
- }
- .dd.open .dd-panel { opacity: 1; transform: translateY(0) scale(1); pointer-events: auto; }
- .dd-option { cursor: pointer; }
- .dd-option.selected { color: #386A20; font-weight: 600; }
- @media (prefers-color-scheme: dark) { .dd-option.selected { color: #9CD67D; } }
-
+
/* Copy button success feedback */
.copy-btn .copy-icon { display: inline-block; transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1); }
.copy-btn.copied .copy-icon { transform: scale(1.25); }
@@ -73,9 +56,8 @@
</style>
</head>
<body class="bg-m3-background dark:bg-m3-backgroundDark text-m3-onSurface dark:text-m3-onSurfaceDark flex flex-col items-center min-h-screen p-4 font-sans">
-
+
<div id="ui-container" class="w-full max-w-[520px] mt-8">
-
<div class="mb-6 flex items-end justify-between">
<div>
<h1 class="text-2xl font-bold tracking-tight">Upload</h1>
@@ -91,28 +73,11 @@
<!-- ==================== UPLOAD TAB ==================== -->
<div id="upload-tab" class="tab-panel">
- <div class="flex flex-col gap-1.5 mb-4">
+ <div class="flex flex-col gap-1.5 mb-6">
<label for="custom-id" class="text-sm font-medium text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark">Custom ID (single file only, optional)</label>
<input type="text" id="custom-id" placeholder="e.g., source-code" class="px-4 py-3 bg-m3-surfaceContainerHighest dark:bg-m3-surfaceContainerHighestDark rounded-t-md border-b-2 border-m3-outline dark:border-m3-outlineDark placeholder-gray-500">
</div>
- <div class="flex flex-col gap-1.5 mb-6">
- <label class="text-sm font-medium text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark">Link expiry (optional)</label>
- <div id="expiry-dd" class="dd" data-value="">
- <div class="dd-trigger px-4 py-3 bg-m3-surfaceContainerHighest dark:bg-m3-surfaceContainerHighestDark rounded-t-md border-b-2 border-m3-outline dark:border-m3-outlineDark">
- <span class="dd-label">Never (permanent link)</span>
- <span class="material-symbols-outlined dd-chevron text-[20px]">expand_more</span>
- </div>
- <div class="dd-panel bg-m3-surfaceContainerHigh dark:bg-m3-surfaceContainerHighDark rounded-[12px] shadow-lg">
- <div class="dd-option px-4 py-3 hover:bg-m3-surfaceContainerHighest dark:hover:bg-m3-surfaceContainerHighestDark selected" data-value="">Never (permanent link)</div>
- <div class="dd-option px-4 py-3 hover:bg-m3-surfaceContainerHighest dark:hover:bg-m3-surfaceContainerHighestDark" data-value="60">1 hour</div>
- <div class="dd-option px-4 py-3 hover:bg-m3-surfaceContainerHighest dark:hover:bg-m3-surfaceContainerHighestDark" data-value="1440">1 day</div>
- <div class="dd-option px-4 py-3 hover:bg-m3-surfaceContainerHighest dark:hover:bg-m3-surfaceContainerHighestDark" data-value="10080">7 days</div>
- <div class="dd-option px-4 py-3 hover:bg-m3-surfaceContainerHighest dark:hover:bg-m3-surfaceContainerHighestDark" data-value="43200">30 days</div>
- </div>
- </div>
- </div>
-
<div id="dropzone" class="bg-m3-surfaceContainer dark:bg-m3-surfaceContainerDark hover:bg-m3-surfaceContainerHigh dark:hover:bg-m3-surfaceContainerHighDark rounded-[24px] p-10 flex flex-col items-center justify-center cursor-pointer mb-6 border border-transparent transition-colors">
<span class="material-symbols-outlined text-[48px] text-m3-primary dark:text-m3-primaryDark mb-4">upload</span>
<span class="text-base font-semibold">Click, drag, or paste (Ctrl+V) files</span>
@@ -161,29 +126,16 @@
<label for="url-input" class="text-sm font-medium text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark">File URL</label>
<input type="text" id="url-input" placeholder="https://example.com/file.png" class="px-4 py-3 bg-m3-surfaceContainerHighest dark:bg-m3-surfaceContainerHighestDark rounded-t-md border-b-2 border-m3-outline dark:border-m3-outlineDark placeholder-gray-500">
</div>
- <div class="flex flex-col gap-1.5 mb-4">
+
+ <div class="flex flex-col gap-1.5 mb-6">
<label for="url-custom-id" class="text-sm font-medium text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark">Custom ID (optional)</label>
<input type="text" id="url-custom-id" placeholder="e.g., logo" class="px-4 py-3 bg-m3-surfaceContainerHighest dark:bg-m3-surfaceContainerHighestDark rounded-t-md border-b-2 border-m3-outline dark:border-m3-outlineDark placeholder-gray-500">
</div>
- <div class="flex flex-col gap-1.5 mb-6">
- <label class="text-sm font-medium text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark">Link expiry (optional)</label>
- <div id="url-expiry-dd" class="dd" data-value="">
- <div class="dd-trigger px-4 py-3 bg-m3-surfaceContainerHighest dark:bg-m3-surfaceContainerHighestDark rounded-t-md border-b-2 border-m3-outline dark:border-m3-outlineDark">
- <span class="dd-label">Never (permanent link)</span>
- <span class="material-symbols-outlined dd-chevron text-[20px]">expand_more</span>
- </div>
- <div class="dd-panel bg-m3-surfaceContainerHigh dark:bg-m3-surfaceContainerHighDark rounded-[12px] shadow-lg">
- <div class="dd-option px-4 py-3 hover:bg-m3-surfaceContainerHighest dark:hover:bg-m3-surfaceContainerHighestDark selected" data-value="">Never (permanent link)</div>
- <div class="dd-option px-4 py-3 hover:bg-m3-surfaceContainerHighest dark:hover:bg-m3-surfaceContainerHighestDark" data-value="60">1 hour</div>
- <div class="dd-option px-4 py-3 hover:bg-m3-surfaceContainerHighest dark:hover:bg-m3-surfaceContainerHighestDark" data-value="1440">1 day</div>
- <div class="dd-option px-4 py-3 hover:bg-m3-surfaceContainerHighest dark:hover:bg-m3-surfaceContainerHighestDark" data-value="10080">7 days</div>
- <div class="dd-option px-4 py-3 hover:bg-m3-surfaceContainerHighest dark:hover:bg-m3-surfaceContainerHighestDark" data-value="43200">30 days</div>
- </div>
- </div>
- </div>
+
<button id="url-upload-btn" class="w-full bg-m3-primary dark:bg-m3-primaryDark text-m3-onPrimary dark:text-m3-onPrimaryDark px-4 py-3 rounded-[8px] text-sm font-semibold mb-6">Fetch & Upload</button>
<div id="url-results-list" class="flex flex-col gap-3 mb-6"></div>
+
<div id="url-error-container" class="hide bg-m3-errorContainer dark:bg-m3-errorContainerDark text-m3-onErrorContainer dark:text-m3-onErrorContainerDark p-4 rounded-[16px] mb-6">
<div class="flex items-start gap-3">
<span class="material-symbols-outlined">error</span>
@@ -210,44 +162,6 @@
</div>
<script>
- // ---------- Custom dropdown ----------
- function initDropdown(id) {
- const dd = document.getElementById(id);
- const trigger = dd.querySelector('.dd-trigger');
- const label = dd.querySelector('.dd-label');
- const options = dd.querySelectorAll('.dd-option');
-
- trigger.addEventListener('click', (e) => {
- e.stopPropagation();
- document.querySelectorAll('.dd.open').forEach(other => { if (other !== dd) other.classList.remove('open'); });
- dd.classList.toggle('open');
- });
-
- options.forEach(opt => {
- opt.addEventListener('click', () => {
- options.forEach(o => o.classList.remove('selected'));
- opt.classList.add('selected');
- label.textContent = opt.textContent;
- dd.dataset.value = opt.dataset.value;
- dd.classList.remove('open');
- });
- });
- }
- initDropdown('expiry-dd');
- initDropdown('url-expiry-dd');
- document.addEventListener('click', () => {
- document.querySelectorAll('.dd.open').forEach(dd => dd.classList.remove('open'));
- });
- function ddValue(id) { return document.getElementById(id).dataset.value || ''; }
- function resetDropdown(id) {
- const dd = document.getElementById(id);
- const first = dd.querySelector('.dd-option');
- dd.querySelectorAll('.dd-option').forEach(o => o.classList.remove('selected'));
- first.classList.add('selected');
- dd.querySelector('.dd-label').textContent = first.textContent;
- dd.dataset.value = '';
- }
-
// ---------- Tabs ----------
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => {
@@ -257,6 +171,7 @@
});
btn.classList.add('tab-active');
btn.classList.remove('text-m3-onSurfaceVariant', 'dark:text-m3-onSurfaceVariantDark');
+
document.querySelectorAll('.tab-panel').forEach(p => p.classList.add('hide'));
document.getElementById(btn.dataset.tab).classList.remove('hide');
});
@@ -286,7 +201,7 @@
if (fileInput.files.length) uploadFiles(Array.from(fileInput.files));
});
- // Paste-to-upload (only when the Upload tab is visible)
+ // Paste-to-upload
document.addEventListener('paste', (e) => {
if (document.getElementById('upload-tab').classList.contains('hide')) return;
const items = e.clipboardData && e.clipboardData.files;
@@ -307,20 +222,19 @@
container.classList.add('show-flex');
}
- function renderResultCard(container, item) {
+ // Creates a single DOM element for a file result
+ function createResultCardDOM(item) {
const origin = window.location.origin;
const finalUrl = origin + item.link_path;
+
const card = document.createElement('div');
card.className = "bg-m3-secondaryContainer dark:bg-m3-secondaryContainerDark p-4 rounded-[16px] border border-transparent";
- const expiryNote = item.expires_at
- ? `<div class="text-xs text-m3-onSecondaryContainer dark:text-m3-onSecondaryContainerDark opacity-80 mb-2">Expires: ${new Date(item.expires_at).toLocaleString()}</div>`
- : '';
+
card.innerHTML = `
<div class="flex items-center gap-2 mb-2">
<span class="material-symbols-outlined text-m3-onSecondaryContainer dark:text-m3-onSecondaryContainerDark">check_circle</span>
<span class="font-bold text-m3-onSecondaryContainer dark:text-m3-onSecondaryContainerDark text-sm">${item.item_id}</span>
</div>
- ${expiryNote}
<div class="flex flex-col gap-3 bg-m3-background dark:bg-m3-backgroundDark p-3 rounded-[8px]">
<div class="text-sm break-all select-all font-medium link-text">${finalUrl}</div>
<div class="flex gap-2 w-full">
@@ -335,6 +249,7 @@
</button>
</div>
</div>`;
+
card.querySelector('.copy-btn').addEventListener('click', function () {
navigator.clipboard.writeText(finalUrl).catch(() => {});
const btn = this;
@@ -349,15 +264,57 @@
label.textContent = 'Copy';
}, 1500);
});
+
card.querySelector('.qr-btn').addEventListener('click', () => openQrModal(finalUrl, item.item_id));
- container.prepend(card);
+
+ return card;
+ }
+
+ // Renders the structured batch layout or single file layout
+ function renderUploadSession(container, files, batchPath) {
+ const sessionDiv = document.createElement('div');
+ sessionDiv.className = "flex flex-col mb-8 border-b-2 border-m3-surfaceContainerHigh dark:border-m3-surfaceContainerHighDark pb-6";
+
+ if (batchPath) {
+ // Render Batch Link section
+ const batchHeader = document.createElement('h3');
+ batchHeader.className = "text-base font-bold text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark mb-3 uppercase tracking-wide";
+ batchHeader.innerText = "Batch Link:";
+ sessionDiv.appendChild(batchHeader);
+
+ sessionDiv.appendChild(createResultCardDOM({
+ item_id: "All Files (Batch)",
+ link_path: batchPath
+ }));
+
+ // Render Individual Links section
+ const indHeader = document.createElement('h3');
+ indHeader.className = "text-base font-bold text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark mt-6 mb-3 uppercase tracking-wide";
+ indHeader.innerText = "Individual File Links:";
+ sessionDiv.appendChild(indHeader);
+ } else {
+ const singleHeader = document.createElement('h3');
+ singleHeader.className = "text-base font-bold text-m3-onSurfaceVariant dark:text-m3-onSurfaceVariantDark mb-3 uppercase tracking-wide";
+ singleHeader.innerText = "Uploaded File:";
+ sessionDiv.appendChild(singleHeader);
+ }
+
+ // Render files
+ const filesContainer = document.createElement('div');
+ filesContainer.className = "flex flex-col gap-3";
+ files.forEach(f => filesContainer.appendChild(createResultCardDOM(f)));
+ sessionDiv.appendChild(filesContainer);
+
+ // Prepend the whole block to the top of results
+ container.prepend(sessionDiv);
}
function uploadFiles(files) {
resetUploadUI();
-
+
const formData = new FormData();
files.forEach(f => formData.append('files', f));
+
const customId = customIdInput.value.trim();
if (customId) {
if (files.length > 1) {
@@ -366,45 +323,37 @@
}
formData.append('custom_id', customId);
}
- const expiry = ddValue('expiry-dd');
- if (expiry) formData.append('expires_in_minutes', expiry);
-
+
progressContainer.classList.remove('hide'); progressContainer.classList.add('show');
-
+
const xhr = new XMLHttpRequest();
xhr.open('POST', '/api/upload', true);
-
+
xhr.upload.onprogress = function (e) {
if (e.lengthComputable) {
const pct = Math.round((e.loaded / e.total) * 100);
progressBar.style.width = pct + '%';
progressText.innerText = pct + '%';
+
if (pct === 100) {
progressContainer.classList.remove('show'); progressContainer.classList.add('hide');
processingContainer.classList.remove('hide'); processingContainer.classList.add('show');
}
}
};
-
+
xhr.onload = function () {
progressContainer.classList.remove('show'); progressContainer.classList.add('hide');
processingContainer.classList.remove('show'); processingContainer.classList.add('hide');
-
+
if (xhr.status >= 200 && xhr.status < 300) {
try {
const data = JSON.parse(xhr.responseText);
-
- if (data.batch_path) {
- renderResultCard(resultsList, {
- item_id: "Batch Link (All Files)",
- link_path: data.batch_path,
- expires_at: data.batch_expires_at
- });
- }
- data.files.forEach(item => renderResultCard(resultsList, item));
+ // Pass to our new structured rendering function
+ renderUploadSession(resultsList, data.files, data.batch_path);
+
fileInput.value = ''; customIdInput.value = '';
- resetDropdown('expiry-dd');
} catch (err) {
showError(errorContainer, errorText, "Invalid response from server.");
}
@@ -417,13 +366,13 @@
}
}
};
-
+
xhr.onerror = function () {
progressContainer.classList.remove('show'); progressContainer.classList.add('hide');
processingContainer.classList.remove('show'); processingContainer.classList.add('hide');
showError(errorContainer, errorText, "System error: Could not connect to server.");
};
-
+
xhr.send(formData);
}
@@ -439,24 +388,24 @@
urlErrorContainer.classList.remove('show-flex'); urlErrorContainer.classList.add('hide');
const url = urlInput.value.trim();
if (!url) { showError(urlErrorContainer, urlErrorText, "Enter a URL first."); return; }
-
+
const formData = new FormData();
formData.append('url', url);
+
const cid = urlCustomId.value.trim();
if (cid) formData.append('custom_id', cid);
- const exp = ddValue('url-expiry-dd');
- if (exp) formData.append('expires_in_minutes', exp);
-
+
urlUploadBtn.disabled = true;
urlUploadBtn.innerText = 'Fetching & uploading...';
-
+
try {
const res = await fetch('/api/upload-url', { method: 'POST', body: formData });
const data = await res.json();
if (!res.ok) throw new Error(data.detail || 'Upload failed.');
- data.files.forEach(item => renderResultCard(urlResultsList, item));
+
+ renderUploadSession(urlResultsList, data.files, null);
+
urlInput.value = ''; urlCustomId.value = '';
- resetDropdown('url-expiry-dd');
} catch (err) {
showError(urlErrorContainer, urlErrorText, err.message);
} finally {
@@ -477,9 +426,11 @@
new QRCode(qrCodeBox, { text: url, width: 220, height: 220 });
qrModal.classList.remove('hide'); qrModal.classList.add('show-flex');
}
+
document.getElementById('qr-close-btn').addEventListener('click', () => {
qrModal.classList.remove('show-flex'); qrModal.classList.add('hide');
});
+
qrDownloadBtn.addEventListener('click', () => {
const canvas = qrCodeBox.querySelector('canvas');
if (!canvas) return;