This commit is contained in:
Anaïs Saraiva
2026-08-15 00:35:22 +02:00
parent 954384d1e4
commit d8b9fbe993
8 changed files with 578 additions and 388 deletions
+98 -73
View File
@@ -6,7 +6,11 @@ const STORAGE_KEY = "filehost.uploads";
const dropzone = $("dropzone");
const fileInput = $("fileInput");
const chosenFile = $("chosenFile");
const pwInput = $("pwInput");
const uploadBtn = $("uploadBtn");
const newUploadBtn = $("newUploadBtn");
const clearListBtn = $("clearListBtn");
const progress = $("progress");
const progressBar = $("progressBar");
const progressPct = $("progressPct");
@@ -14,20 +18,17 @@ const progressName = $("progressName");
const result = $("result");
const resultUrl = $("resultUrl");
const resultName = $("resultName");
const resultSize = $("resultSize");
const resultLock = $("resultLock");
const copyBtn = $("copyBtn");
const deleteBtn = $("deleteBtn");
const againBtn = $("againBtn");
const notice = $("notice");
const fileListWrap = $("fileListWrap");
const fileList = $("fileList");
const clearListBtn = $("clearListBtn");
const fileCount = $("fileCount");
const emptyState = $("emptyState");
let current = null;
let currentFile = null;
let uploading = false;
function formatBytes(bytes) {
if (!Number.isFinite(bytes) || bytes < 0) return "-";
if (bytes < 1024) return `${bytes} B`;
const units = ["KB", "MB", "GB", "TB"];
let value = bytes;
@@ -57,18 +58,21 @@ function setProgress(loaded, total) {
function reset() {
uploading = false;
current = null;
currentFile = null;
result.classList.add("hidden");
resultLock.classList.add("hidden");
progress.classList.add("hidden");
progressBar.style.width = "0%";
progressPct.textContent = "0%";
chosenFile.classList.add("hidden");
chosenFile.textContent = "";
fileInput.value = "";
pwInput.value = "";
uploadBtn.disabled = false;
uploadBtn.textContent = "Upload";
clearNotice();
}
// ---- client-side upload history (localStorage) ----
// ---- client-side history (localStorage) ----
function loadUploads() {
try {
@@ -84,7 +88,7 @@ function saveUploads(list) {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(list));
} catch {
/* storage unavailable (e.g. private mode) — ignore */
/* storage unavailable — ignore */
}
}
@@ -142,80 +146,79 @@ function setCopyFeedback(btn, label) {
}, 1500);
}
function formatDate(iso) {
try {
return new Date(iso).toLocaleString();
} catch {
return "";
}
}
function renderList() {
const list = loadUploads();
if (list.length === 0) {
fileListWrap.classList.add("hidden");
fileList.innerHTML = "";
return;
}
fileListWrap.classList.remove("hidden");
fileList.innerHTML = "";
emptyState.classList.toggle("hidden", list.length > 0);
fileCount.textContent = list.length === 0 ? "" : `${list.length} file${list.length === 1 ? "" : "s"}`;
for (const item of list) {
const li = document.createElement("li");
li.className = "file-item";
const meta = document.createElement("div");
meta.className = "file-item-meta";
const tr = document.createElement("tr");
const nameTd = document.createElement("td");
const nameWrap = document.createElement("div");
nameWrap.className = "file-name";
const name = document.createElement("span");
name.className = "file-item-name";
name.textContent = item.originalName;
name.title = item.originalName;
const lock = document.createElement("span");
lock.className = "lock";
lock.textContent = "Protected";
nameWrap.appendChild(name);
nameWrap.appendChild(lock);
nameTd.appendChild(nameWrap);
if (item.protected) {
const lock = document.createElement("span");
lock.className = "lock";
lock.textContent = "Password protected";
name.appendChild(lock);
}
const sizeTd = document.createElement("td");
sizeTd.className = "file-size";
sizeTd.textContent = formatBytes(item.size);
const sub = document.createElement("span");
sub.className = "file-item-sub";
let subText = formatBytes(item.size || 0);
if (item.createdAt) {
try {
subText += ` \u00b7 ${new Date(item.createdAt).toLocaleString()}`;
} catch {
/* ignore date formatting */
}
}
sub.textContent = subText;
meta.appendChild(name);
meta.appendChild(sub);
const dateTd = document.createElement("td");
dateTd.className = "file-date";
dateTd.textContent = formatDate(item.createdAt);
const actionsTd = document.createElement("td");
const actions = document.createElement("div");
actions.className = "file-item-actions";
actions.className = "row-actions";
const dl = document.createElement("a");
dl.className = "btn-link";
dl.className = "btn btn-primary";
dl.href = downloadUrl(item);
dl.textContent = "Download";
dl.title = "Open download link";
const copy = document.createElement("button");
copy.type = "button";
copy.className = "btn btn-ghost";
copy.textContent = "Copy";
copy.addEventListener("click", async () => {
const ok = await copyText(downloadUrl(item));
setCopyFeedback(copy, ok ? "Copied" : "Copy failed");
setCopyFeedback(copy, ok ? "Copied" : "Failed");
});
const del = document.createElement("button");
del.type = "button";
del.className = "danger";
del.className = "btn btn-danger";
del.textContent = "Delete";
del.addEventListener("click", () => deleteFile(item, () => removeUpload(item.id)));
actions.appendChild(dl);
actions.appendChild(copy);
actions.appendChild(del);
actionsTd.appendChild(actions);
li.appendChild(meta);
li.appendChild(actions);
fileList.appendChild(li);
tr.appendChild(nameTd);
tr.appendChild(sizeTd);
tr.appendChild(dateTd);
tr.appendChild(actionsTd);
fileList.appendChild(tr);
}
}
@@ -251,32 +254,30 @@ function showResult(data) {
progress.classList.add("hidden");
result.classList.remove("hidden");
resultName.textContent = data.originalName;
resultSize.textContent = formatBytes(data.size);
resultUrl.value = downloadUrl(data);
resultLock.classList.toggle("hidden", !data.protected);
current = data;
addUpload({
id: data.id,
key: data.key,
url: data.url,
originalName: data.originalName,
size: data.size,
protected: !!data.protected,
createdAt: new Date().toISOString(),
});
}
function startUpload(file) {
function startUpload(file, password) {
if (uploading) return;
clearNotice();
progress.classList.remove("hidden");
progressName.textContent = file.name;
setProgress(0, file.size);
uploading = true;
uploadBtn.disabled = true;
uploadBtn.textContent = "Uploading\u2026";
const xhr = new XMLHttpRequest();
const form = new FormData();
if (pwInput.value) form.append("password", pwInput.value);
form.append("password", password);
form.append("file", file);
xhr.upload.addEventListener("progress", (e) => {
@@ -285,11 +286,13 @@ function startUpload(file) {
xhr.addEventListener("load", () => {
uploading = false;
uploadBtn.disabled = false;
uploadBtn.textContent = "Upload";
if (xhr.status >= 200 && xhr.status < 300) {
progress.classList.add("hidden");
try {
showResult(JSON.parse(xhr.responseText));
} catch {
progress.classList.add("hidden");
showNotice("Invalid server response.", "error");
}
return;
@@ -307,12 +310,16 @@ function startUpload(file) {
xhr.addEventListener("error", () => {
uploading = false;
uploadBtn.disabled = false;
uploadBtn.textContent = "Upload";
progress.classList.add("hidden");
showNotice("Network error during upload.", "error");
});
xhr.addEventListener("abort", () => {
uploading = false;
uploadBtn.disabled = false;
uploadBtn.textContent = "Upload";
progress.classList.add("hidden");
});
@@ -320,13 +327,30 @@ function startUpload(file) {
xhr.send(form);
}
function handleFile(file) {
function selectFile(file) {
if (!file) return;
if (file.size === 0) {
showNotice("Empty files are not supported.", "error");
return;
}
startUpload(file);
currentFile = file;
chosenFile.textContent = file.name;
chosenFile.classList.remove("hidden");
clearNotice();
}
function submitUpload() {
if (!currentFile) {
showNotice("Choose a file first.", "error");
return;
}
const password = pwInput.value;
if (!password) {
showNotice("A password is required to protect this file.", "error");
pwInput.focus();
return;
}
startUpload(currentFile, password);
}
dropzone.addEventListener("click", () => {
@@ -342,7 +366,18 @@ dropzone.addEventListener("keydown", (e) => {
fileInput.addEventListener("change", () => {
const file = fileInput.files ? fileInput.files[0] : null;
handleFile(file);
selectFile(file);
});
pwInput.addEventListener("keydown", (e) => {
if (e.key === "Enter") submitUpload();
});
uploadBtn.addEventListener("click", submitUpload);
newUploadBtn.addEventListener("click", () => {
window.scrollTo({ top: 0, behavior: "smooth" });
pwInput.focus();
});
let dragDepth = 0;
@@ -365,28 +400,18 @@ document.addEventListener("drop", (e) => {
dropzone.classList.remove("drag");
if (uploading) return;
const file = e.dataTransfer && e.dataTransfer.files ? e.dataTransfer.files[0] : null;
if (file) handleFile(file);
selectFile(file);
});
copyBtn.addEventListener("click", async () => {
const ok = await copyText(resultUrl.value);
setCopyFeedback(copyBtn, ok ? "Copied" : "Copy failed");
setCopyFeedback(copyBtn, ok ? "Copied" : "Failed");
});
deleteBtn.addEventListener("click", () => {
if (!current) return;
deleteFile(current, () => {
removeUpload(current.id);
reset();
});
});
againBtn.addEventListener("click", reset);
clearListBtn.addEventListener("click", () => {
if (loadUploads().length === 0) return;
const ok = window.confirm(
"Clear this list? This only removes the entries from THIS browser. The uploaded files remain on the server.",
"Clear this list? This only removes the entries from THIS browser. The files remain on the server.",
);
if (!ok) return;
saveUploads([]);