tryit / static /js /app.js
triflix's picture
Update static/js/app.js
01ed3be verified
(() => {
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 = "<div class='muted'>No recent uploads</div>";
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 = `
<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();
});
}
// 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) => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#039;'}[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;
}
})();
})();