| (() => { |
| const fileInput = document.getElementById("fileInput"); |
| const uploadCard = document.getElementById("uploadCard"); |
| const placeholder = document.getElementById("placeholder"); |
| const preview = document.getElementById("preview"); |
| const previewThumb = document.getElementById("previewThumb"); |
| const previewName = document.getElementById("previewName"); |
| const previewSize = document.getElementById("previewSize"); |
| const uploadProgress = document.getElementById("uploadProgress"); |
| const progressText = document.getElementById("progressText"); |
| const uploadBtn = document.getElementById("uploadBtn"); |
| const copyBtn = document.getElementById("copyBtn"); |
| const viewBtn = document.getElementById("viewBtn"); |
| const dlBtn = document.getElementById("dlBtn"); |
| const historyList = document.getElementById("historyList"); |
| const clearHistoryBtn = document.getElementById("clearHistory"); |
| const apiBtn = document.getElementById("apiBtn"); |
| |
| const apiModal = document.getElementById("apiModal"); |
| const closeApi = document.getElementById("closeApi"); |
| const copyCurl = document.getElementById("copyCurl"); |
| const curlExample = document.getElementById("curlExample"); |
|
|
| const MAX_BYTES = window.APP_CONFIG.MAX_BYTES || (2 * 1024 * 1024 * 1024); |
| const EXPIRE_SECONDS = window.APP_CONFIG.EXPIRE_SECONDS || (3 * 3600); |
|
|
| let currentFile = null; |
| let currentSlug = null; |
| let currentUrl = null; |
|
|
| |
| function humanFileSize(bytes) { |
| if (bytes === 0) return "0 B"; |
| const k = 1024; |
| const dm = 1; |
| const sizes = ["B","KB","MB","GB","TB"]; |
| const i = Math.floor(Math.log(bytes) / Math.log(k)); |
| return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + " " + sizes[i]; |
| } |
|
|
| |
| if (placeholder && fileInput) { |
| placeholder.addEventListener("click", () => fileInput.click()); |
| } |
|
|
| if (fileInput) { |
| fileInput.addEventListener("change", (ev) => { |
| const f = ev.target.files[0]; |
| if (!f) return; |
| selectFile(f); |
| }); |
| } |
|
|
| function selectFile(file) { |
| |
| if (file.size > MAX_BYTES) { |
| alert("File larger than max allowed (2GB)."); |
| return; |
| } |
| const banned = ["bat","exe","cmd","sh","msi","ps1","com","scr"]; |
| const ext = (file.name.split(".").pop() || "").toLowerCase(); |
| if (banned.includes(ext)) { |
| alert("File type not allowed."); |
| return; |
| } |
| currentFile = file; |
| previewName.textContent = file.name; |
| previewSize.textContent = humanFileSize(file.size); |
| |
| if (file.type && file.type.startsWith("image/")) { |
| const url = URL.createObjectURL(file); |
| previewThumb.style.backgroundImage = `url(${url})`; |
| previewThumb.textContent = ""; |
| } else { |
| previewThumb.style.backgroundImage = 'none'; |
| previewThumb.textContent = ext ? ext.toUpperCase() : ""; |
| } |
| if (placeholder) placeholder.hidden = true; |
| if (preview) preview.hidden = false; |
| if (uploadProgress) uploadProgress.value = 0; |
| if (progressText) progressText.textContent = ""; |
| } |
|
|
| async function uploadSelectedFile(customSlug = "") { |
| if (!currentFile) { |
| |
| if (fileInput) fileInput.click(); |
| return; |
| } |
| const fd = new FormData(); |
| fd.append("file", currentFile); |
| if (customSlug) fd.append("custom_slug", customSlug); |
|
|
| const xhr = new XMLHttpRequest(); |
| xhr.open("POST", "/api/upload", true); |
|
|
| xhr.upload.onprogress = (e) => { |
| if (e.lengthComputable) { |
| const pct = Math.floor((e.loaded / e.total) * 100); |
| if (uploadProgress) uploadProgress.value = pct; |
| if (progressText) progressText.textContent = `${pct}%`; |
| } |
| }; |
|
|
| xhr.onload = function () { |
| if (xhr.status >= 200 && xhr.status < 300) { |
| const data = JSON.parse(xhr.responseText); |
| currentSlug = data.slug; |
| currentUrl = data.url; |
| |
| try { |
| navigator.clipboard.writeText(window.location.origin + currentUrl); |
| showToast("Link copied to clipboard"); |
| } catch (e) {} |
| |
| addHistory({ |
| slug: data.slug, |
| url: data.url, |
| filename: data.filename, |
| size: data.size, |
| created_at: Date.now(), |
| expires_at: data.expires_at * 1000 |
| }); |
| renderHistory(); |
| } else { |
| const err = tryParseJSON(xhr.responseText); |
| alert((err && err.detail) ? err.detail : "Upload failed."); |
| } |
| if (progressText) progressText.textContent = ""; |
| if (uploadProgress) uploadProgress.value = 0; |
| |
| if (currentSlug) { |
| currentUrl = `/f/${currentSlug}`; |
| } |
| }; |
|
|
| xhr.onerror = function () { |
| alert("Upload failed due to network error."); |
| }; |
|
|
| xhr.send(fd); |
| } |
|
|
| |
| if (uploadBtn) { |
| uploadBtn.addEventListener("click", async (ev) => { |
| |
| const isShift = ev.shiftKey; |
| let custom = ""; |
| if (isShift) { |
| custom = prompt("Enter custom slug (letters, numbers, -, _ )"); |
| if (!custom) custom = ""; |
| } |
| await uploadSelectedFile(custom); |
| }); |
| } |
|
|
| if (copyBtn) { |
| copyBtn.addEventListener("click", () => { |
| if (!currentUrl && !currentSlug) return showToast("No link yet"); |
| const u = window.location.origin + (currentUrl || `/f/${currentSlug}`); |
| navigator.clipboard.writeText(u).then(() => showToast("Copied")); |
| }); |
| } |
|
|
| if (viewBtn) { |
| viewBtn.addEventListener("click", () => { |
| if (!currentUrl && !currentSlug) return showToast("No file to view"); |
| const u = (currentUrl || `/f/${currentSlug}`); |
| window.open(u, "_blank"); |
| }); |
| } |
|
|
| if (dlBtn) { |
| dlBtn.addEventListener("click", () => { |
| if (!currentUrl && !currentSlug) return showToast("No file to download"); |
| const u = (currentUrl || `/f/${currentSlug}`) + "?dl=1"; |
| window.open(u, "_blank"); |
| }); |
| } |
|
|
| |
| function historyKey(){ return "doto_history_v1" } |
| function getHistory(){ |
| try { |
| const raw = localStorage.getItem(historyKey()); |
| if (!raw) return []; |
| const parsed = JSON.parse(raw); |
| |
| const now = Date.now(); |
| return (parsed || []).filter(it => !it.expires_at || it.expires_at > now); |
| } catch (e) { |
| return []; |
| } |
| } |
| function addHistory(item){ |
| const arr = getHistory(); |
| arr.unshift(item); |
| |
| localStorage.setItem(historyKey(), JSON.stringify(arr.slice(0,50))); |
| } |
| function clearHistory(){ |
| localStorage.removeItem(historyKey()); |
| renderHistory(); |
| } |
|
|
| function renderHistory(){ |
| const arr = getHistory(); |
| if (!historyList) return; |
| historyList.innerHTML = ""; |
| if (arr.length === 0) { |
| historyList.innerHTML = "<div class='muted'>No recent uploads</div>"; |
| return; |
| } |
| arr.forEach(item => { |
| const el = document.createElement("div"); |
| el.className = "history-item"; |
| |
| const thumbStyle = (item.filename && item.filename.match(/\.(jpg|jpeg|png|gif|webp)$/i)) |
| ? `background-image:url(${window.location.origin}/f/${item.slug})` : ""; |
| el.innerHTML = ` |
| <div class="history-thumb" style="${thumbStyle}"></div> |
| <div class="history-meta"> |
| <div style="font-weight:700">${escapeHtml(item.filename || item.slug)}</div> |
| <div style="font-size:12px;color:var(--muted)">${timeAgo(item.created_at)}</div> |
| </div> |
| <div style="display:flex;flex-direction:column;gap:6px"> |
| <button class="link-btn" data-slug="${item.slug}" data-action="copy">Copy</button> |
| <button class="link-btn" data-slug="${item.slug}" data-action="open">Open</button> |
| </div> |
| `; |
| historyList.appendChild(el); |
| }); |
| } |
|
|
| if (historyList) { |
| historyList.addEventListener("click", (e) => { |
| const btn = e.target.closest("button"); |
| if (!btn) return; |
| const slug = btn.dataset.slug; |
| const action = btn.dataset.action; |
| if (action === "copy") { |
| const u = window.location.origin + `/f/${slug}`; |
| navigator.clipboard.writeText(u).then(()=> showToast("Copied")); |
| } else if (action === "open") { |
| window.open(`/f/${slug}`, "_blank"); |
| } |
| }); |
| } |
|
|
| if (clearHistoryBtn) { |
| clearHistoryBtn.addEventListener("click", () => { |
| if (confirm("Clear local history?")) clearHistory(); |
| }); |
| } |
|
|
| |
| if (apiBtn && apiModal) { |
| apiBtn.addEventListener("click", () => { |
| apiModal.hidden = false; |
| |
| const focusable = apiModal.querySelector("button, [tabindex]:not([tabindex='-1'])"); |
| if (focusable) focusable.focus(); |
| }); |
| } |
|
|
| |
| if (closeApi && apiModal) { |
| closeApi.addEventListener("click", () => { |
| apiModal.hidden = true; |
| }); |
| } |
|
|
| |
| if (apiModal) { |
| apiModal.addEventListener("click", (e) => { |
| |
| if (e.target === apiModal) { |
| apiModal.hidden = true; |
| } |
| }); |
| } |
|
|
| |
| document.addEventListener("keydown", (e) => { |
| if (e.key === "Escape" && apiModal && !apiModal.hidden) { |
| apiModal.hidden = true; |
| } |
| }); |
|
|
| if (copyCurl && curlExample) { |
| copyCurl.addEventListener("click", () => { |
| navigator.clipboard.writeText(curlExample.textContent).then(()=> showToast("Copied")); |
| }); |
| } |
|
|
| |
| function showToast(text) { |
| const t = document.createElement("div"); |
| t.textContent = text; |
| t.style.position = "fixed"; |
| t.style.bottom = "86px"; |
| t.style.left = "50%"; |
| t.style.transform = "translateX(-50%)"; |
| t.style.background = "#111"; |
| t.style.color = "#fff"; |
| t.style.padding = "10px 14px"; |
| t.style.borderRadius = "10px"; |
| t.style.zIndex = 2000; |
| document.body.appendChild(t); |
| setTimeout(()=> t.remove(), 1800); |
| } |
|
|
| function timeAgo(ts){ |
| const s = Math.floor((Date.now() - ts)/1000); |
| if (s < 60) return `${s}s ago`; |
| if (s < 3600) return `${Math.floor(s/60)}m ago`; |
| if (s < 86400) return `${Math.floor(s/3600)}h ago`; |
| return `${Math.floor(s/86400)}d ago`; |
| } |
|
|
| function escapeHtml(s){ |
| if(!s) return ""; |
| return s.replace(/[&<>"']/g, (m) => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[m])); |
| } |
|
|
| function tryParseJSON(text){ |
| try { return JSON.parse(text); } catch(e){ return null; } |
| } |
|
|
| |
| renderHistory(); |
|
|
| |
| (function restoreLast(){ |
| const arr = getHistory(); |
| if (arr.length > 0) { |
| currentSlug = arr[0].slug; |
| currentUrl = `/f/${currentSlug}`; |
| currentFile = null; |
| previewName.textContent = arr[0].filename; |
| previewSize.textContent = humanFileSize(arr[0].size || 0); |
| if (placeholder) placeholder.hidden = true; |
| if (preview) preview.hidden = false; |
| } |
| })(); |
|
|
| })(); |
|
|