uploads

Log | Files | Refs

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:
Mindex.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 &amp; 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;
© notamitgamer • Site Built: 2026-07-21 13:58:23 UTC • git-mirror commit: 1037f62 [view raw info]
Originally created with stagit • modified by notamitgamer
Forked from github.com/notamitgamer/git-mirror