(() => { 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 = "
No recent uploads
"; return; } arr.forEach(item => { const el = document.createElement("div"); el.className = "history-item"; // show inline thumbnail only for images (browser will try to load) const thumbStyle = (item.filename && item.filename.match(/\.(jpg|jpeg|png|gif|webp)$/i)) ? `background-image:url(${window.location.origin}/f/${item.slug})` : ""; el.innerHTML = `
${escapeHtml(item.filename || item.slug)}
${timeAgo(item.created_at)}
`; 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(); }); } // modal api - robust handlers if (apiBtn && apiModal) { apiBtn.addEventListener("click", () => { apiModal.hidden = false; // focus first interactive element in modal if any const focusable = apiModal.querySelector("button, [tabindex]:not([tabindex='-1'])"); if (focusable) focusable.focus(); }); } // close click handler (guarded) if (closeApi && apiModal) { closeApi.addEventListener("click", () => { apiModal.hidden = true; }); } // click outside modal content (backdrop) if (apiModal) { apiModal.addEventListener("click", (e) => { // if click directly on the backdrop (modal container) close it if (e.target === apiModal) { apiModal.hidden = true; } }); } // close on Escape key 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")); }); } // small helpers 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; } } // UI init renderHistory(); // optional: restore last history item to current (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; } })(); })();