(() => { 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"); // NOTE: these might be null in some edge cases, so guard below 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; // helper 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]; } // show placeholder click to open file picker 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) { // client-side checks 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); // preview thumb for images 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) { // trigger file picker 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; // show toast and copy to clipboard try { navigator.clipboard.writeText(window.location.origin + currentUrl); showToast("Link copied to clipboard"); } catch (e) {} // save history 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; // keep preview visible and set currentUrl for view/download if (currentSlug) { currentUrl = `/f/${currentSlug}`; } }; xhr.onerror = function () { alert("Upload failed due to network error."); }; xhr.send(fd); } // wire upload button if (uploadBtn) { uploadBtn.addEventListener("click", async (ev) => { // Shift-click allows custom slug (legacy behavior) 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"); }); } // history in localStorage function historyKey(){ return "doto_history_v1" } function getHistory(){ try { const raw = localStorage.getItem(historyKey()); if (!raw) return []; const parsed = JSON.parse(raw); // filter expired entries client-side 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); // cap to 50 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 = "